Attention, ce post peut contenir un peu de troll et de mauvaise foi.

Si vous avez (au hasard) un serveur d'hébergement pour des applications utilisant le SGBD[1] MySQL, il peut être intéressant de mettre en place un système de réplication. Ça se fait assez facilement :

  • On positionne les paramètres server-id à des valeurs différentes sur le maître et l'esclave ;
  • On donne un chemin au paramètre log_bin sur le maître ;
  • On crée un utilisateur avec les droits REPLICATION SLAVE sur le maître ;
  • On dumpe la ou les bases en prenant soin de récupérer les infos sur la position de la réplication (mysqldump --master-data) ;
  • On injecte les bases sur l'esclave (source mondump.sql) ;
  • On indique les informations de connexion au maître (CHANGE MASTER TO MASTER_HOST='...', MASTER_USER='...', MASTER_PASSWORD='...') ;
  • On passe les coordonnées du binlog à utiliser (qui se trouvent sous forme de commentaire dans le dump réalisé précédemment, sauf si on a precisé --master-data=1) ;
  • On démarre l'esclave.

Là, vous avez un système assez bien foutu, il faut le reconnaître[2], qui va commencer à répliquer les données depuis le maître vers l'esclave. Si vous avez une perte de connexion entre les deux, la reprise se fera automatiquement (à condition d'avoir bien configuré expire_logs_days et max_binlog_size qui indiquent respectivement le nombre de jours au bout duquel le maître détruit une information de réplication et la taille maximale des journaux de réplication).

Bon voila pour l'introduction (qui n'était pas l'objet initial de ce post).

Le problème qui nous intéresse est que pour des raisons de sécurité par exemple, vous ne désirez pas répliquer les utilisateurs de base entre les deux systèmes (oui, la réplication c'est bourrin, les utilisateurs et les privilèges étant stockés dans des tables, vous les répliquez également[3]).

Ceux qui lisent la doc se diront « j'ai qu'à utiliser binlog_ignore_db[4] sur le maître ou replicate_ignore_db sur l'esclave ». En effet ces deux options permettent de dire, soit d'un côté soit de l'autre[5], que l'on souhaite ignorer une certaine base. Dans le cas qui nous intéresse binlog_ignore_db=mysql devrait convenir.

Et effectivement, vous le paramétrez, testez vite fait et passez à autre chose parce que l'administration système c'est pas vraiment un métier donc faut pas que ça prenne trop de temps et que Jean-Kevin a encore saturé le serveur Samba avec l'intégrale de Clara Morgan.

Le temps passe et fatalement, ce qui devait arriver arrive, le serveur maître se crashe. POUF ! Plus de disque ou une pelleteuse est passé sur la fibre et le client peut pas se permettre plus de 2h d'indispo, il faut faire quelque chose. Vous commencez donc le processus de bascule (reconfiguration DNS ou proxy ou ce que vous voulez) et commencez à faire chauffer le serveur secondaire. Vous lancez donc tout ce qu'il faut (apache et consorts) pour que les clients puissent avoir leur beau site et, surtout leurs données. Tout va bien la bascule se déroule comme prévu c'est génial, il est 12h c'est l'heure du casse-croûte. Toutefois, au retour de votre pause déjeuner, vous constatez que la messagerie téléphonique est saturée de messages de clients qui râlent parce que leur base prospects qui contenait 10 000 enregistrements saisis avec amour ne contient plus que 500 pékins et aucun de ceux saisis depuis 6 mois. Vous bredouillez donc des excuses et proposez de restaurer un backup[6] non sans vous faire insulter parce que vous aviez assuré que ce genre de choses ne pouvait pas[7] arriver.

Une fois les différents clients gérés et votre petit doigt gauche amputé par un commercial qui a du promettre de refaire 12 sites à l'œil et des réductions sur l'hébergement pour faire passer la pilule, vous décidez d'enquêter pour découvrir ce qui s'est passé. Vous faites donc un SHOW SLAVE STATUS\G sur l'esclave[8]. En réalité, vous auriez du le faire dès la bascule, ça vous aurait permis de voir que tout n'allait pas bien et de réagir plus vite[9], mais l'article aurait été beaucoup plus court.

Le show slave status vous indique donc une erreur //via// ses champs Last_Errno et Last_Error concernant un UPDATE/INSERT/DELETE sur une table qui soit-disant n'existerait pas. Après vérification la table n'existe effectivement pas. Mais alors comment est-ce possible, la réplication était pourtant quelque chose de merveilleux qui semblait fonctionner toute seule. Et bien oui mais non. Si on lit la doc plus en profondeur, on n'y trouve rien de concret à part une légère piste.

En effet, la documentation indique que replicate-ignore-db (et son équivalent binlog-ignore-db) ignore les instructions pour lesquelles la base de données courante est celle passée en paramètre. Et d'illustrer avec un exemple (c'est pas celui de la doc, je sais adapter les choses) :

USE mabaseignore;
CREATE TABLE mabaseimportante.matableimportanteaussi(id integer not null auto_increment, age integer, sexe enum('F', 'M', 'N/A') DEFAULT NULL, ville VARCHAR(50) DEFAULT NULL, PRIMARY KEY PK_matableimportanteaussi_id id);

Dans cet exemple l'instruction n'est pas répliquée parce que (binlog/replicate)_ignore_db se base sur la base courante (celle sélectionnée avec USE ou celle définie comme base courante par programmation) pour savoir si il réplique ou pas (c'est plus rapide). La contrepartie est qu'en cas d'instruction utilisant la notation nombase.nomtable à partir d'une base non répliquée, celle-ci n'est pas répliquée du tout (ce n'est pas seulement limité aux CREATE TABLE).

Bon, comme je l'ai dit, cela ne solutionne pas notre problème mais cela semble être une piste. En effet, dans notre cas, on ignore la base mysql, donc toute instruction exécutée suite à un USE mysql n'est pas répliquée. C'est bien gentil, mais vos utilisateurs n'ont pas pour habitude d'utiliser la base de données mysql pour faire leurs modifications (d'ailleurs, ils n'ont pas le droit de s'y connecter, n'est-ce pas, vous ne leur avez donné que les privilèges nécessaires, hein ?). Et bien il semblerait qu'en fait (replicate/binlog)_ignore_db ait un comportement particulier lorsqu'aucune base n'est sélectionnée et ne réplique pas le statement[10]. La solution est d'utiliser la variante des ignore-db, qui n'est disponible que pour les esclaves, il s'agit de replicate-wild-ignore-table qui est légèrement plus lent (mais on s'en fiche un peu, c'est géré par l'esclave qui n'a pas de contraintes de charge importantes théoriquement) mais permet la réplication des instructions avec les noms de table qualifiés.

On configurera donc un replicate-wild-ignore-table=mysql.% (le wild est là pour wildcard, la correspondance est faite comme pour un LIKE, le % remplace une chaine, le _ un caractère) ce qui ignorera toute commande portant sur la base MySQL se basera autant sur la base courante que sur les bases mentionnées dans la requête pour faire son filtrage.

Notes

[1] Ça fait mal aux doigts d'appeler ça comme ça...

[2] Enfin, s'il était si bien foutu, ce post n'aurait pas lieu d'être

[3] Comment ça ça fait 10 minutes que vous cherchez à comprendre pourquoi vous ne pouvez plus vous connecter à l'esclave ?

[4] Attention, les - des options deviennent des _ dans le fichier de conf

[5] La possibilité de configurer des éléments à ignorer ou à sélectionner (replicate_do_db) uniquement sur l'esclave permet de répliquer différentes bases sur différentes machines pour des raisons de volume ou d'emplacement

[6] Vous en faites ? hein, vous en faites ?

[7] Il ne faut jamais dire ce genre de choses

[8] Le \G permet d'afficher le résultat formaté d'une façon lisible

[9] Qui plus est c'est pas mal de le faire lorsqu'on arrête l'esclave, ce qui est conseillé lors du passage sur le serveur de secours

[10] J'aurais compris avec un binlog/replicate_do_db mais avec le ignore, j'avoue que la logique m'échappe. Notez que je n'ai pas eu le temps de tester si c'était un cas spécifique lorsque l'on met mysql dans l'ignore ou si cela se produit quelle que soit la base citée.