9 juil. 2012

Réplication MySQL : synchroniser les serveurs en minimisant l’indisponibilité du maître

Pour l'amour d'un dauphin
Je viens de m’apercevoir que je n'avais encore rien posté à propos de MySQL. Je corrige donc ce tort en partageant ici une note sur le processus permettant de répliquer un serveur en minimisant le temps d'innacessibilité du serveur maître. La procédure classique suggère habituellement d’arrêter le serveur maître, faire un backup avec mysqldump, importer le backup sur le (ou les) serveur(s) esclave(s), et enfin relancer le tout avec les paramètres de configuration appropriés.

Si l'on prends mon cas, j'ai un serveur MySQL dédié contenant plus de 300 Go de données et qui se doit d’être accessible (seul les redémarrages pour appliquer divers patchs le week-end sont tolérés). La procédure habituelle rendrait mon serveur indisponible pendant beaucoup trop longtemps pour que ça soit acceptable. Voici comment synchroniser un serveur maître et esclave en ne nécessitant qu'un seul redémarrage du maître.

Etape 1 : Préparer le maître

  • /!\ Faites un backup, je ne suis absolument pas responsable de ce qui pourrait arriver à vos données, donc prenez les mesures appropriées et ayez toujours du recul avant de copier-coller ce que vous trouvez sur Internet sur vos serveurs de production.
  • Ouvrez le fichier de configuration mysql (/etc/my.cnf si vous utilisez RHEL par exemple).
  • Ajoutez les lignes suivantes si elles ne s'y trouvent pas déjà (j'utilise IUS community pour gérer mes packages MySQL, donc les fichiers de configuration sont assez verbeux à la base) :
## Replication
server-id         = 1
log-bin           = /var/lib/mysqllogs/bin-log
expire-logs-days  = 7

Une petite parenthèse pour comprendre ce que vous venez d'ajouter : server-id est un identifiant unique utilisé par le mécanisme de réplication, vos serveurs esclaves devront donc avoir un numéro différent et unique. log-bin indique l'emplacement où seront stockés les fichiers log binaires, listant toutes les commandes effectuées sur le serveur, expire-logs-days servira à spécifier combien de jours ces logs seront stockés, définissant ainsi le lag maximum que vous pourrez vous permettre entre vos serveurs maître et esclaves (vous pouvez en augmenter ou réduire la durée en fonction de l'espace disque disponible).
  • Utilisez le client sur le serveur MySQL maître, et tapez les commandes :
mysql> CREATE USER 'replication'@'slave-server' IDENTIFIED BY 'slavepassword';
mysql> GRANT REPLICATION SLAVE ON *.* TO 'replication'@'slave-server';
mysql> FLUSH PRIVILEGES;

Ainsi vous venez de créer un utilisateur 'replication' avec les privilèges associés. Notez que l'adresse slave-server doit correspondre à l'adresse IP de votre serveur esclave.
  • Redémarrez mysql : /etc/init.d/mysqld restart (sous RHEL). Le redémarrage peut prendre du temps si votre serveur contient beaucoup de données, donc pensez à prévenir vos utilisateurs/développeurs, et toujours envisager le pire, donc ayez un backup récent sous la main.
  • Vérifiez le status de la réplication en tapant la commande suivante :
mysql> SHOW MASTER STATUS;
+------------------+----------+--------------+------------------+
| File             | Position | Binlog_Do_DB | Binlog_Ignore_DB |
+------------------+----------+--------------+------------------+
| mysql-bin.000001 | 50       | test         | manual,mysql     |
+------------------+----------+--------------+------------------+
  • Voila pour le serveur maître, comme promis, à part un redémarrage, l'interruption de service n'aura pas été si longue. (Si vous ne pouvez pas vous permettre un redémarrage parce que votre système est critique, j’espère que vos bases de données sont déjà dupliquées et redondantes, donc ce billet ne vous intéressera probablement pas).

Etape 2 : Préparer l'esclave

  • En partant du postulat que votre serveur esclave possède une installation récente de MySQL (la même version que le maître si possible), il ne devrait pas y avoir de base de données existantes exceptées celles nécessaires au système.
  • Ouvrez le fichier de configuration MySQL (toujours /etc/my.cnf), et ajoutez les lignes suivantes :
## Replication
server-id             = 2
relay-log             = /var/lib/mysqllogs/relay-log
relay-log-space-limit = 16G
expire-logs-days       = 7
  • Utilisez le client MySQL sur le serveur esclave, saisissez :
mysql> CREATE USER 'master'@'master-server' IDENTIFIED BY 'masterpassword';
mysql> GRANT ALL PRIVILEGES ON *.* TO 'master'@'master-server';
mysql> FLUSH PRIVILEGES;

Un nouvel utilisateur 'master' vient d’être crée sur le serveur slave, il servira à la synchronisation initiale entre les deux serveurs. Cet utilisateur bénéficiant de tous les privilèges, il sera plus prudent de le supprimer une fois la synchronisation terminée.
  • Redémarrez le serveur (/etc/init.d/mysqld restart)
  • Entrez la commande suivante dans le client MySQL:
mysql> STOP SLAVE;
mysql> CHANGE MASTER TO
    -> MASTER_HOST='master-server',
    -> MASTER_USER='replication',
    -> MASTER_PASSWORD='slavepassword';
  • Voila pour l'esclave (pour le moment), maintenant passons à la synchronisation.

Etape 3 : Synchronisation

  • Toute l'astuce de cette technique repose sur la commande suivante, sur le serveur maître, exécutez :
mysqldump --single-transaction --all-databases --master-data=1 --host=localhost | mysql --host=slave-server --user=master --password=masterpassword
La synchronisation devrait alors s'effectuer du maitre vers l'esclave. Dans mon cas l'opération prends une douzaine d'heures, donc vous préférerez sans doute basculer la commande en tâche de fond ( ctrl + Z, tapez bg, et appuyez sur entrée).
  • Une fois l'opération terminée (vérifiez le avec un ps -ef | grep mysqldump, ou top), connectez vous avec le client MySQL sur le serveur esclave, et tapez (je commence à être a court de synonymes) :
mysql> START SLAVE;
mysql> SHOW SLAVE STATUS \G
*************************** 1. row ***************************
               Slave_IO_State: Waiting for master to send event
                  Master_Host: 192.168.100.2
                  Master_User: replication
                  Master_Port: 3306
                Connect_Retry: 60
              Master_Log_File: bin-log.000003
          Read_Master_Log_Pos: 283248
               Relay_Log_File: relay-log.000002
                Relay_Log_Pos: 7319
        Relay_Master_Log_File: bin-log.000002
             Slave_IO_Running: Yes
            Slave_SQL_Running: Yes
...
        Seconds_Behind_Master: 2054
Master_SSL_Verify_Server_Cert: No
                Last_IO_Errno: 0
                Last_IO_Error:
               Last_SQL_Errno: 0
               Last_SQL_Error:
  Replicate_Ignore_Server_Ids:
             Master_Server_Id: 1
1 row in set (0.01 sec)

Pour vérifier que la réplication fonctionne correctement, il faut regarder les lignes Slave_IO_Running et Slave_SQL_Running, toutes deux doivent retourner "Yes". Seconds_Behind_Master indique le nombre de secondes qui sépare les requêtes effectuées sur votre serveur maître et celles effectuées sur le serveur esclave (ce nombre devrait fluctuer en fonction de l'activité du maître, mais assurez vous qu'il ne dépasse jamais le nombre de jour spécifié par expire-log-days dans le fichier de configuration my.cnf).

long post is long
Voila, ce post etait un peu long et assez technique, mais il me tenait a cœur de l'écrire, car cette procédure m'a été très utile à de multiples occasions, j’espère qu'elle vous rendra service aussi. N’hésitez pas à vous manifester dans les commentaires si vous constatez une erreur, je m'en voudrais de diriger les gens vers la mauvaise solution.

A noter que l'astuce principale est issue du livre "High performance MYSQL" que je recommande à quiconque veux tirer le meilleur de ce SGBD, personnellement j'en ai toujours un exemplaire sous la main.

2 commentaires:

  1. Merci pour le tuto, il y a une coquille sur la conf du slave
    "expire-log-days = 7" (manque le 's' a logs)

    RépondreSupprimer
  2. De rien, content de voir que cet article sert à d'autres, et merci d'avoir repéré la typo.

    RépondreSupprimer

Merci pour votre participation constructive