SurpresoNão, este não sou eu! O Titio é bem mais bonitinho (boa definição para um feio arrumadinho). Mas, esta cara de bocó abismado bem me veste, ao trazer à luz este assunto. Sinceramente, não sei dizer se seria um byta-bug, ou, um problema de configuração.

Apesar da introdução, como não poderia deixar de ser, o assunto é sério e merece uma revisão da configuração de seu ambiente de replicação.

Ao longo do tempo tenho ouvido algumas reclamações, muito esporádicas e pontuais, sobre problemas na replicação, que por vezes “deixa de replicar”. Contudo, como na maioria das vezes, tratava-se de uma “meia dúzia” de comandos, muitos de manutenção, os quais podiam ser replicados manualmente, jamais, justificou-se um forensic (investigação) mais apurado.

Recentemente, um de meus Padwan, me abordou com um intrigante afirmação: “Foi dado um comando no servidor master, mas, não foi replicado para os slaves”.

Como Assim? Logicamente, voltei com a pergunta mais relevante: “Verifique se o comando de fato foi gravado no Binlog (binary log), e, também no Relay Log”.

Fiquei aterrorizado com a resposta! Sim, o comando (um INSERT) estava lá! Em outras palavras para podermos desenhar melhor o cenário:

– 1 servidor Master e 3 servidores slaves

– Comando INSERT  no Master (aplicado com êxito), registrado no Binlog (condição para existência da replicação)

– Comando recebido pelos SLAVES, devidamente, registrado no Relay Log (contra parte do Binlog)

O mistério: Se o comando foi aplicado no servidor Master corretamente (a linha foi de fato incluída na tabela), registrado no Binlog, replicado para os Relay Logs dos Slaves, por que não foi aplicada a inserção da linha na tabela?

Evidentemente, não havia problema no servidor Master. O MySQL fez o que se esperava: incluiu a linha na tabela, e, registrou-a no Binlog.

Será que o problema estaria nos Slaves? Em todos os 3?? Será que algum Lord Sith havia se apoderado dos incautos servidores MySQL Slaves?

Me intriguei com o fato e resolvi por fim nesta bandalheira de uma vez por todas. Jedi, mas, apaixonado por Darth Vader, resolvi recorrer à Força para solver este mistério. E, muitas surpresas se revelariam!

Preparei um ambiente simples, com duas instâncias de MySQL rodando em uma mesma máquina virtual. Se quiserem reviver esta experiência, subam duas instâncias de MySQL (uma na porta 3306 e outra na porta 3307), configurados com replicação Master-Slave (unidirecional, one-way), apenas atentando para as seguintes configurações, já de acordo com nossa proposta de estudo de caso:

– Configuração adicional para o servidor MySQL Master

binlog_format = STATEMENT

– Configuração adicional para o servidor MySQL Slave

replicate-do-db = bug_da_replicacao_1

Próximo passo, vamos nos conectar ao servidor MySQL Master, através de qualquer cliente SQL de sua preferência, para criarmos o banco de dados bug_da_replicacao_1, que será o alvo de nosso experimento:

CREATE TABLE `tabela_1` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`dt` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
`rnd` int(11) DEFAULT NULL,
UNIQUE KEY `id` (`id`)
) ENGINE=InnoDB

Vamos popular a tabela tabela_1, através do servidor MySQL Master:

  • USE mysql;
  • insert into bug_da_replicacao_1.tabela_1 values (null,now(),rand()*1000000);
  • insert into bug_da_replicacao_1.tabela_1 values (null,now(),rand()*1000000);
  • insert into bug_da_replicacao_1.tabela_1 values (null,now(),rand()*1000000);

A intenção do comando USE é forçar a saída do banco de dados bug_da_replicacao_1, Isto é necessário para fins deste estudo.

Agora, nos conectamos à instância do servidor MySQL Slave para comprovar se os dados foram replicados:

mysql> select * from bug_da_replicacao_1.tabela_1;
Empty set (0.01 sec)

Não foram! Voce poderia examinar o binlog e relay log, pois, tais comandos estarão lá. Esta é a hora que começa a Marcha Imperial (famosa trilha sonora do Star Wars! Rurrrrzz. Pára tudo! Você não conhece isso? Então porque está lendo este artigo? 🙁 ).

Em análise, possíveis pontos de falha:

  • Binlog: no lado do servidor master, está descartada qualquer falha, pois, o comando consta registrado no Binlog
  • IO_Thread e Relay Log: no lado do servidor slave, também descartado, pois a IO_Thread fez o download do comando do Master
  • SQL_Thread: Aha! Aqui está o problema! A SQL_Thread está falhando, não está aplicando o comando SQL, perfeitamente, registrado no Relay Log.

De fato, o problema está no SQL_Thread, mas, por causa de uma conjunção de fatores, quais sejam:

  • O mais impactante, certamente, é o fato da replicação ser SBR, ou seja, Statement Based Replication
  • De igual importância, adicionando-se à SBR (binlog_format = STATEMENT), está o fato de restringirmos o que deve ser replicado no servidor MySQL Slave, através do varíavel de configuração: Replicate_Do_DB = bug_da_replicacao_1 
  • E, finalmente, para completar o imbroglio: confiar que qualificar o banco de dados (banco_de_dados.tabela, ou, no nosso caso: bug_da_replicacao_1.tabela_1) nos entregaria a segurança de que o comando seria interpretado, corretamente, no servidor MySQL Slave.

Agora, proponho mais um teste. No servidor MySQL Master:

  • USE bug_da_replicacao_1;
  • insert into bug_da_replicacao_1.tabela_1 values (null,now(),1234);
  • insert into tabela_1 values (null,now(),4321);

Vamos conferir a replicação no servidor MySQL Slave:

mysql> select * from bug_da_replicacao_1.tabela_1;
+—-+———————+——+
| id | dt | rnd |
+—-+———————+——+
| 4 | 2013-03-17 17:39:45 | 1234 |
| 5 | 2013-03-17 17:39:46 | 4321 |
+—-+———————+——+
2 rows in set (0.00 sec)

“Et voilà”! Ao nos conectar-mos, diretamente, ao banco de dados, qualificando ou não, o comando é, corretamente, replicado!

É por isso que eu insisto em dizer: o problema não está na SBR, não está na qualificação do banco de dados e/ou estar conectado nele, não está na filtragem do banco de dados que deverá ser replicado, através do Replicate_Do_Db, não está na SQL_Thread. Não está, isoladamente, nestes fatores. Mas, sim, o problema reside na conjunção de todos eles.

A replicação SBR reinou, absoluta, nas versões 3 e 4 do MySQL. Robusta e vigorosa. Na versão 5.0 do MySQL, tivemos a inclusão da RBR (Row Based Replication), muito mais precisa, de fato. Na versão 5.1 do MySQL, tivemos a implementação da capacidade de o MySQL decidir qual replicação era mais recomendada do ponto de vista de eficiência e segurança: SBR ou RBR, consegue-se isto através da configuração binlog_format = MIXED. Não existe uma MBR 🙂 Mixed Based Replication :-). O parâmetro Mixed, fará com que o MySQL decida qual o melhor tipo de replicação a ser utilizado.

Voce me perguntaria:

“Titio, nunca mais usar devo SBR?” Não, caro Padwan. Usar deve voce SBR! Mas, quando for imperativo grandes manutenções, como grandes UPDATE’s e DELETE’s (principalmente, estes últimos). RBR é, totalmente, ineficiente para estes casos. Peço que conheça, as limitações e as diferenças entre SBR e RBR, antes de usar SBR. Decidido que é possível, e, recomendável usar SBR, por favor, querido Padwan: Ao banco conectar deve você!

“Titio, muito usar SBR na empresa e problema nunca aparecer. Por quê?”. Simples, futuro Jedi. O problema só foi, evidenciado, por se tratar de uma manutenção, e, não pelo uso da aplicação. Normalmente, as aplicações ao estabelecerem uma conexão com o banco, é exigido pelo conector: Host/IP do servidor, Porta, Usuário, Senha, e, BANCO DE DADOS! Sacou? Normalmente, os desenvolvedores, por vício e costume já fornecem o nome do banco, o que, evita esta pane.

Outrossim, devemos, orientar aos nossos desenvolvedores que sempre forneçam ao objeto de conexão (conector) o nome do banco de dados.

De novo! Não quero que deixem de usar SBR, principalmente, se estivermos falando de grandes manutenções no banco de dados. Mas, certamente, se voce estiver usando MySQL 5.1 em diante, faz muito melhor à saúde do MySQL que se utilize binlog_format = MIXED.

Queridos alunos, ex-alunos e amigos, um excelente domingo chuvoso. Aproveitem tudo o que gostam, independentemente, do que “os outros” dizem.