Niedawno natknęliśmy się na interesującą sprawę obsługi klienta dotyczącą konfiguracji replikacji MariaDB. Spędziliśmy dużo czasu na badaniu tego problemu i pomyśleliśmy, że warto podzielić się tym z Tobą w tym poście na blogu.
Opis środowiska klienta
Problem był następujący:używany był stary (sprzed 10.x) serwer MariaDB i podjęto próbę migracji z niego danych do nowszej konfiguracji replikacji MariaDB. Spowodowało to problemy z używaniem Mariabackup do odbudowy urządzeń podrzędnych w nowym klastrze replikacji. Na potrzeby testów odtworzyliśmy to zachowanie w następującym środowisku:
Dane zostały przeniesione z wersji 5.5 do 10.4 za pomocą mysqldump:
mysqldump --single-transaction --master-data=2 --events --routines sbtest > /root/dump.sql
To pozwoliło nam zebrać główne współrzędne dziennika binarnego i spójny zrzut. W rezultacie mogliśmy udostępnić węzeł główny MariaDB 10.4 i skonfigurować replikację między starym węzłem głównym w wersji 5.5 a nowym węzłem 10.4. Ruch nadal działał na węźle 5.5. Urządzenie główne 10.4 generowało identyfikatory GTID, ponieważ musiało replikować dane do urządzenia podrzędnego 10.4. Zanim zagłębimy się w szczegóły, przyjrzyjmy się, jak działa GTID w MariaDB.
MariaDB i GTID
Na początek MariaDB używa innego formatu identyfikatora GTID niż Oracle MySQL. Składa się z trzech liczb oddzielonych myślnikami:
0 - 1 - 345
Po pierwsze to domena replikacji, która pozwala na prawidłową obsługę replikacji wielu źródeł. Nie dotyczy to naszego przypadku, ponieważ wszystkie węzły znajdują się w tej samej domenie replikacji. Druga liczba to identyfikator serwera węzła, który wygenerował identyfikator GTID. Trzeci to numer sekwencyjny - wzrasta on monotonicznie z każdym zdarzeniem zapisanym w dziennikach binarnych.
MariaDB używa kilku zmiennych do przechowywania informacji o identyfikatorach GTID wykonanych na danym węźle. Najciekawsze dla nas to:
Gtid_binlog_pos — zgodnie z dokumentacją ta zmienna jest identyfikatorem GTID ostatniej grupy zdarzeń zapisanej w dzienniku binarnym.
Gtid_slave_pos — zgodnie z dokumentacją ta zmienna systemowa zawiera identyfikator GTID ostatniej transakcji zastosowanej do bazy danych przez wątki podrzędne serwera.
Gtid_current_pos — zgodnie z dokumentacją ta zmienna systemowa zawiera identyfikator GTID ostatniej transakcji zastosowanej do bazy danych. Jeśli identyfikator_serwera odpowiedniego identyfikatora GTID w gtid_binlog_pos jest równy identyfikatorowi serwera będącego własnością serwera, a numer kolejny jest wyższy niż odpowiadający identyfikator GTID w identyfikatorze gtid_slave_pos, zostanie użyty identyfikator GTID z identyfikatora gtid_binlog_pos. W przeciwnym razie dla tej domeny zostanie użyty identyfikator GTID z gtid_slave_pos.
Dlatego, aby było jasne, gtid_binlog_pos przechowuje identyfikator GTID ostatniego lokalnie wykonanego zdarzenia. Gtid_slave_pos przechowuje GTID zdarzenia wykonanego przez wątek podrzędny, a gtid_current_pos pokazuje albo wartość z gtid_binlog_pos, jeśli ma najwyższy numer sekwencji i ma identyfikator serwera lub gtid_slave_pos, jeśli ma najwyższą sekwencję. Proszę mieć to na uwadze.
Przegląd problemu
Początkowy stan odpowiednich zmiennych jest na wzorcu 10.4:
MariaDB [(none)]> show global variables like '%gtid%';
+-------------------------+----------+
| Variable_name | Value |
+-------------------------+----------+
| gtid_binlog_pos | 0-1001-1 |
| gtid_binlog_state | 0-1001-1 |
| gtid_cleanup_batch_size | 64 |
| gtid_current_pos | 0-1001-1 |
| gtid_domain_id | 0 |
| gtid_ignore_duplicates | ON |
| gtid_pos_auto_engines | |
| gtid_slave_pos | 0-1001-1 |
| gtid_strict_mode | ON |
| wsrep_gtid_domain_id | 0 |
| wsrep_gtid_mode | OFF |
+-------------------------+----------+
11 rows in set (0.001 sec)
Proszę zwrócić uwagę na gtid_slave_pos, który teoretycznie nie ma sensu - pochodzi z tego samego węzła, ale przez wątek podrzędny. Może się to zdarzyć, jeśli wcześniej wykonasz wyłącznik główny. Zrobiliśmy właśnie to - mając dwa węzły 10.4, zmieniliśmy mastery z hosta o identyfikatorze serwera 1001 na host o identyfikatorze serwera 1002, a następnie z powrotem na 1001.
Potem skonfigurowaliśmy replikację z 5.5 do 10.4 i tak to wyglądało:
MariaDB [(none)]> show global variables like '%gtid%';
+-------------------------+-------------------------+
| Variable_name | Value |
+-------------------------+-------------------------+
| gtid_binlog_pos | 0-55-117029 |
| gtid_binlog_state | 0-1001-1537,0-55-117029 |
| gtid_cleanup_batch_size | 64 |
| gtid_current_pos | 0-1001-1 |
| gtid_domain_id | 0 |
| gtid_ignore_duplicates | ON |
| gtid_pos_auto_engines | |
| gtid_slave_pos | 0-1001-1 |
| gtid_strict_mode | ON |
| wsrep_gtid_domain_id | 0 |
| wsrep_gtid_mode | OFF |
+-------------------------+-------------------------+
11 rows in set (0.000 sec)
Jak widać, zdarzenia zreplikowane z MariaDB 5.5, wszystkie zostały uwzględnione w zmiennej gtid_binlog_pos:wszystkie zdarzenia z identyfikatorem serwera 55. Powoduje to poważny problem. Jak być może pamiętasz, gtid_binlog_pos powinien zawierać zdarzenia wykonywane lokalnie na hoście. Tutaj zawiera zdarzenia zreplikowane z innego serwera o innym identyfikatorze serwera.
To sprawia, że sprawy stają się ryzykowne, gdy chcesz odbudować urządzenie podrzędne 10.4, oto dlaczego. Mariabackup, podobnie jak Xtrabackup, działa w prosty sposób. Kopiuje pliki z serwera MariaDB podczas skanowania dzienników przeróbek i przechowywania wszelkich przychodzących transakcji. Po skopiowaniu plików funkcja Mariabackup zamroziła bazę danych, używając opcji OCZYSZCZANIA TABEL Z BLOKADĄ ODCZYTU lub blokad kopii zapasowych, w zależności od wersji MariaDB i dostępności blokad kopii zapasowych. Następnie odczytuje ostatnio wykonany identyfikator GTID i przechowuje go wraz z kopią zapasową. Następnie blokada zostaje zwolniona i tworzenie kopii zapasowej zostaje zakończone. Identyfikator GTID przechowywany w kopii zapasowej powinien być używany jako ostatnio wykonany identyfikator GTID w węźle. W przypadku odbudowy slave'ów zostanie on umieszczony jako gtid_slave_pos i następnie użyty do uruchomienia replikacji GTID. Ten GTID jest pobierany z gtid_current_pos, co ma sens - w końcu jest to „GTID ostatniej transakcji zastosowanej do bazy danych”. Ostry czytelnik już widzi problem. Pokażmy dane wyjściowe zmiennych, gdy 10.4 replikuje się z wzorca 5.5:
MariaDB [(none)]> show global variables like '%gtid%';
+-------------------------+-------------------------+
| Variable_name | Value |
+-------------------------+-------------------------+
| gtid_binlog_pos | 0-55-117029 |
| gtid_binlog_state | 0-1001-1537,0-55-117029 |
| gtid_cleanup_batch_size | 64 |
| gtid_current_pos | 0-1001-1 |
| gtid_domain_id | 0 |
| gtid_ignore_duplicates | ON |
| gtid_pos_auto_engines | |
| gtid_slave_pos | 0-1001-1 |
| gtid_strict_mode | ON |
| wsrep_gtid_domain_id | 0 |
| wsrep_gtid_mode | OFF |
+-------------------------+-------------------------+
11 rows in set (0.000 sec)
Gtid_current_pos jest ustawione na 0-1001-1. To zdecydowanie nie jest właściwy moment w czasie, pochodzi z gtid_slave_pos, podczas gdy mamy kilka transakcji, które pochodzą z 5.5 po tym. Problem polega na tym, że te transakcje są przechowywane jako gtid_binlog_pos. Z drugiej strony gtid_current_pos jest obliczany w taki sposób, że wymaga identyfikatora lokalnego serwera dla identyfikatorów GTID w gitd_binlog_pos, zanim będą mogły zostać użyte jako gtid_current_pos. W naszym przypadku mają identyfikator serwera węzła 5.5, więc nie będą traktowane poprawnie jako zdarzenia wykonane na masterze 10.4. Po przywróceniu kopii zapasowej, jeśli ustawisz urządzenie podrzędne zgodnie ze stanem GTID zapisanym w kopii zapasowej, spowoduje to ponowne zastosowanie wszystkich zdarzeń pochodzących z wersji 5.5. To oczywiście przerwałoby replikację.
Rozwiązanie
Rozwiązaniem tego problemu jest wykonanie kilku dodatkowych kroków:
- Zatrzymaj replikację z 5.5 do 10.4. Uruchom STOP SLAVE na 10.4 master
- Wykonaj dowolną transakcję w dniu 10.4 — UTWÓRZ SCHEMAT, JEŚLI NIE ISTNIEJE poprawka błędu — zmieni to sytuację GTID w następujący sposób:
MariaDB [(none)]> show global variables like '%gtid%';
+-------------------------+---------------------------+
| Variable_name | Value |
+-------------------------+---------------------------+
| gtid_binlog_pos | 0-1001-117122 |
| gtid_binlog_state | 0-55-117121,0-1001-117122 |
| gtid_cleanup_batch_size | 64 |
| gtid_current_pos | 0-1001-117122 |
| gtid_domain_id | 0 |
| gtid_ignore_duplicates | ON |
| gtid_pos_auto_engines | |
| gtid_slave_pos | 0-1001-1 |
| gtid_strict_mode | ON |
| wsrep_gtid_domain_id | 0 |
| wsrep_gtid_mode | OFF |
+-------------------------+---------------------------+
11 rows in set (0.001 sec)
Najnowsze GITD zostało wykonane lokalnie, więc zostało zapisane jako gtid_binlog_pos. Ponieważ ma identyfikator lokalnego serwera, jest wybierany jako gtid_current_pos. Teraz możesz wziąć kopię zapasową i użyć jej do odbudowania slave'ów z mastera 10.4. Po wykonaniu tej czynności ponownie uruchom wątek podrzędny.
MariaDB zdaje sobie sprawę, że tego rodzaju błąd istnieje, jednym z odpowiednich raportów o błędach, które znaleźliśmy, jest: https://jira.mariadb.org/browse/MDEV-10279 Niestety, jak dotąd nie ma rozwiązania . Odkryliśmy, że ten problem dotyczy MariaDB do 5.5. Zdarzenia inne niż GTID, które pochodzą z MariaDB 10.0, są poprawnie rozliczane w wersji 10.4 jako pochodzące z wątku podrzędnego, a gtid_slave_pos jest prawidłowo aktualizowany. MariaDB 5.5 jest dość stara (mimo że nadal jest obsługiwana), więc nadal możesz zobaczyć działające na niej konfiguracje i próby migracji z wersji 5.5 do nowszych wersji MariaDB z obsługą GTID. Co gorsza, zgodnie z raportem o błędzie, który znaleźliśmy, dotyczy to również replikacji pochodzącej z serwerów innych niż MariaDB (jeden z komentarzy mówi o problemie pojawiającym się na serwerach Percona Server 5.6) do MariaDB.
W każdym razie mamy nadzieję, że ten post na blogu okazał się przydatny i mamy nadzieję, że nie napotkasz problemu, który właśnie opisaliśmy.