Recentemente, encontramos um caso interessante de suporte ao cliente envolvendo uma configuração de replicação do MariaDB. Passamos muito tempo pesquisando esse problema e achamos que valeria a pena compartilhar isso com você nesta postagem do blog.
Descrição do ambiente do cliente
O problema era o seguinte:um servidor MariaDB antigo (pré 10.x) estava em uso e foi feita uma tentativa de migrar dados dele para uma configuração de replicação MariaDB mais recente. Isso resultou em problemas com o uso do Mariabackup para reconstruir escravos no novo cluster de replicação. Para fins de testes, recriamos esse comportamento no seguinte ambiente:
Os dados foram migrados de 5.5 para 10.4 usando mysqldump:
mysqldump --single-transaction --master-data=2 --events --routines sbtest > /root/dump.sql
Isso nos permitiu coletar as coordenadas do log binário mestre e o dump consistente. Como resultado, conseguimos provisionar o nó mestre do MariaDB 10.4 e configurar a replicação entre o antigo mestre 5.5 e o novo nó 10.4. O tráfego ainda estava em execução no nó 5.5. O mestre 10.4 estava gerando GTIDs, pois tinha que replicar dados para o escravo 10.4. Antes de nos aprofundarmos nos detalhes, vamos dar uma olhada rápida em como os GTIDs funcionam no MariaDB.
MariaDB e GTID
Para começar, o MariaDB usa um formato de GTID diferente do Oracle MySQL. É composto por três números separados por traços:
0 - 1 - 345
Primeiro é um domínio de replicação, que permite que a replicação de várias fontes seja tratada adequadamente. Isso não é relevante para o nosso caso, pois todos os nós estão no mesmo domínio de replicação. O segundo número é o ID do servidor do nó que gerou o GTID. O terceiro é o número de sequência - ele aumenta monotonicamente com cada evento armazenado nos logs binários.
MariaDB usa diversas variáveis para armazenar as informações sobre os GTIDs executados em um determinado nó. Os mais interessantes para nós são:
Gtid_binlog_pos - conforme a documentação, esta variável é o GTID do último grupo de eventos gravado no log binário.
Gtid_slave_pos - conforme a documentação, esta variável de sistema contém o GTID da última transação aplicada ao banco de dados pelas threads escravas do servidor.
Gtid_current_pos - conforme a documentação, esta variável de sistema contém o GTID da última transação aplicada ao banco de dados. Se o server_id do GTID correspondente em gtid_binlog_pos for igual ao server_id do próprio servidor e o número de sequência for maior que o GTID correspondente em gtid_slave_pos, então o GTID de gtid_binlog_pos será usado. Caso contrário, o GTID de gtid_slave_pos será usado para esse domínio.
Então, para deixar claro, gtid_binlog_pos armazena o GTID do último evento executado localmente. Gtid_slave_pos armazena o GTID do evento executado pela thread escrava e gtid_current_pos mostra o valor de gtid_binlog_pos, se tiver o número de sequência mais alto e tiver server-id ou gtid_slave_pos se tiver a sequência mais alta. Por favor, mantenha isso em sua mente.
Uma visão geral do problema
O estado inicial das variáveis relevantes está no 10.4 master:
MariaDB [(none)]> show global variables like '%gtid%';
+-------------------------+----------+
| Variable_name | Value |
+-------------------------+----------+
| gtid_binlog_pos | 0-1001-1 |
| gtid_binlog_state | 0-1001-1 |
| gtid_cleanup_batch_size | 64 |
| gtid_current_pos | 0-1001-1 |
| gtid_domain_id | 0 |
| gtid_ignore_duplicates | ON |
| gtid_pos_auto_engines | |
| gtid_slave_pos | 0-1001-1 |
| gtid_strict_mode | ON |
| wsrep_gtid_domain_id | 0 |
| wsrep_gtid_mode | OFF |
+-------------------------+----------+
11 rows in set (0.001 sec)
Observe gtid_slave_pos que, teoricamente, não faz sentido - veio do mesmo nó, mas via encadeamento escravo. Isso pode acontecer se você fizer um switch mestre antes. Fizemos exatamente isso - com dois nós 10.4, trocamos os mestres de host com ID de servidor de 1001 para host com ID de servidor de 1002 e depois voltamos para 1001.
Depois configuramos a replicação de 5.5 para 10.4 e ficou assim:
MariaDB [(none)]> show global variables like '%gtid%';
+-------------------------+-------------------------+
| Variable_name | Value |
+-------------------------+-------------------------+
| gtid_binlog_pos | 0-55-117029 |
| gtid_binlog_state | 0-1001-1537,0-55-117029 |
| gtid_cleanup_batch_size | 64 |
| gtid_current_pos | 0-1001-1 |
| gtid_domain_id | 0 |
| gtid_ignore_duplicates | ON |
| gtid_pos_auto_engines | |
| gtid_slave_pos | 0-1001-1 |
| gtid_strict_mode | ON |
| wsrep_gtid_domain_id | 0 |
| wsrep_gtid_mode | OFF |
+-------------------------+-------------------------+
11 rows in set (0.000 sec)
Como você pode ver, os eventos replicados do MariaDB 5.5, todos eles foram contabilizados na variável gtid_binlog_pos:todos os eventos com ID de servidor 55. Isso resulta em um problema sério. Como você deve se lembrar, gtid_binlog_pos deve conter eventos executados localmente no host. Aqui ele contém eventos replicados de outro servidor com ID de servidor diferente.
Isso torna as coisas arriscadas quando você quer reconstruir o escravo 10.4, eis o porquê. O Mariabackup, assim como o Xtrabackup, funciona de forma simples. Ele copia os arquivos do servidor MariaDB enquanto verifica os redo logs e armazena todas as transações recebidas. Quando os arquivos forem copiados, o Mariabackup congelará o banco de dados usando FLUSH TABLES WITH READ LOCK ou bloqueios de backup, dependendo da versão do MariaDB e da disponibilidade dos bloqueios de backup. Em seguida, ele lê o último GTID executado e o armazena junto com o backup. Em seguida, o bloqueio é liberado e o backup é concluído. O GTID armazenado no backup deve ser usado como o último GTID executado em um nó. No caso de reconstrução de escravos, ele será colocado como gtid_slave_pos e então usado para iniciar a replicação do GTID. Este GTID é retirado de gtid_current_pos, o que faz todo o sentido - afinal é o “GTID da última transação aplicada ao banco de dados”. O leitor aguçado já pode ver o problema. Vamos mostrar a saída das variáveis quando 10.4 replica do mestre 5.5:
MariaDB [(none)]> show global variables like '%gtid%';
+-------------------------+-------------------------+
| Variable_name | Value |
+-------------------------+-------------------------+
| gtid_binlog_pos | 0-55-117029 |
| gtid_binlog_state | 0-1001-1537,0-55-117029 |
| gtid_cleanup_batch_size | 64 |
| gtid_current_pos | 0-1001-1 |
| gtid_domain_id | 0 |
| gtid_ignore_duplicates | ON |
| gtid_pos_auto_engines | |
| gtid_slave_pos | 0-1001-1 |
| gtid_strict_mode | ON |
| wsrep_gtid_domain_id | 0 |
| wsrep_gtid_mode | OFF |
+-------------------------+-------------------------+
11 rows in set (0.000 sec)
Gtid_current_pos está definido como 0-1001-1. Este definitivamente não é o momento correto, foi tirado de gtid_slave_pos enquanto temos um monte de transações que vieram de 5.5 depois disso. O problema é que essas transações são armazenadas como gtid_binlog_pos. Por outro lado, gtid_current_pos é calculado de uma forma que requer o ID do servidor local para GTIDs em gitd_binlog_pos antes que eles possam ser usados como gtid_current_pos. No nosso caso, eles têm o ID do servidor do nó 5.5, portanto, não serão tratados adequadamente como eventos executados no mestre 10.4. Após a restauração do backup, se você configurasse o escravo de acordo com o estado do GTID armazenado no backup, ele acabaria reaplicando todos os eventos que vieram do 5.5. Isso, obviamente, quebraria a replicação.
A solução
Uma solução para esse problema é executar várias etapas adicionais:
- Parar a replicação de 5.5 para 10.4. Execute STOP SLAVE no mestre 10.4
- Execute qualquer transação em 10.4 - CREATE SCHEMA IF NOT EXISTS bugfix - isso mudará a situação do GTID assim:
MariaDB [(none)]> show global variables like '%gtid%';
+-------------------------+---------------------------+
| Variable_name | Value |
+-------------------------+---------------------------+
| gtid_binlog_pos | 0-1001-117122 |
| gtid_binlog_state | 0-55-117121,0-1001-117122 |
| gtid_cleanup_batch_size | 64 |
| gtid_current_pos | 0-1001-117122 |
| gtid_domain_id | 0 |
| gtid_ignore_duplicates | ON |
| gtid_pos_auto_engines | |
| gtid_slave_pos | 0-1001-1 |
| gtid_strict_mode | ON |
| wsrep_gtid_domain_id | 0 |
| wsrep_gtid_mode | OFF |
+-------------------------+---------------------------+
11 rows in set (0.001 sec)
O GITD mais recente foi executado localmente, então foi armazenado como gtid_binlog_pos. Como tem o ID do servidor local, é escolhido como gtid_current_pos. Agora, você pode fazer um backup e usá-lo para reconstruir escravos do mestre 10.4. Feito isso, inicie o thread escravo novamente.
MariaDB está ciente de que esse tipo de bug existe, um dos relatórios de bug relevantes que encontramos é: https://jira.mariadb.org/browse/MDEV-10279 Infelizmente, não há correção até agora . O que descobrimos é que esse problema afeta o MariaDB até 5.5. Eventos não-GTID que vêm do MariaDB 10.0 são contabilizados corretamente em 10.4 como provenientes do encadeamento escravo e gtid_slave_pos é atualizado corretamente. O MariaDB 5.5 é bastante antigo (embora ainda seja suportado), então você ainda pode ver configurações em execução nele e tentar migrar de 5.5 para versões mais recentes do MariaDB habilitadas para GTID. O que é pior, de acordo com o relatório de bug que encontramos, isso também afeta a replicação vinda de servidores não-MariaDB (um dos comentários menciona o problema que aparece no Percona Server 5.6) em servidores MariaDB.
De qualquer forma, esperamos que você tenha achado esta postagem do blog útil e esperamos que você não se depare com o problema que acabamos de descrever.