MariaDB a introduit une fonctionnalité très intéressante appelée Flashback. Le flashback est une fonctionnalité qui permet de restaurer des instances, des bases de données ou des tables vers un ancien instantané. Traditionnellement, pour effectuer une récupération à un moment donné (PITR), il fallait restaurer une base de données à partir d'une sauvegarde et relire les journaux binaires pour faire avancer l'état de la base de données à un certain moment ou à une certaine position.
Avec Flashback, la base de données peut être restaurée à un moment donné dans le passé, ce qui est beaucoup plus rapide si nous voulons simplement voir le passé qui vient de se produire il n'y a pas longtemps. Parfois, l'utilisation du flashback peut être inefficace si vous souhaitez voir un très ancien instantané de vos données par rapport à la date et à l'heure actuelles. La restauration à partir d'un esclave retardé ou à partir d'une sauvegarde et la relecture du journal binaire peuvent être les meilleures options.
Cette fonctionnalité n'est disponible que dans le package client MariaDB, mais cela ne signifie pas que nous ne pouvons pas l'utiliser avec nos serveurs MySQL. Ce billet de blog montre comment nous pouvons utiliser cette fonctionnalité étonnante sur un serveur MySQL.
Exigences MariaDB Flashback
Pour ceux qui souhaitent utiliser la fonctionnalité de retour en arrière de MariaDB en plus de MySQL, nous pouvons essentiellement procéder comme suit :
- Activez le journal binaire avec le paramètre suivant :
- binlog_format =ROW (par défaut depuis MySQL 5.7.7).
- binlog_row_image =FULL (par défaut depuis MySQL 5.6).
- Utilisez l'utilitaire msqlbinlog à partir de n'importe quelle installation de MariaDB 10.2.4 et versions ultérieures.
- Flashback n'est actuellement pris en charge que sur les instructions DML (INSERT, DELETE, UPDATE). Une prochaine version de MariaDB ajoutera la prise en charge du flashback sur les instructions DDL (DROP, TRUNCATE, ALTER, etc.) en copiant ou en déplaçant la table actuelle vers une base de données réservée et masquée, puis en copiant ou en revenant en arrière lors de l'utilisation du flashback.
Le retour en arrière est réalisé en tirant parti de la prise en charge existante des journaux binaires au format image complète, il prend donc en charge tous les moteurs de stockage. Notez que les événements de flashback seront stockés en mémoire. Par conséquent, vous devez vous assurer que votre serveur dispose de suffisamment de mémoire pour cette fonctionnalité.
Comment fonctionne MariaDB Flashback ?
L'utilitaire mysqlbinlog de MariaDB est fourni avec deux options supplémentaires à cet effet :
- -B, --flashback :la fonctionnalité Flashback peut restaurer vos données validées à un moment précis.
- -T, --table=[name] - Liste les entrées pour cette table uniquement (journal local uniquement).
En comparant la sortie de mysqlbinlog avec et sans l'indicateur --flashback, nous pouvons facilement comprendre comment cela fonctionne. Considérez que l'instruction suivante est exécutée sur un serveur MariaDB :
MariaDB> DELETE FROM sbtest.sbtest1 WHERE id = 1;
Sans indicateur de flashback, nous verrons l'événement réel DELETE binlog :
$ mysqlbinlog -vv \
--start-datetime="$(date '+%F %T' -d 'now - 10 minutes')" \
--database=sbtest \
--table=sbtest1 \
/var/lib/mysql/binlog.000003
...
# at 453196541
#200227 12:58:18 server id 37001 end_log_pos 453196766 CRC32 0xdaa248ed Delete_rows: table id 238 flags: STMT_END_F
BINLOG '
6rxXXhOJkAAAQwAAAP06AxsAAO4AAAAAAAEABnNidGVzdAAHc2J0ZXN0MQAEAwP+/gTu4P7wAAEB
AAID/P8AFuAQfA==
6rxXXiCJkAAA4QAAAN47AxsAAO4AAAAAAAEAAgAE/wABAAAAVJ4HAHcAODM4Njg2NDE5MTItMjg3
NzM5NzI4MzctNjA3MzYxMjA0ODYtNzUxNjI2NTk5MDYtMjc1NjM1MjY0OTQtMjAzODE4ODc0MDQt
NDE1NzY0MjIyNDEtOTM0MjY3OTM5NjQtNTY0MDUwNjUxMDItMzM1MTg0MzIzMzA7Njc4NDc5Njcz
NzctNDgwMDA5NjMzMjItNjI2MDQ3ODUzMDEtOTE0MTU0OTE4OTgtOTY5MjY1MjAyOTHtSKLa
'/*!*/;
### DELETE FROM `sbtest`.`sbtest1`
### WHERE
### @1=1 /* INT meta=0 nullable=0 is_null=0 */
### @2=499284 /* INT meta=0 nullable=0 is_null=0 */
### @3='83868641912-28773972837-60736120486-75162659906-27563526494-20381887404-41576422241-93426793964-56405065102-33518432330' /* STRING(480) meta=61152 nullable=0 is_null=0 */
### @4='67847967377-48000963322-62604785301-91415491898-96926520291' /* STRING(240) meta=65264 nullable=0 is_null=0 */
...
En étendant la commande mysqlbinlog ci-dessus avec --flashback, nous pouvons voir que l'événement DELETE est converti en événement INSERT et de manière similaire aux clauses WHERE et SET respectives :
$ mysqlbinlog -vv \
--start-datetime="$(date '+%F %T' -d 'now - 10 minutes')" \
--database=sbtest \
--table=sbtest1 \
/var/lib/mysql/binlog.000003 \
--flashback
...
BINLOG '
6rxXXhOJkAAAQwAAAP06AxsAAO4AAAAAAAEABnNidGVzdAAHc2J0ZXN0MQAEAwP+/gTu4P7wAAEB
AAID/P8AFuAQfA==
6rxXXh6JkAAA4QAAAN47AxsAAO4AAAAAAAEAAgAE/wABAAAAVJ4HAHcAODM4Njg2NDE5MTItMjg3
NzM5NzI4MzctNjA3MzYxMjA0ODYtNzUxNjI2NTk5MDYtMjc1NjM1MjY0OTQtMjAzODE4ODc0MDQt
NDE1NzY0MjIyNDEtOTM0MjY3OTM5NjQtNTY0MDUwNjUxMDItMzM1MTg0MzIzMzA7Njc4NDc5Njcz
NzctNDgwMDA5NjMzMjItNjI2MDQ3ODUzMDEtOTE0MTU0OTE4OTgtOTY5MjY1MjAyOTHtSKLa
'/*!*/;
### INSERT INTO `sbtest`.`sbtest1`
### SET
### @1=1 /* INT meta=0 nullable=0 is_null=0 */
### @2=499284 /* INT meta=0 nullable=0 is_null=0 */
### @3='83868641912-28773972837-60736120486-75162659906-27563526494-20381887404-41576422241-93426793964-56405065102-33518432330' /* STRING(480) meta=61152 nullable=0 is_null=0 */
### @4='67847967377-48000963322-62604785301-91415491898-96926520291' /* STRING(240) meta=65264 nullable=0 is_null=0 */
...
Dans la réplication basée sur les lignes (binlog_format=ROW), chaque événement de changement de ligne contient deux images, une image "avant" (sauf INSERT) dont les colonnes sont comparées lors de la recherche de la ligne à mettre à jour, et une image "après" (sauf DELETE) contenant les modifications. Avec binlog_row_image=FULL, MariaDB enregistre des lignes complètes (c'est-à-dire toutes les colonnes) pour les images avant et après.
L'exemple suivant montre les événements de journal binaire pour UPDATE. Considérez que l'instruction suivante est exécutée sur un serveur MariaDB :
MariaDB> UPDATE sbtest.sbtest1 SET k = 0 WHERE id = 5;
En examinant l'événement binlog pour la déclaration ci-dessus, nous verrons quelque chose comme ceci :
$ mysqlbinlog -vv \
--start-datetime="$(date '+%F %T' -d 'now - 5 minutes')" \
--database=sbtest \
--table=sbtest1 \
/var/lib/mysql/binlog.000001
...
### UPDATE `sbtest`.`sbtest1`
### WHERE
### @1=5 /* INT meta=0 nullable=0 is_null=0 */
### @2=499813 /* INT meta=0 nullable=0 is_null=0 */
### @3='44257470806-17967007152-32809666989-26174672567-29883439075-95767161284-94957565003-35708767253-53935174705-16168070783' /* STRING(480) meta=61152 nullable=0 is_null=0 */
### @4='34551750492-67990399350-81179284955-79299808058-21257255869' /* STRING(240) meta=65264 nullable=0 is_null=0 */
### SET
### @1=5 /* INT meta=0 nullable=0 is_null=0 */
### @2=0 /* INT meta=0 nullable=0 is_null=0 */
### @3='44257470806-17967007152-32809666989-26174672567-29883439075-95767161284-94957565003-35708767253-53935174705-16168070783' /* STRING(480) meta=61152 nullable=0 is_null=0 */
### @4='34551750492-67990399350-81179284955-79299808058-21257255869' /* STRING(240) meta=65264 nullable=0 is_null=0 */
# Number of rows: 1
...
Avec le drapeau --flashback, l'image "avant" est échangée avec l'image "après" de la ligne existante :
$ mysqlbinlog -vv \
--start-datetime="$(date '+%F %T' -d 'now - 5 minutes')" \
--database=sbtest \
--table=sbtest1 \
/var/lib/mysql/binlog.000001 \
--flashback
...
### UPDATE `sbtest`.`sbtest1`
### WHERE
### @1=5 /* INT meta=0 nullable=0 is_null=0 */
### @2=0 /* INT meta=0 nullable=0 is_null=0 */
### @3='44257470806-17967007152-32809666989-26174672567-29883439075-95767161284-94957565003-35708767253-53935174705-16168070783' /* STRING(480) meta=61152 nullable=0 is_null=0 */
### @4='34551750492-67990399350-81179284955-79299808058-21257255869' /* STRING(240) meta=65264 nullable=0 is_null=0 */
### SET
### @1=5 /* INT meta=0 nullable=0 is_null=0 */
### @2=499813 /* INT meta=0 nullable=0 is_null=0 */
### @3='44257470806-17967007152-32809666989-26174672567-29883439075-95767161284-94957565003-35708767253-53935174705-16168070783' /* STRING(480) meta=61152 nullable=0 is_null=0 */
### @4='34551750492-67990399350-81179284955-79299808058-21257255869' /* STRING(240) meta=65264 nullable=0 is_null=0 */
...
Nous pouvons ensuite rediriger la sortie flashback vers le client MySQL, ramenant ainsi la base de données ou la table au moment souhaité. D'autres exemples sont présentés dans les sections suivantes.
MariaDB a une page de base de connaissances dédiée à cette fonctionnalité. Consultez la page de la base de connaissances MariaDB Flashback.
MariaDB Flashback avec MySQL
Pour avoir la capacité de flashback pour MySQL, il faut faire ce qui suit :
- Copiez l'utilitaire mysqlbinlog depuis n'importe quel serveur MariaDB (10.2.4 ou version ultérieure).
- Désactivez MySQL GTID avant d'appliquer le fichier SQL flashback. Les variables globales gtid_mode et apply_gtid_consistency peuvent être définies dans l'environnement d'exécution depuis MySQL 5.7.5.
Supposons que nous ayons la topologie de réplication MySQL 8.0 simple suivante :
Dans cet exemple, nous avons copié l'utilitaire mysqlbinlog du dernier MariaDB 10.4 sur l'un de nos esclaves MySQL 8.0 (slave2) :
(mariadb-server)$ scp /bin/mysqlbinlog [email protected]:/root/
(slave2-mysql8)$ ls -l /root/mysqlbinlog
-rwxr-xr-x. 1 root root 4259504 Feb 27 13:44 /root/mysqlbinlog
L'utilitaire mysqlbinlog de MariaDB est maintenant situé dans /root/mysqlbinlog sur slave2. Sur le maître MySQL, nous avons exécuté l'instruction désastreuse suivante :
mysql> DELETE FROM sbtest1 WHERE id BETWEEN 5 AND 100;
Query OK, 96 rows affected (0.01 sec)
96 lignes ont été supprimées dans la déclaration ci-dessus. Attendez quelques secondes pour laisser les événements se répliquer du maître à tous les esclaves avant de pouvoir essayer de trouver la position binlog de l'événement désastreux sur le serveur esclave. La première étape consiste à récupérer tous les journaux binaires sur ce serveur :
mysql> SHOW BINARY LOGS;
+---------------+-----------+-----------+
| Log_name | File_size | Encrypted |
+---------------+-----------+-----------+
| binlog.000001 | 850 | No |
| binlog.000002 | 18796 | No |
+---------------+-----------+-----------+
Notre événement désastreux devrait exister dans binlog.000002, le dernier journal binaire de ce serveur. Nous pouvons ensuite utiliser l'utilitaire mysqlbinlog de MariaDB pour récupérer tous les événements binlog pour la table sbtest1 depuis 10 minutes :
(slave2-mysql8)$ /root/mysqlbinlog -vv \
--start-datetime="$(date '+%F %T' -d 'now - 10 minutes')" \
--database=sbtest \
--table=sbtest1 \
/var/lib/mysql/binlog.000002
...
# at 195
#200228 15:09:45 server id 37001 end_log_pos 281 CRC32 0x99547474 Ignorable
# Ignorable event type 33 (MySQL Gtid)
# at 281
#200228 15:09:45 server id 37001 end_log_pos 353 CRC32 0x8b12bd3c Query thread_id=19 exec_time=0 error_code=0
SET TIMESTAMP=1582902585/*!*/;
SET @@session.pseudo_thread_id=19/*!*/;
SET @@session.foreign_key_checks=1, @@session.sql_auto_is_null=0, @@session.unique_checks=1, @@session.autocommit=1, @@session.check_constraint_checks=1/*!*/;
SET @@session.sql_mode=524288/*!*/;
SET @@session.auto_increment_increment=1, @@session.auto_increment_offset=1/*!*/;
SET @@session.character_set_client=255,@@session.collation_connection=255,@@session.collation_server=255/*!*/;
SET @@session.lc_time_names=0/*!*/;
SET @@session.collation_database=DEFAULT/*!*/;
BEGIN
/*!*/;
# at 353
#200228 15:09:45 server id 37001 end_log_pos 420 CRC32 0xe0e44a1b Table_map: `sbtest`.`sbtest1` mapped to number 92
# at 420
# at 8625
# at 16830
#200228 15:09:45 server id 37001 end_log_pos 8625 CRC32 0x99b1a8fc Delete_rows: table id 92
#200228 15:09:45 server id 37001 end_log_pos 16830 CRC32 0x89496a07 Delete_rows: table id 92
#200228 15:09:45 server id 37001 end_log_pos 18765 CRC32 0x302413b2 Delete_rows: table id 92 flags: STMT_END_F
Pour rechercher facilement le numéro de position du binlog, faites attention aux lignes qui commencent par "# at". À partir des lignes ci-dessus, nous pouvons voir que l'événement DELETE se produisait à la position 281 dans binlog.000002 (commence à "# à 281"). Nous pouvons également récupérer les événements binlog directement à l'intérieur d'un serveur MySQL :
mysql> SHOW BINLOG EVENTS IN 'binlog.000002';
+---------------+-------+----------------+-----------+-------------+-------------------------------------------------------------------+
| Log_name | Pos | Event_type | Server_id | End_log_pos | Info |
+---------------+-------+----------------+-----------+-------------+-------------------------------------------------------------------+
| binlog.000002 | 4 | Format_desc | 37003 | 124 | Server ver: 8.0.19, Binlog ver: 4 |
| binlog.000002 | 124 | Previous_gtids | 37003 | 195 | 0d98d975-59f8-11ea-bd30-525400261060:1 |
| binlog.000002 | 195 | Gtid | 37001 | 281 | SET @@SESSION.GTID_NEXT= '0d98d975-59f8-11ea-bd30-525400261060:2' |
| binlog.000002 | 281 | Query | 37001 | 353 | BEGIN |
| binlog.000002 | 353 | Table_map | 37001 | 420 | table_id: 92 (sbtest.sbtest1) |
| binlog.000002 | 420 | Delete_rows | 37001 | 8625 | table_id: 92 |
| binlog.000002 | 8625 | Delete_rows | 37001 | 16830 | table_id: 92 |
| binlog.000002 | 16830 | Delete_rows | 37001 | 18765 | table_id: 92 flags: STMT_END_F |
| binlog.000002 | 18765 | Xid | 37001 | 18796 | COMMIT /* xid=171006 */ |
+---------------+-------+----------------+-----------+-------------+-------------------------------------------------------------------+
9 rows in set (0.00 sec)
Nous pouvons maintenant confirmer que la position 281 est l'endroit où nous voulons que nos données reviennent. Nous pouvons ensuite utiliser l'indicateur --start-position pour générer des événements de flashback précis. Notez que nous omettons le drapeau "-vv" et ajoutez le drapeau --flashback :
(slave2-mysql8)$ /root/mysqlbinlog \
--start-position=281 \
--database=sbtest \
--table=sbtest1 \
/var/lib/mysql/binlog.000002 \
--flashback > /root/flashback.binlog
Le flashback.binlog contient tous les événements requis pour annuler toutes les modifications apportées à la table sbtest1 sur ce serveur MySQL. Puisqu'il s'agit d'un nœud esclave d'un cluster de réplication, nous devons interrompre la réplication sur l'esclave choisi (slave2) afin de l'utiliser à des fins de flashback. Pour ce faire, nous devons arrêter la réplication sur l'esclave choisi, définir MySQL GTID sur ON_PERMISSIVE et rendre l'esclave accessible en écriture :
mysql> STOP SLAVE;
SET GLOBAL gtid_mode = ON_PERMISSIVE;
SET GLOBAL enforce_gtid_consistency = OFF;
SET GLOBAL read_only = OFF;
À ce stade, slave2 ne fait pas partie de la réplication et notre topologie ressemble à ceci :
Importez le flashback via le client mysql et nous ne voulons pas que ce changement soit enregistré dans le journal binaire MySQL :
(slave2-mysql8)$ mysql -uroot -p --init-command='SET sql_log_bin=0' sbtest < /root/flashback.binlog
Nous pouvons alors voir toutes les lignes supprimées, comme le prouve l'énoncé suivant :
mysql> SELECT COUNT(id) FROM sbtest1 WHERE id BETWEEN 5 and 100;
+-----------+
| COUNT(id) |
+-----------+
| 96 |
+-----------+
1 row in set (0.00 sec)
Nous pouvons ensuite créer un fichier de vidage SQL pour la table sbtest1 pour notre référence :
(slave2-mysql8)$ mysqldump -uroot -p --single-transaction sbtest sbtest1 > sbtest1_flashbacked.sql
Une fois l'opération de flashback terminée, nous pouvons réintégrer le nœud esclave dans la chaîne de réplication. Mais d'abord, nous devons ramener la base de données dans un état cohérent, en rejouant tous les événements à partir de la position que nous avions flashbackée. N'oubliez pas d'ignorer la journalisation binaire car nous ne voulons pas "écrire" sur l'esclave et nous risquer avec des transactions errantes :
(slave2-mysql8)$ /root/mysqlbinlog \
--start-position=281 \
--database=sbtest \
--table=sbtest1 \
/var/lib/mysql/binlog.000002 | mysql -uroot -p --init-command='SET sql_log_bin=0' sbtest
Enfin, préparez à nouveau le nœud à son rôle d'esclave MySQL et démarrez la réplication :
mysql> SET GLOBAL read_only = ON;
SET GLOBAL enforce_gtid_consistency = ON;
SET GLOBAL gtid_mode = ON;
START SLAVE;
Vérifiez que le nœud esclave se réplique correctement :
mysql> SHOW SLAVE STATUS\G
...
Slave_IO_Running: Yes
Slave_SQL_Running: Yes
...
À ce stade, nous avons réintégré l'esclave dans la chaîne de réplication et notre topologie est maintenant revenue à son état d'origine :
Merci à l'équipe MariaDB d'avoir présenté cette fonctionnalité étonnante !