MySQL 8, clônage d’instances pour la réplication avec la commande CLONE

Logo

Introduction

Le nombre d’étapes manuelles pour configurer une réplication est élevé (réplication basée sur la position dans le log binaire ou GTID), en particulier lorsque l’instantané des bases de données du serveur source est effectué.

À partir de MySQL 8.0.17, le nouveau plugin CLONE simplifie le provisionnement des replica.

Dans cet article, une instance MySQL est clonée pour de la réplication à l’aide de ce plugin.

MySQL 8, CLONE plugin

La terminologie suivante est adoptée lors de l’utilisation du plugin CLONE :

  • Le serveur source est appelé le serveur donateur (donor).
  • Le serveur cible est appelé le serveur destinataire (recipient).
L’opération CLONE n’altère pas les fichiers de configuration des serveurs MySQL donateur et destinataire, ni leurs paramètres de démarrage.

Quelques limitations :

  • Les instructions DDL (CREATE DROP ALTER) et TRUNCATE ne sont pas permises durant un clônage, seules les intructions DML concurrentes sont autorisées. Les commandes DDL sont bloquées dans le serveur donateur lorsqu’un clônage est en cours.
  • Les versions MySQL doivent être exactement les mêmes, jusqu’aux numéros de versions mineures.
  • Les fichiers de logs binaires (Binary logs) ne sont pas clônées.
  • Seules les tables InnoDB sont clônées. Les tables MyISAM ou CSV sont clônées mais uniquement les structures, pas les données.

La progression de l’opération CLONE peut être surveillée.

Explorons cette nouvelle fonctionnalité : étapes, mécanisme interne… Un clônage distant est réalisé dans cet article.

Installation du plugin CLONE

Sur les deux serveurs (donateur et destinataire), le plugin CLONE est installé : au démarrage du serveur ou à l’exécution

Au démarrage du serveur :

$CFG/vpsfrsqlpac(1|2).cfg
[mysqld]
plugin-load-add=mysql_clone.so
clone=FORCE_PLUS_PERMANENT

En cours d’exécution (runtime) :

(vpsfrsqlpac1|2) mysql> INSTALL PLUGIN CLONE SONAME 'mysql_clone.so';

S’il y a un problème lors de l’installation, vérifier la variable plugin_dir :

(vpsfrsqlpac1|2) mysql> SHOW VARIABLES LIKE 'plugin_dir';
+---------------+----------------------------------+
| Variable_name | Value                            |
+---------------+----------------------------------+
| plugin_dir    | /opt/mysql/mysql-8.0/lib/plugin/ |
+---------------+----------------------------------+

Vérifier l’installation :

(vpsfrsqlpac1|2) mysql> SELECT plugin_name, plugin_status
                           FROM information_schema.plugins
                           WHERE plugin_name='clone';
+-------------+---------------+
| plugin_name | plugin_status |
+-------------+---------------+
| clone       | ACTIVE        |
+-------------+---------------+

Vérification des prérequis (optionnel)

Vérifier certains prérequis si une personnalisation a été configurée (innodb_data_file_path, localisation et nom des fichiers undo…). Si il n’y a pas de personnalisation, sauter cette étape.

Vérifier les variables innodb_data_file_path et innodb_page_size, elles doivent être identiques :

(vpsfrsqlpac1|2) mysql> SHOW VARIABLES LIKE 'innodb_data_file_path';
(vpsfrsqlpac1|2) mysql> SHOW VARIABLES LIKE 'innodb_page_size';
+-----------------------+------------------------+
| Variable_name         | Value                  |
+-----------------------+------------------------+
| innodb_data_file_path | ibdata1:12M:autoextend |
| innodb_page_size      | 16384                  |
+-----------------------+------------------------+

Les noms des fichiers Undo dans le donateur doivent être uniques car ils vont être regroupés et clônés vers le destinataire dans le répertoire innodb_undo_directory.

(vpsfrsqlpac1) mysql> SELECT tablespace_name, file_name FROM information_schema.files
                           WHERE file_type LIKE 'UNDO LOG';
+-----------------+------------+
| TABLESPACE_NAME | FILE_NAME  |
+-----------------+------------+
| innodb_undo_001 | ./undo_001 |
| innodb_undo_002 | ./undo_002 |
+-----------------+------------+

Le paramètre max_allowed_packet doit être supérieur à 2MB dans les serveurs donateur et destinataire :

(vpsfrsqlpac1|2) mysql> SHOW VARIABLES LIKE 'max_allowed_packet';
+--------------------+----------+
| Variable_name      | Value    |
+--------------------+----------+
| max_allowed_packet | 67108864 |
+--------------------+----------+

Préparation du serveur donateur

Un user avec le privilège BACKUP_ADMIN est créé dans le serveur donateur :

(vpsfrsqlpac1) mysql> CREATE USER clone_user@'%' IDENTIFIED BY '***************';
(vpsfrsqlpac1) mysql> GRANT BACKUP_ADMIN on *.* TO clone_user@'%';
          
(vpsfrsqlpac1) mysql> SHOW GRANTS FOR clone_user@'%';
+-----------------------------------------------+
| Grants for clone_user@%                       |
+-----------------------------------------------+
| GRANT USAGE ON *.* TO `clone_user`@`%`        |
| GRANT BACKUP_ADMIN ON *.* TO `clone_user`@`%` |
+-----------------------------------------------+

Préparation du serveur donateur

Garder à l’esprit que la base mysql est également clônée : les propriétés et le mot de passe du compte root seront ceux du serveur donateur. Aussi, sauvegarder quelques informations avant le clônage si nécessaire :

  • Users, passwords, plugin d’authentification (mysql.user).
  • Grants.
  • Serveurs distants (mysql.servers) …

Si l’opération de clônage n’est pas réalisée avec le compte root, le compte utilisé pour le clônage doit avoir le privilège CLONE_ADMIN :

(vpsfrsqlpac2) mysql> CREATE USER clone_user@'%' IDENTIFIED BY '***************';
(vpsfrsqlpac2) mysql> GRANT CLONE_ADMIN on *.* TO clone_user@'%';
          
(vpsfrsqlpac2) mysql> SHOW GRANTS FOR clone_user@'%';
+----------------------------------------------+
| Grants for clone_user@%                      |
+----------------------------------------------+
| GRANT USAGE ON *.* TO `clone_user`@`%`       |
| GRANT CLONE_ADMIN ON *.* TO `clone_user`@`%` |
+----------------------------------------------+

Le privilège CLONE_ADMIN comprend les privilèges BACKUP_ADMIN et SHUTDOWN.

L’adresse du serveur donateur est ajoutée dans la variable clone_valid_donor_list, il s’agit d’une couche de sécurité :

(vpsfrsqlpac2) mysql> SET GLOBAL clone_valid_donor_list = 'vpsfrsqlpac1:40008';

Exécution de la commande CLONE

La commande CLONE est exécutée depuis le serveur destinataire :

(vpsfrsqlpac2) mysql> CLONE INSTANCE FROM clone_user@vpsfrsqlpac1:40008 IDENTIFIED BY '**********'

Ajouter l’option DATA DIRECTORY = '<dir>' pour clôner une instance dans un autre répertoire sans supprimer les données existantes du serveur destinataire. Avec cette option, l’opération de clônage ne redémarre pas le serveur destinataire pour basculer vers ce répertoire.

Vérification de la progression

La progression du clônage est disponible dans la table performance_schema.clone_status :

SELECT state as "State", 
       CAST(BEGIN_TIME AS DATETIME) as "Start time",
       CASE WHEN end_time IS NULL THEN
         LPAD(sys.format_time(POWER(10,12) * (UNIX_TIMESTAMP(now()) - UNIX_TIMESTAMP(begin_time))), 10, ' ')
       ELSE
         LPAD(sys.format_time(POWER(10,12) * (UNIX_TIMESTAMP(end_time) - UNIX_TIMESTAMP(begin_time))), 10, ' ')
       END as "Duration"
FROM performance_schema.clone_status;
+-------------+---------------------+------------+
| State       | Start time          | Duration   |
+-------------+---------------------+------------+
| In Progress | 2021-01-20 01:34:29 |     1.22 m |
+-------------+---------------------+------------+

Les détails de la progression du clônage peuvent être surveillés en interrogeant la table performance_schema.clone_progress :

SELECT stage as "Stage", 
       state as "State",
       
       CAST(begin_time AS TIME) as "Start time",
       
       CASE WHEN end_time IS NULL THEN
          LPAD(sys.format_time(POWER(10,12) * (UNIX_TIMESTAMP(now()) - UNIX_TIMESTAMP(begin_time))), 10, ' ')
       ELSE
          LPAD(sys.format_time(POWER(10,12) * (UNIX_TIMESTAMP(end_time) - UNIX_TIMESTAMP(begin_time))), 10, ' ')
       END as "Duration",
       
       LPAD(CONCAT(ROUND(estimate/1024/1024,0), " MB"), 16, ' ') as "Estimate",
       
       CASE WHEN begin_time IS NULL THEN LPAD('0%', 7, ' ')
            WHEN estimate > 0 THEN
               LPAD(CONCAT(CAST(ROUND(data*100/estimate, 0) AS CHAR), "%"), 7, ' ')
            WHEN end_time IS NULL THEN LPAD('0%', 7, ' ')
            ELSE LPAD('100%', 7, ' ')
       END as "Done(%)"

  FROM performance_schema.clone_progress;
+-----------+-------------+------------+------------+------------------+---------+
| Stage     | State       | Start time | Duration   | Estimate         | Done(%) |
+-----------+-------------+------------+------------+------------------+---------+
| DROP DATA | Completed   | 09:59:21   |  845.87 ms |             0 MB |    100% |
| FILE COPY | In Progress | 09:59:21   |     1.98 m |          3254 MB |     42% |
| PAGE COPY | Not Started | NULL       | NULL       |             0 MB |      0% |
| REDO COPY | Not Started | NULL       | NULL       |             0 MB |      0% |
| FILE SYNC | Not Started | NULL       | NULL       |             0 MB |      0% |
| RESTART   | Not Started | NULL       | NULL       |             0 MB |      0% |
| RECOVERY  | Not Started | NULL       | NULL       |             0 MB |      0% |
+-----------+-------------+------------+------------+------------------+---------+

+-----------+-----------+------------+------------+------------------+---------+
| Stage     | State     | Start time | Duration   | Estimate         | Done(%) |
+-----------+-----------+------------+------------+------------------+---------+
| DROP DATA | Completed | 09:59:21   |  845.87 ms |             0 MB |    100% |
| FILE COPY | Completed | 09:59:21   |     4.65 m |          3254 MB |    100% |
| PAGE COPY | Completed | 10:04:01   |  301.54 ms |             0 MB |    100% |
| REDO COPY | Completed | 10:04:01   |  300.47 ms |             0 MB |    100% |
| FILE SYNC | Completed | 10:04:01   |  477.28 ms |             0 MB |    100% |
| RESTART   | Completed | 10:04:02   |     3.06 s |             0 MB |    100% |
| RECOVERY  | Completed | 10:04:05   |  470.53 ms |             0 MB |    100% |
+-----------+-----------+------------+------------+------------------+---------+

Le statut final (succès / échec) est stocké dans la table performance_schema.clone_status :

(vpsfrsqlpac2) mysql> SELECT state,
                                begin_time, end_time,
                                source,
                                error_no, error_message
                                FROM performance_schema.clone_status
+-----------+-------------------------+-------------------------+--------------------+----------+---------------+
| state     | begin_time              | end_time                | source             | error_no | error_message |
+-----------+-------------------------+-------------------------+--------------------+----------+---------------+
| Completed | 2021-01-20 09:59:21.126 | 2021-01-20 10:04:06.204 | vpsfrsqlpac1:40008 |        0 |               |
+-----------+-------------------------+-------------------------+--------------------+----------+---------------+

Gestion des erreurs

Un verrou de sauvegarde (backup lock) est pris pour bloquer les opérations DDL dans le serveur donateur et le timeout pour acquérir ce verrou est contrôlé par le paramètre clone_ddl_timeout : les opérations DDL sont susceptibles de générer une erreur si ce timeout est atteint.

Si une erreur se produit, l’opération de clônage est annulée (roll back) et toutes les données clônées sont supprimées. À ce stade, les données utilisateur ont été détruites dans le serveur destinataire (bases de données…), mais pas dans la base de données mysql, le serveur destinataire est toujours dans un état cohérent pour exécuter des commandes d’administration. L’échange de la base mysql par son clône est effectué juste avant l’étape finale de redémarrage, le clônage peut être réexécuté sans étapes supplémentaires une fois que la cause de l’erreur a été identifiée et corrigée.

+--------+-------------------------+-------------------------+--------------------+----------+---------------------------+
| state  | begin_time              | end_time                | source             | error_no | error_message             |
+--------+-------------------------+-------------------------+--------------------+----------+---------------------------+
| Failed | 2021-01-20 12:20:50.535 | 2021-01-20 12:23:26.844 | vpsfrsqlpac1:40008 |     3862 | Clone Donor Error: 1317 : |
|        |                         |                         |                    |          | Query execution was       |
|        |                         |                         |                    |          | interrupted.              |
+--------+-------------------------+-------------------------+--------------------+----------+---------------------------+
(vpsfrsqlpac2) mysql> CLONE INSTANCE FROM clone_user@vpsfrsqlpac1:40008 IDENTIFIED BY '**********';

L’étape en erreur est extraite en interrogeant la table performance_schema.clone_progress :

+-----------+-------------+------------+------------+------------------+---------+
| Stage     | State       | Start time | Duration   | Estimate         | Done(%) |
+-----------+-------------+------------+------------+------------------+---------+
| DROP DATA | Completed   | 02:20:51   |  917.33 ms |             0 MB |    100% |
| FILE COPY | Failed      | 02:20:52   |     2.59 m |          3254 MB |     55% |
| PAGE COPY | Not Started | NULL       | NULL       |             0 MB |      0% |
| REDO COPY | Not Started | NULL       | NULL       |             0 MB |      0% |

Pour les tables non InnoDB (MyISAM, CSV), un simple avertissement est consigné dans le fichier de log du serveur destinataire:

2021-01-20T09:52:23.533528Z 0 [Warning] [MY-013461] [InnoDB] Non innodb table: sqlpac.article_en_details is not cloned and is empty.

Étapes du clônage

Les bases de données utiliseur sont supprimées pour être remplacées mais le process n’écrase pas les fichiers systèmes (tablespace système mysql, undo, tablespace par défaut innodb, buffer pool…), il créé des fichiers .#clone pour ceux-ci :

-rw-r----- 1 mysql dba    68852 Jan 20 09:38  ib_buffer_pool
-rw-r----- 1 mysql dba    86048 Jan 20 09:38  ib_buffer_pool.#clone
-rw-r----- 1 mysql dba 79691776 Jan 20 09:38  ibdata1
-rw-r----- 1 mysql dba 79691776 Jan 20 09:38  ibdata1.#clone
-rw-r----- 1 mysql dba 26214400 Jan 20 09:38  mysql.ibd
-rw-r----- 1 mysql dba 26214400 Jan 20 09:38  mysql.ibd.#clone
-rw-r----- 1 mysql dba 22020096 Jan 20 09:38  undo_001
-rw-r----- 1 mysql dba        0 Jan 20 09:38  undo_001.#clone
-rw-r----- 1 mysql dba 22020096 Jan 20 09:38  undo_002
-rw-r----- 1 mysql dba        0 Jan 20 09:38  undo_002.#clone
drwxr-x--- 2 mysql dba     4096 Jan 20 09:38 '#clone'

Un répertoire #clone est créé, répertoire qui contient des fichiers temporaires ou persistents pour la mécanique interne du clônage :

  • #new_files
  • #old_files
  • #replace_files
  • #status_error
  • #status_files
  • #status_in_progress
  • #status_recovery
  • #view_progress
  • #view_status

Les 2 derniers fichiers #view_progress et #view_status sont persistents et utilisés pour alimenter respectivement les tables performance_schema.clone_progress et performance_schema.clone_status.

Lorsque le processus de clônage terminé arrive à son terme, il échange les fichiers .#clone et si l’option DATA DIRECTORY n’est pas utilisée, il redémarre le serveur destinataire avec la nouvelle commande SQL MySQL 8.0 RESTART.

Activation de la réplication

Lors du clônage, le fichier binlog + la position et la valeur GTID si elle existe sont extraits du serveur donateur et transférés dans la table performance_schema.clone_status du serveur destinataire.

(vpsfrsqlpac2) mysql> SELECT * FROM performance_schema.clone_status \G;
             ID: 1
            PID: 0
          STATE: Completed
     BEGIN_TIME: 2021-01-19 09:32:17.855
       END_TIME: 2021-01-19 09:37:02.582
         SOURCE: vpsfrsqlpac1:40008
    DESTINATION: LOCAL INSTANCE
       ERROR_NO: 0
  ERROR_MESSAGE:
    BINLOG_FILE: binlog-srvmysqlpac.000010
BINLOG_POSITION: 184592856
  GTID_EXECUTED:

C’est fini ! La réplication peut être démarrée en utilisant ces valeurs (binary log file + position dans ce cas pratique) :

MySQL >= 8.0.23
(vpsfrsqlpac2) mysql>
                
CHANGE MASTER
   TO MASTER_HOST='vpsfrsqlpac1',
      MASTER_PORT=40008,
      MASTER_USER='standby_user',
      MASTER_PASSWORD='*************',
      MASTER_LOG_FILE='binlog-srvmysqlpac.000010',
      MASTER_LOG_POS=184592856,
      GET_MASTER_PUBLIC_KEY=1;

START REPLICA;
(vpsfrsqlpac2) mysql>
                    
CHANGE REPLICATION SOURCE
   TO SOURCE_HOST='vpsfrsqlpac1',
      SOURCE_PORT=40008,
      SOURCE_USER='standby_user',
      SOURCE_PASSWORD='*************',
      SOURCE_LOG_FILE='binlog-srvmysqlpac.000010',
      SOURCE_LOG_POS=184592856,
      GET_SOURCE_PUBLIC_KEY=1;
                    
START REPLICA;

Pas besoin de redéfinir les filtres de réplication : ils sont spécifiés dans les fichiers de configuration ou en paramètres de démarrage des serveurs MySQL, ils ne sont pas modifiés et affectés par les commandes CLONE / RESTART.

Conclusion

Le nouveau plugin CLONE est puissant pour provisionner des réplica. 2 inconvénients majeurs :

  • Les instructions DDL et TRUNCATE sont verrouillées lors du clônage d’un serveur source : certaines applications utilisent intensivement ces commandes (tables temporaires…).
  • Les versions MySQL doivent être rigoureusement les mêmes, en raison du dictionnaire MySQL clôné avec la base mysql.