piątek, 23 maja 2014

Naprawianie replikacji master-master w MySQLu

Pamiętacie mój ostatni post na temat podpinania nowego noda do replikacji w MySQL-u? Możecie go przeczytać tutaj, jeśli jeszcze tego nie zrobiliście.

Chciała bym teraz troszkę zmienić scenariusz wydarzeń i zastanowić się co musimy zrobić aby naprawić replikację master-master w MySQLu. Załóżmy, że mamy dwa serwery, których zmiany powinny być wzajemnie replikowane. Niestety zauważyliśmy, że zmiany z II serwera już od jakiegoś czasu nie są zatwierdzane na slavie z powodu błędu. Dodatkowym problemem jest rosnąca liczba plików relay logów na serwerze. Slave nie może ich usunąć bo zmiany w nich nie zostały zatwierdzone ale są cały czas od mastera pobierane nowe zmiany. Musimy zacząć myśleć co z tym zrobić aby się nie okazało, że za jakiś czas zacznie brakować nam miejsca.

Oto przykładowy błąd w tym wypadku:

mysql> show slave status \G
*************************** 1. row ***************************
               Slave_IO_State: Waiting for master to send event
                  Master_Host: 1.1.1.1
                  Master_User: replicate
                  Master_Port: 3306
                Connect_Retry: 60
              Master_Log_File: host-mysql-01-bin.002539
          Read_Master_Log_Pos: 902412914
               Relay_Log_File: host-mysql-01-relay-bin.001829
                Relay_Log_Pos: 461330748
        Relay_Master_Log_File: host-mysql-01-bin.002399
             Slave_IO_Running: Yes
            Slave_SQL_Running: No
              Replicate_Do_DB: 
          Replicate_Ignore_DB: 
           Replicate_Do_Table: 
       Replicate_Ignore_Table: 
      Replicate_Wild_Do_Table: 
  Replicate_Wild_Ignore_Table: 
                   Last_Errno: 1452
                   Last_Error: Could not execute Write_rows event on table test.BATCH_STEP_EXECUTION; Cannot add or update a child row: a foreign key constraint fails (`test`.`BATCH_STEP_EXECUTION`, CONSTRAINT `JOB_EXEC_STEP_FK` FOREIGN KEY (`JOB_EXECUTION_ID`) REFERENCES `BATCH_JOB_EXECUTION` (`JOB_EXECUTION_ID`)), Error_code: 1452; handler error HA_ERR_NO_REFERENCED_ROW; the event's master log host-mysql-01-bin.002399, end_log_pos 508490809
                 Skip_Counter: 0
          Exec_Master_Log_Pos: 508490445
              Relay_Log_Space: 145560879727
              Until_Condition: None
               Until_Log_File: 
                Until_Log_Pos: 0
           Master_SSL_Allowed: No
           Master_SSL_CA_File: 
           Master_SSL_CA_Path: 
              Master_SSL_Cert: 
            Master_SSL_Cipher: 
               Master_SSL_Key: 
        Seconds_Behind_Master: NULL
Master_SSL_Verify_Server_Cert: No
                Last_IO_Errno: 0
                Last_IO_Error: 
               Last_SQL_Errno: 1452
               Last_SQL_Error: Could not execute Write_rows event on table test.BATCH_STEP_EXECUTION; Cannot add or update a child row: a foreign key constraint fails (`test`.`BATCH_STEP_EXECUTION`, CONSTRAINT `JOB_EXEC_STEP_FK` FOREIGN KEY (`JOB_EXECUTION_ID`) REFERENCES `BATCH_JOB_EXECUTION` (`JOB_EXECUTION_ID`)), Error_code: 1452; handler error HA_ERR_NO_REFERENCED_ROW; the event's master log host-mysql-01-bin.002399, end_log_pos 508490809
1 row in set (0.00 sec)

Napisze od razu, że zastosowanie SQL_SLAVE_SKIP_COUNTER=1 (a tu artykuł dlaczego jest to złe) w tym przypadku nie jest rozwiązaniem - według błędu ta zmiana próbuje się odwołać do elementu, który nie istnieje. Co jeśli takich zmian jest cała masa? Chcecie cały czas siedzieć przed komputerem i sprawdzać czy nie trzeba countera pominąć?! Najlepszym rozwiązaniem będzie odtworzenie całej replikacji. Aby to zrobić będziemy początkowo traktować 'zepsuty' slave jako nowy nod (http://db-diary.blogspot.com/2014/05/podpiecie-nowego-noda-do-replikacji-w.html).

No to co robimy:
  1. Przekierowywujemy cały ruch aplikacji na działającego noda - w naszym przypadku jest to II serwer.
  2. Generujemy dump z II serwera:

    mysqldump -uroot -proot --skip-lock-tables --single-transaction --flush-logs --hex-blob --master-data=2 -A > dump.sql


    UWAGA!
    Tutaj musicie założyć, że wszystkie bazy danych mają tabelkę transakcyjne - InnoDB. Jeśli używanie także MyISAM wymagane będzie zatrzymać aplikację, które powodują zamiany na działającym masterze (w naszym przypadku to oczywiście serwer II) lub wymusić lokowanie tabel w czasie robienia dumpa. Jeśli tego nie zrobicie w dumpie mogą pojawić się dane z 'późniejszych' insertów niż pozycja i plik binary logów na to wskazuje.
  3. Zczytujemy pozycję mastera z dumpa

    head dump.sql -n80 | grep "MASTER_LOG_POS"
  4. Kopiujemy dump na I serwer
  5. Stopujemy replikację na oby dwóch serwerach przy pomocy polecenia:

    STOP SLAVE;

    Ponieważ w dumpie mamy wszystkie polecenia do odtworzenia całego klastra, musimy się upewnić, że w czasie importowania jego, żadne zmiany nie przeniosą się na slava.
  6. Resetujemy slava na I serwerze

    RESET SLAVE;

    Przy pomocy tego polecenia wszystkie relay-logi zostaną usunięte i zostanie stworzony całkiem nowy. Informacje o replikacji także zostaną 'zapomniane' - pliki master.info i relay-log.info zostaną usunięte. Zawierają one informację o połączeniu i pozycjach w plikach. Warto je przekopiować na wypadek jak byśmy nie pamiętali nazwy użytkownika i jego hasła lub IP hosta potrzebnego do połączenia w replikacji.
  7. Zaimportować dump

    mysql -uroot -proot < dump.sql
  8. Zczytujemy nazwę binarylogu i pozycję na serwerze I

    SHOW MASTER STATUS;
  9. Ustawiamy slava dla serwerów

    CHANGE MASTER TO MASTER_HOST='HOST',MASTER_USER='replication',MASTER_PASSWORD='replication', MASTER_LOG_FILE='mysql-bin.000002', MASTER_LOG_POS=504122;

    Plik i pozycję dla serwera I zaczytaliśmy z dumpa (punkt 3), a dla serwera II z poprzedniego kroku (punkt 8).
  10. Uruchamiany replikację

    START SLAVE;
  11. Potwierdzamy działanie replikacji

    SHOW SLAVE STATUS \G

Może się zdarzyć, że mimo to replikacja i tak nie działa. Musicie się przyjrzeć dokładnie jakie zmiany mają być przenoszone - może to wynikać ze złego sposobu wykonania dumpa (popatrz na komentarz w punkcie 2). Załóżmy, że miało to miejsce i pojawił się następujący błąd:

mysql> show slave status \G
*************************** 1. row ***************************
               Slave_IO_State: Waiting for master to send event
                  Master_Host: 1.1.1.1
                  Master_User: replicate
                  Master_Port: 3306
                Connect_Retry: 60
              Master_Log_File: host-03-bin.002549
          Read_Master_Log_Pos: 130312481
               Relay_Log_File: host-03-relay-bin.000002
                Relay_Log_Pos: 33108
        Relay_Master_Log_File: host-03-bin.002549
             Slave_IO_Running: Yes
            Slave_SQL_Running: No
              Replicate_Do_DB: 
          Replicate_Ignore_DB: 
           Replicate_Do_Table: 
       Replicate_Ignore_Table: 
      Replicate_Wild_Do_Table: 
  Replicate_Wild_Ignore_Table: 
                   Last_Errno: 1032
                   Last_Error: Could not execute Update_rows event on table test.BATCH_JOB_SEQ; Can't find record in 'BATCH_JOB_SEQ', Error_code: 1032; handler error HA_ERR_END_OF_FILE; the event's master log host-03-bin.002549, end_log_pos 33132
                 Skip_Counter: 0
          Exec_Master_Log_Pos: 32951
              Relay_Log_Space: 130313453
              Until_Condition: None
               Until_Log_File: 
                Until_Log_Pos: 0
           Master_SSL_Allowed: No
           Master_SSL_CA_File: 
           Master_SSL_CA_Path: 
              Master_SSL_Cert: 
            Master_SSL_Cipher: 
               Master_SSL_Key: 
        Seconds_Behind_Master: NULL
Master_SSL_Verify_Server_Cert: No
                Last_IO_Errno: 0
                Last_IO_Error: 
               Last_SQL_Errno: 1032
               Last_SQL_Error: Could not execute Update_rows event on table test.BATCH_JOB_SEQ; Can't find record in 'BATCH_JOB_SEQ', Error_code: 1032; handler error HA_ERR_END_OF_FILE; the event's master log host-03-bin.002549, end_log_pos 33132
1 row in set (0.00 sec)

Istnieje prawdopodobieństwo, że tych złych danych jest tak mało, że możemy ręcznie wyrównać zmiany, aż slave przestanie sypać błędami (wykonanie dumpa trwało na tyle krótko, że tych 'nadmiernych' zmian było mało). Ale jak to zrobić? Posłużymy się narzędziem masterbinlog. Ja postanowiłam przejżeć wszystkie zmiany w relay logach:

mysqlbinlog --start-position=0 --verbose --base64-output=DECODE-ROWS host-03-relay-bin.000002 > /tmp/binary.logs

Przeglądałam plik i szukałam wystąpienia znaków 'end_log_pos 33132'. W ten sposób znalazłam:

#140522  1:12:00 server id 173  end_log_pos 33108     Table_map: `test`.`BATCH_JOB_SEQ` mapped to number 5085
#140522  1:12:00 server id 173  end_log_pos 33132     Update_rows: table id 5085 flags: STMT_END_F
### UPDATE test.BATCH_JOB_SEQ
### WHERE
###   @1=354422
### SET
###   @1=354423
# at 4309887

Sprawdziłam, że ten wiersz dla tego ID w tej tabeli nie istnieje, a właściwie istnieje ale ma inną wartość - dlatego slave nie jest w stanie zatwierdzić zmian. Co teraz zrobić?

  1. Zatrzymujemy slave

    STOP SLAVE;
  2. Wykonujemy taką zmiane na bazie danych aby dane do tego momentu były zgodne do zatwierdzenia przez slave:

    UPDATE test.BATCH_JOB_SEQ SET ID = 354422 WHERE ID = X;
  3. Wznawiamy slava

    START SLAVE;
Może się okazać, że tych zmian jest troszkę. Ale czasie warto przejść przez to kilka razy. Jeśli jednak zmian jest za dużo, warto się zastanowić nad wykonaniem dumpa ponownie z lokowaniem zapisy do tabel.

Brak komentarzy:

Prześlij komentarz