Nous avons récemment rencontré un cas de support client intéressant impliquant une configuration de réplication MariaDB. Nous avons passé beaucoup de temps à rechercher ce problème et avons pensé qu'il valait la peine de le partager avec vous dans cet article de blog.
Description de l'environnement du client
Le problème était le suivant :un ancien serveur MariaDB (avant 10.x) était en cours d'utilisation et une tentative a été faite pour migrer les données depuis celui-ci vers une configuration de réplication MariaDB plus récente. Cela a entraîné des problèmes d'utilisation de Mariabackup pour reconstruire les esclaves dans le nouveau cluster de réplication. Pour les besoins des tests, nous avons recréé ce comportement dans l'environnement suivant :
Les données ont été migrées de 5.5 à 10.4 à l'aide de mysqldump :
mysqldump --single-transaction --master-data=2 --events --routines sbtest > /root/dump.sql
Cela nous a permis de collecter les coordonnées du journal binaire principal et le vidage cohérent. En conséquence, nous avons pu provisionner le nœud maître MariaDB 10.4 et configurer la réplication entre l'ancien nœud maître 5.5 et le nouveau nœud 10.4. Le trafic fonctionnait toujours sur le nœud 5.5. Le maître 10.4 générait des GTID car il devait répliquer les données sur l'esclave 10.4. Avant d'entrer dans les détails, examinons rapidement le fonctionnement de GTID dans MariaDB.
MariaDB et GTID
Pour commencer, MariaDB utilise un format de GTID différent de celui d'Oracle MySQL. Il se compose de trois chiffres séparés par des tirets :
0 - 1 - 345
First est un domaine de réplication, qui permet de gérer correctement la réplication multi-sources. Ceci n'est pas pertinent pour notre cas car tous les nœuds sont dans le même domaine de réplication. Le deuxième numéro est l'ID de serveur du nœud qui a généré le GTID. Le troisième est le numéro de séquence - il augmente de manière monotone avec chaque événement stocké dans les journaux binaires.
MariaDB utilise plusieurs variables pour stocker les informations sur les GTID exécutés sur un nœud donné. Les plus intéressants pour nous sont :
Gtid_binlog_pos - selon la documentation, cette variable est le GTID du dernier groupe d'événements écrit dans le journal binaire.
Gtid_slave_pos - selon la documentation, cette variable système contient le GTID de la dernière transaction appliquée à la base de données par les threads esclaves du serveur.
Gtid_current_pos - selon la documentation, cette variable système contient le GTID de la dernière transaction appliquée à la base de données. Si le server_id du GTID correspondant dans gtid_binlog_pos est égal au server_id du serveur et que le numéro de séquence est supérieur au GTID correspondant dans gtid_slave_pos, alors le GTID de gtid_binlog_pos sera utilisé. Sinon, le GTID de gtid_slave_pos sera utilisé pour ce domaine.
Donc, pour que ce soit clair, gtid_binlog_pos stocke le GTID du dernier événement exécuté localement. Gtid_slave_pos stocke le GTID de l'événement exécuté par le thread esclave et gtid_current_pos affiche soit la valeur de gtid_binlog_pos, s'il a le numéro de séquence le plus élevé et s'il a le server-id ou gtid_slave_pos s'il a la séquence la plus élevée. Veuillez garder cela à l'esprit.
Un aperçu du problème
L'état initial des variables pertinentes se trouve sur le maître 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)
Veuillez noter gtid_slave_pos qui, théoriquement, n'a pas de sens - il provient du même nœud mais via le thread esclave. Cela peut arriver si vous faites un interrupteur principal avant. C'est exactement ce que nous avons fait - avec deux nœuds 10.4, nous avons fait passer les maîtres de l'hôte avec l'ID de serveur de 1001 à l'hôte avec l'ID de serveur de 1002, puis de nouveau à 1001.
Ensuite, nous avons configuré la réplication de 5.5 à 10.4 et voici à quoi cela ressemblait :
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)
Comme vous pouvez le voir, les événements répliqués à partir de MariaDB 5.5, ils ont tous été pris en compte dans la variable gtid_binlog_pos :tous les événements avec l'ID de serveur de 55. Cela entraîne un problème sérieux. Comme vous vous en souvenez peut-être, gtid_binlog_pos doit contenir des événements exécutés localement sur l'hôte. Ici, il contient des événements répliqués à partir d'un autre serveur avec un ID de serveur différent.
Cela rend les choses délicates lorsque vous souhaitez reconstruire l'esclave 10.4, voici pourquoi. Mariabackup, tout comme Xtrabackup, fonctionne de manière simple. Il copie les fichiers du serveur MariaDB tout en analysant les journaux redo et en stockant toutes les transactions entrantes. Une fois les fichiers copiés, Mariabackup gèlerait la base de données en utilisant soit FLUSH TABLES WITH READ LOCK, soit des verrous de sauvegarde, selon la version de MariaDB et la disponibilité des verrous de sauvegarde. Ensuite, il lit le dernier GTID exécuté et le stocke avec la sauvegarde. Ensuite, le verrou est libéré et la sauvegarde est terminée. Le GTID stocké dans la sauvegarde doit être utilisé comme dernier GTID exécuté sur un nœud. En cas de reconstruction d'esclaves, il sera mis en tant que gtid_slave_pos puis utilisé pour démarrer la réplication du GTID. Ce GTID est tiré de gtid_current_pos, ce qui est parfaitement logique - après tout, c'est le "GTID de la dernière transaction appliquée à la base de données". Le lecteur averti peut déjà voir le problème. Montrons la sortie des variables lorsque 10.4 réplique à partir du maître 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 est défini sur 0-1001-1. Ce n'est certainement pas le bon moment dans le temps, il est tiré de gtid_slave_pos alors que nous avons un tas de transactions qui sont venues de 5.5 après cela. Le problème est que ces transactions sont stockées sous gtid_binlog_pos. D'autre part, gtid_current_pos est calculé de manière à nécessiter un ID de serveur local pour les GTID dans gitd_binlog_pos avant qu'ils ne puissent être utilisés comme gtid_current_pos. Dans notre cas, ils ont l'ID de serveur du nœud 5.5, ils ne seront donc pas traités correctement comme des événements exécutés sur le maître 10.4. Après la restauration de la sauvegarde, si vous définissez l'esclave en fonction de l'état GTID stocké dans la sauvegarde, il finira par réappliquer tous les événements provenant de 5.5. Ceci, évidemment, casserait la réplication.
La solution
Une solution à ce problème consiste à suivre plusieurs étapes supplémentaires :
- Arrêter la réplication de 5.5 à 10.4. Exécutez STOP SLAVE sur le maître 10.4
- Exécuter n'importe quelle transaction sur 10.4 - CREATE SCHEMA IF NOT EXISTS bugfix - cela changera la situation GTID comme ceci :
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)
Le dernier GITD a été exécuté localement, il a donc été stocké sous gtid_binlog_pos. Comme il a l'ID du serveur local, il est choisi comme gtid_current_pos. Maintenant, vous pouvez faire une sauvegarde et l'utiliser pour reconstruire les esclaves à partir du maître 10.4. Une fois cela fait, redémarrez le thread esclave.
MariaDB est au courant de l'existence de ce type de bogue, l'un des rapports de bogue pertinents que nous avons trouvés est : https://jira.mariadb.org/browse/MDEV-10279 Malheureusement, il n'y a pas de solution à ce jour . Ce que nous avons constaté, c'est que ce problème affecte MariaDB jusqu'à 5.5. Les événements non-GTID provenant de MariaDB 10.0 sont correctement comptabilisés sur 10.4 comme provenant du thread esclave et gtid_slave_pos est correctement mis à jour. MariaDB 5.5 est assez ancienne (même si elle est toujours prise en charge), vous pouvez donc toujours voir des configurations s'exécuter dessus et tenter de migrer de 5.5 vers des versions MariaDB plus récentes et compatibles GTID. Pire encore, selon le rapport de bogue que nous avons trouvé, cela affecte également la réplication provenant de serveurs non-MariaDB (l'un des commentaires mentionne un problème apparaissant sur Percona Server 5.6) dans MariaDB.
Quoi qu'il en soit, nous espérons que vous avez trouvé cet article de blog utile et que vous ne rencontrerez pas le problème que nous venons de décrire.