Recentemente ci siamo imbattuti in un interessante caso di assistenza clienti che riguardava una configurazione di replica MariaDB. Abbiamo dedicato molto tempo alla ricerca di questo problema e abbiamo pensato che sarebbe valsa la pena condividerlo con te in questo post del blog.
Descrizione dell'ambiente del cliente
Il problema era il seguente:era in uso un vecchio server MariaDB (precedente alla 10.x) ed è stato effettuato un tentativo di migrare i dati da esso in una configurazione di replica MariaDB più recente. Ciò ha comportato problemi con l'utilizzo di Mariabackup per ricostruire gli slave nel nuovo cluster di replica. Ai fini dei test abbiamo ricreato questo comportamento nel seguente ambiente:
I dati sono stati migrati da 5.5 a 10.4 utilizzando mysqldump:
mysqldump --single-transaction --master-data=2 --events --routines sbtest > /root/dump.sql
Questo ci ha permesso di raccogliere le coordinate del registro binario principale e il dump coerente. Di conseguenza, siamo stati in grado di eseguire il provisioning del nodo master MariaDB 10.4 e impostare la replica tra il vecchio master 5.5 e il nuovo nodo 10.4. Il traffico era ancora in esecuzione sul nodo 5.5. 10.4 master stava generando GTID poiché doveva replicare i dati su 10.4 slave. Prima di approfondire i dettagli, diamo una rapida occhiata al funzionamento di GTID in MariaDB.
MariaDB e GTID
Per cominciare, MariaDB utilizza un formato di GTID diverso da Oracle MySQL. È composto da tre numeri separati da trattini:
0 - 1 - 345
Il primo è un dominio di replica, che consente di gestire correttamente la replica multi-sorgente. Questo non è rilevante nel nostro caso poiché tutti i nodi si trovano nello stesso dominio di replica. Il secondo numero è l'ID del server del nodo che ha generato il GTID. Il terzo è il numero di sequenza:aumenta in modo monotono con ogni evento memorizzato nei log binari.
MariaDB utilizza diverse variabili per memorizzare le informazioni sui GTID eseguiti su un dato nodo. I più interessanti per noi sono:
Gtid_binlog_pos - come da documentazione, questa variabile è il GTID dell'ultimo gruppo di eventi scritto nel log binario.
Gtid_slave_pos - come da documentazione, questa variabile di sistema contiene il GTID dell'ultima transazione applicata al database dai thread slave del server.
Gtid_current_pos - come da documentazione, questa variabile di sistema contiene il GTID dell'ultima transazione applicata al database. Se il server_id del GTID corrispondente in gtid_binlog_pos è uguale al server stesso server_id e il numero di sequenza è maggiore del GTID corrispondente in gtid_slave_pos, verrà utilizzato il GTID di gtid_binlog_pos. In caso contrario, per quel dominio verrà utilizzato il GTID di gtid_slave_pos.
Quindi, per chiarire, gtid_binlog_pos memorizza il GTID dell'ultimo evento eseguito localmente. Gtid_slave_pos memorizza il GTID dell'evento eseguito dal thread slave e gtid_current_pos mostra il valore da gtid_binlog_pos, se ha il numero di sequenza più alto e ha server-id o gtid_slave_pos se ha la sequenza più alta. Tienilo a mente.
Una panoramica del problema
Lo stato iniziale delle variabili rilevanti è su 10.4 master:
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)
Si prega di notare gtid_slave_pos che, in teoria, non ha senso:proveniva dallo stesso nodo ma tramite thread slave. Ciò potrebbe accadere se si effettua prima un interruttore principale. Abbiamo fatto proprio questo:avendo due nodi 10.4 abbiamo cambiato i master da host con ID server 1001 a host con ID server 1002 e poi di nuovo a 1001.
Successivamente abbiamo configurato la replica da 5.5 a 10.4 ed ecco come apparivano le cose:
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)
Come puoi vedere, gli eventi replicati da MariaDB 5.5 sono stati tutti contabilizzati nella variabile gtid_binlog_pos:tutti gli eventi con ID server 55. Ciò si traduce in un problema serio. Come forse ricorderai, gtid_binlog_pos dovrebbe contenere eventi eseguiti localmente sull'host. Qui contiene eventi replicati da un altro server con ID server diverso.
Questo rende le cose rischiose quando vuoi ricostruire lo slave 10.4, ecco perché. Mariabackup, proprio come Xtrabackup, funziona in modo semplice. Copia i file dal server MariaDB durante la scansione dei registri di ripristino e la memorizzazione di tutte le transazioni in entrata. Quando i file sono stati copiati, Mariabackup congela il database utilizzando FLUSH TABLES WITH READ LOCK o blocchi di backup, a seconda della versione di MariaDB e della disponibilità dei blocchi di backup. Quindi legge l'ultimo GTID eseguito e lo memorizza insieme al backup. Quindi il blocco viene rilasciato e il backup è completato. Il GTID memorizzato nel backup deve essere utilizzato come l'ultimo GTID eseguito su un nodo. In caso di ricostruzione degli slave, verrà inserito come gtid_slave_pos e quindi utilizzato per avviare la replica GTID. Questo GTID è preso da gtid_current_pos, il che ha perfettamente senso:dopotutto è il "GTID dell'ultima transazione applicata al database". Il lettore acuto può già vedere il problema. Mostriamo l'output delle variabili quando 10.4 replica dal master 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 è impostato su 0-1001-1. Questo non è sicuramente il momento corretto, è preso da gtid_slave_pos mentre abbiamo un sacco di transazioni che provengono dalla 5.5 successiva. Il problema è che quelle transazioni sono archiviate come gtid_binlog_pos. D'altra parte gtid_current_pos è calcolato in modo da richiedere l'ID del server locale per i GTID in gitd_binlog_pos prima che possano essere usati come gtid_current_pos. Nel nostro caso hanno l'ID server del nodo 5.5 quindi non verranno trattati correttamente come eventi eseguiti sul master 10.4. Dopo il ripristino del backup, se imposti lo slave in base allo stato GTID memorizzato nel backup, finirebbe per riapplicare tutti gli eventi provenienti da 5.5. Questo, ovviamente, interromperebbe la replica.
La soluzione
Una soluzione a questo problema consiste nell'eseguire diversi passaggi aggiuntivi:
- Interrompi la replica da 5.5 a 10.4. Esegui STOP SLAVE su 10.4 master
- Esegui qualsiasi transazione su 10.4 - CREATE SCHEMA IF NOT EXISTS bugfix - questo cambierà la situazione GTID in questo modo:
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)
L'ultimo GITD è stato eseguito localmente, quindi è stato archiviato come gtid_binlog_pos. Poiché ha l'ID del server locale, viene selezionato come gtid_current_pos. Ora puoi fare un backup e usarlo per ricostruire gli slave dal master 10.4. Fatto ciò, riavvia il thread slave.
MariaDB è consapevole dell'esistenza di questo tipo di bug, una delle segnalazioni di bug rilevanti che abbiamo trovato è: https://jira.mariadb.org/browse/MDEV-10279 Sfortunatamente, finora non ci sono soluzioni . Quello che abbiamo scoperto è che questo problema riguarda MariaDB fino a 5.5. Gli eventi non GTID che provengono da MariaDB 10.0 sono correttamente contabilizzati su 10.4 come provenienti dal thread slave e gtid_slave_pos è aggiornato correttamente. MariaDB 5.5 è piuttosto vecchio (anche se è ancora supportato), quindi potresti ancora vedere le impostazioni in esecuzione su di esso e tentare di migrare da 5.5 a versioni più recenti di MariaDB abilitate per GTID. Quel che è peggio, secondo la segnalazione di bug che abbiamo trovato, ciò influisce anche sulla replica proveniente da server non MariaDB (uno dei commenti menziona problemi che si verificano su Percona Server 5.6) in MariaDB.
Comunque, speriamo che tu abbia trovato utile questo post del blog e speriamo che non ti imbatterai nel problema che abbiamo appena descritto.