MariaDB introduziu um recurso muito legal chamado Flashback. Flashback é um recurso que permitirá que instâncias, bancos de dados ou tabelas sejam revertidas para um instantâneo antigo. Tradicionalmente, para executar uma recuperação pontual (PITR), deve-se restaurar um banco de dados de um backup e reproduzir os logs binários para avançar o estado do banco de dados em um determinado momento ou posição.
Com o Flashback, o banco de dados pode ser revertido para um ponto do tempo no passado, o que é muito mais rápido se quisermos apenas ver o passado que aconteceu há pouco tempo. Ocasionalmente, o uso de flashback pode ser ineficiente se você quiser ver um instantâneo muito antigo de seus dados em relação à data e hora atuais. Restaurar a partir de um escravo atrasado ou de um backup mais a reprodução do log binário podem ser as melhores opções.
Esse recurso está disponível apenas no pacote cliente MariaDB, mas isso não significa que não podemos usá-lo com nossos servidores MySQL. Esta postagem no blog mostra como podemos usar esse recurso incrível em um servidor MySQL.
Requisitos de Flashback do MariaDB
Para aqueles que desejam usar o recurso de flashback do MariaDB em cima do MySQL, podemos basicamente fazer o seguinte:
- Ative o log binário com a seguinte configuração:
- binlog_format =ROW (padrão desde o MySQL 5.7.7).
- binlog_row_image =FULL (padrão desde o MySQL 5.6).
- Use o utilitário msqlbinlog de qualquer instalação do MariaDB 10.2.4 e posterior.
- Atualmente, o flashback é compatível apenas com instruções DML (INSERT, DELETE, UPDATE). Uma versão futura do MariaDB adicionará suporte para flashback sobre instruções DDL (DROP, TRUNCATE, ALTER, etc.) copiando ou movendo a tabela atual para um banco de dados reservado e oculto e, em seguida, copiando ou voltando ao usar o flashback.
O flashback é obtido aproveitando-se o suporte existente para logs binários de formato de imagem completo, portanto, suporta todos os mecanismos de armazenamento. Observe que os eventos de flashback serão armazenados na memória. Portanto, você deve certificar-se de que seu servidor tenha memória suficiente para esse recurso.
Como funciona o Flashback do MariaDB?
O utilitário mysqlbinlog do MariaDB vem com duas opções extras para este propósito:
- -B, --flashback - O recurso Flashback pode reverter seus dados confirmados para um ponto de tempo especial.
- -T, --table=[name] - Lista entradas apenas para esta tabela (somente log local).
Ao comparar a saída do mysqlbinlog com e sem o sinalizador --flashback, podemos entender facilmente como ele funciona. Considere que a seguinte instrução é executada em um servidor MariaDB:
MariaDB> DELETE FROM sbtest.sbtest1 WHERE id = 1;
Sem o sinalizador de flashback, veremos o evento de log binário DELETE:
$ 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 */
...
Ao estender o comando mysqlbinlog acima com --flashback, podemos ver que o evento DELETE é convertido em um evento INSERT e similarmente às respectivas cláusulas WHERE e SET:
$ 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 */
...
Na replicação baseada em linha (binlog_format=ROW), cada evento de alteração de linha contém duas imagens, uma imagem "antes" (exceto INSERT) cujas colunas são comparadas ao pesquisar a linha a ser atualizada, e uma imagem “depois” (exceto DELETE) contendo as alterações. Com binlog_row_image=FULL, o MariaDB registra linhas completas (ou seja, todas as colunas) para as imagens antes e depois.
O exemplo a seguir mostra eventos de log binários para UPDATE. Considere que a seguinte instrução é executada em um servidor MariaDB:
MariaDB> UPDATE sbtest.sbtest1 SET k = 0 WHERE id = 5;
Ao olhar para o evento binlog para a declaração acima, veremos algo assim:
$ 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
...
Com o sinalizador --flashback, a imagem "antes" é trocada pela imagem "depois" da linha existente:
$ 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 */
...
Podemos então redirecionar a saída do flashback para o cliente MySQL, revertendo assim o banco de dados ou a tabela para o ponto de tempo que desejamos. Mais exemplos são mostrados nas próximas seções.
O MariaDB tem uma página de base de conhecimento dedicada para esse recurso. Confira a página da base de conhecimento do MariaDB Flashback.
MariaDB Flashback com MySQL
Para ter a capacidade de flashback do MySQL, deve-se fazer o seguinte:
- Copie o utilitário mysqlbinlog de qualquer servidor MariaDB (10.2.4 ou posterior).
- Desative o MySQL GTID antes de aplicar o arquivo SQL de flashback. As variáveis globais gtid_mode e enforcement_gtid_consistency podem ser definidas em tempo de execução desde o MySQL 5.7.5.
Suponha que estamos tendo a seguinte topologia de replicação simples do MySQL 8.0:
Neste exemplo, copiamos o utilitário mysqlbinlog do MariaDB 10.4 mais recente em um de nossos escravos 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
O utilitário mysqlbinlog do nosso MariaDB agora está localizado em /root/mysqlbinlog no slave2. No MySQL master, executamos a seguinte declaração desastrosa:
mysql> DELETE FROM sbtest1 WHERE id BETWEEN 5 AND 100;
Query OK, 96 rows affected (0.01 sec)
96 linhas foram excluídas na declaração acima. Aguarde alguns segundos para permitir que os eventos sejam replicados do mestre para todos os escravos antes de tentarmos encontrar a posição do log binário do evento desastroso no servidor escravo. A primeira etapa é recuperar todos os logs binários nesse servidor:
mysql> SHOW BINARY LOGS;
+---------------+-----------+-----------+
| Log_name | File_size | Encrypted |
+---------------+-----------+-----------+
| binlog.000001 | 850 | No |
| binlog.000002 | 18796 | No |
+---------------+-----------+-----------+
Nosso evento desastroso deve existir dentro de binlog.000002, o último log binário neste servidor. Podemos então usar o utilitário mysqlbinlog do MariaDB para recuperar todos os eventos binlog da tabela sbtest1 desde 10 minutos atrás:
(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
Para procurar facilmente o número da posição do log binário, preste atenção nas linhas que começam com "# at ". Nas linhas acima, podemos ver que o evento DELETE estava acontecendo na posição 281 dentro de binlog.000002 (começa em "# em 281"). Também podemos recuperar os eventos binlog diretamente dentro de um servidor 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)
Agora podemos confirmar que a posição 281 é para onde queremos que nossos dados sejam revertidos. Podemos então usar o sinalizador --start-position para gerar eventos de flashback precisos. Observe que omitimos o sinalizador "-vv" e adicione o sinalizador --flashback:
(slave2-mysql8)$ /root/mysqlbinlog \
--start-position=281 \
--database=sbtest \
--table=sbtest1 \
/var/lib/mysql/binlog.000002 \
--flashback > /root/flashback.binlog
O flashback.binlog contém todos os eventos necessários para desfazer todas as mudanças ocorridas na tabela sbtest1 neste servidor MySQL. Como este é um nó escravo de um cluster de replicação, temos que interromper a replicação no escravo escolhido (slave2) para usá-lo para fins de flashback. Para fazer isso, temos que parar a replicação no escravo escolhido, definir MySQL GTID para ON_PERMISSIVE e tornar o escravo gravável:
mysql> STOP SLAVE;
SET GLOBAL gtid_mode = ON_PERMISSIVE;
SET GLOBAL enforce_gtid_consistency = OFF;
SET GLOBAL read_only = OFF;
Neste ponto, slave2 não faz parte da replicação e nossa topologia está assim:
Importar o flashback via cliente mysql e não queremos que essa alteração seja registrado no log binário do MySQL:
(slave2-mysql8)$ mysql -uroot -p --init-command='SET sql_log_bin=0' sbtest < /root/flashback.binlog
Podemos então ver todas as linhas excluídas, conforme comprovado pela seguinte declaração:
mysql> SELECT COUNT(id) FROM sbtest1 WHERE id BETWEEN 5 and 100;
+-----------+
| COUNT(id) |
+-----------+
| 96 |
+-----------+
1 row in set (0.00 sec)
Podemos então criar um arquivo dump SQL para a tabela sbtest1 para nossa referência:
(slave2-mysql8)$ mysqldump -uroot -p --single-transaction sbtest sbtest1 > sbtest1_flashbacked.sql
Depois que a operação de flashback for concluída, podemos reunir novamente o nó escravo na cadeia de replicação. Mas, primeiro, temos que trazer de volta o banco de dados para um estado consistente, reproduzindo todos os eventos a partir da posição em que fizemos o flashback. Não se esqueça de pular o log binário, pois não queremos "escrever" no escravo e nos arriscar com transações errôneas:
(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
Finalmente, prepare o nó de volta para sua função como escravo do MySQL e inicie a replicação:
mysql> SET GLOBAL read_only = ON;
SET GLOBAL enforce_gtid_consistency = ON;
SET GLOBAL gtid_mode = ON;
START SLAVE;
Verifique se o nó escravo está replicando corretamente:
mysql> SHOW SLAVE STATUS\G
...
Slave_IO_Running: Yes
Slave_SQL_Running: Yes
...
Neste ponto, juntamos novamente o escravo de volta à cadeia de replicação e nossa topologia está de volta ao seu estado original:
Agradeça à equipe do MariaDB por apresentar esse recurso incrível!