Replicação e failover automático no MariaDB com MaxScale: alta disponibilidade sem gambiarra

Quando o primário cai às 3h da manhã, quem decide o que fazer deveria ser o sistema, e não o DBA de plantão acordado pelo telefone.
Resumo para quem tem pressa: a arquitetura de alta disponibilidade mais comum com MariaDB é um primário e uma ou mais réplicas, ligadas por replicação com GTID. Na frente delas fica o MaxScale, o proxy inteligente da MariaDB, que faz três coisas: manda as escritas para o primário e as leituras para as réplicas, detecta quando o primário cai e promove uma réplica sozinho, e mantém um endpoint estável para a aplicação; a recuperação depende das conexões e dos timeouts. Somando a replicação semissíncrona, que já vem no servidor, você reduz muito o risco de perder transações na troca. Neste artigo eu monto tudo isso passo a passo.
O problema que estamos resolvendo
Sem alta disponibilidade, a queda do servidor de banco costuma seguir um roteiro triste:
- O monitoramento dispara e alguém acorda.
- Essa pessoa descobre o que aconteceu, decide qual réplica promover e executa os comandos à mão.
- Alguém altera o endereço do banco na configuração da aplicação e reinicia os serviços.
- Mais tarde, alguém precisa reconstruir o servidor antigo como réplica.
Entre o passo 1 e o 3, o sistema fica fora do ar por 30 minutos, 1 hora ou mais. Com o MaxScale, a promoção e o roteamento podem ser automáticos, e a reintegração pode ser automatizada quando não há divergências; o tempo real precisa ser medido.
As peças
| Peça | Papel | Vem com o MariaDB? |
|---|---|---|
| Replicação com GTID | Mantém as réplicas com cópia dos dados do primário | Sim, nativa |
| Replicação semissíncrona | Espera reconhecimento do relay log de uma réplica enquanto o modo está ativo; pode voltar ao assíncrono | Sim, integrada desde o 10.3.3 |
| Replicação paralela | Faz a réplica aplicar as mudanças em várias threads, para não ficar para trás | Sim, nativa |
| MaxScale | Proxy que divide leitura e escrita, monitora e faz o failover | Produto separado da MariaDB plc (veja a licença abaixo) |
O GTID (Global Transaction ID) é a peça-chave. Cada transação recebe um identificador único no formato domínio-servidor-sequência, por exemplo 0-1-99812. Com isso, qualquer réplica sabe exatamente até onde já aplicou, e trocar de primário vira uma operação simples, sem precisar caçar arquivo e posição de binlog.
Passo 1: configure os servidores
No exemplo, db1 (10.0.0.11) é o primário e db2 (10.0.0.12) é a réplica. Os dois recebem quase a mesma configuração, porque qualquer um pode virar primário depois de um failover:
# /etc/mysql/mariadb.conf.d/60-replicacao.cnf
[mariadb]
server_id = 1 # único por servidor: 1 no db1, 2 no db2
log_bin = mariadb-bin
binlog_format = ROW
log_slave_updates = ON # a réplica também grava binlog (pode virar primário)
gtid_strict_mode = ON # recusa transações que quebrariam a sequência de GTID
# Replicação semissíncrona (habilitada nos dois lados, em todos os servidores)
rpl_semi_sync_master_enabled = ON
rpl_semi_sync_slave_enabled = ON
rpl_semi_sync_master_timeout = 10000 # ms; depois disso, volta a ser assíncrona
# Replicação paralela, para a réplica não ficar para trás
slave_parallel_threads = 4
slave_parallel_mode = optimistic
Na réplica, adicione também read_only = ON, para ninguém gravar nela por engano. O MaxScale cuida de ligar e desligar isso nas trocas de papel.
Passo 2: o usuário de replicação
No primário:
CREATE USER 'repl'@'10.0.0.%' IDENTIFIED BY 'troque-esta-senha';
GRANT REPLICATION REPLICA ON *.* TO 'repl'@'10.0.0.%';
Passo 3: crie a réplica a partir de um backup
Para bases grandes, prefira o backup físico quando a compatibilidade de versões permitir; o dump continua útil quando você precisa de portabilidade. O backup físico, que copia os dados com o primário no ar e restaura rápido. Explico o mariadb-backup em detalhe no artigo sobre backup sem parar o banco. Aqui vai o resumo:
# No primário (db1): backup completo
mariadb-backup --defaults-extra-file=/etc/mysql/backup.cnf \
--backup --target-dir=/backup/para-replica
# Copie para a réplica (db2)
rsync -a /backup/para-replica/ db2:/backup/para-replica/
# Na réplica (db2), em laboratório: preparar e restaurar
# Confirme o datadir real e preserve qualquer dado existente antes de seguir
mariadb-backup --prepare --target-dir=/backup/para-replica
systemctl stop mariadb
mv /var/lib/mysql /var/lib/mysql.antes-da-restauracao && mkdir /var/lib/mysql
mariadb-backup --copy-back --target-dir=/backup/para-replica
chown -R mysql:mysql /var/lib/mysql
systemctl start mariadb
# O GTID do momento do backup é a terceira coluna deste arquivo
cat /backup/para-replica/mariadb_backup_binlog_info
# mariadb-bin.000231 48213 0-1-99812
(Em versões anteriores ao 11.1, o arquivo se chama xtrabackup_binlog_info.)
Passo 4: ligue a réplica
Na réplica, aponte para o primário usando o GTID que o backup registrou:
SET GLOBAL gtid_slave_pos = '0-1-99812';
CHANGE MASTER TO
MASTER_HOST = '10.0.0.11',
MASTER_USER = 'repl',
MASTER_PASSWORD = 'troque-esta-senha',
MASTER_USE_GTID = slave_pos;
START REPLICA;
SHOW REPLICA STATUS\G
No resultado, confira três campos:
Slave_IO_Running: YeseSlave_SQL_Running: Yes: a réplica está recebendo e aplicando.Seconds_Behind_Master: o atraso em segundos. O ideal é ficar perto de zero.Gtid_IO_Pos: até qual transação a réplica já recebeu.
Para conferir se a semissíncrona está ativa, rode no primário:
SHOW GLOBAL STATUS LIKE 'Rpl_semi_sync_master_status'; -- deve ser ON
Atenção: a semissíncrona garante que houve reconhecimento da gravação no relay log de uma réplica, não que ela já foi aplicada; a durabilidade depende também da configuração da réplica. E, se nenhuma réplica responder dentro do rpl_semi_sync_master_timeout, o primário volta a funcionar de forma assíncrona para não parar a aplicação. Monitore a variável acima: se ela ficar OFF, você perdeu essa proteção.
Passo 5: o MaxScale na frente
Primeiro, um ponto importante sobre a licença: ela depende da versão. As séries 2.0 a 24.02 usaram BSL, com autorização adicional para produção com menos de três instâncias de servidor e conversão para GPL na data definida na licença de cada versão. A partir do MaxScale 25.01, aplicam-se termos comerciais: a antiga franquia BSL de dois servidores não deve ser presumida para as versões atuais. Nosso exemplo com dois bancos não implica gratuidade: confira os termos e a assinatura do pacote escolhido nas fontes oficiais ao final. MariaDB Community Server e MaxScale são produtos com licenças distintas.
Os usuários do MaxScale
O MaxScale precisa de dois usuários: um para monitorar e executar o failover e outro para autenticar as conexões da aplicação. No primário (eles replicam para as réplicas):
-- Usuário do monitor (MariaDB 11.0.1 ou superior)
CREATE USER 'mariadbmon'@'10.0.0.%' IDENTIFIED BY 'senha-monitor';
GRANT REPLICA MONITOR, READ_ONLY ADMIN, REPLICATION SLAVE ADMIN,
BINLOG ADMIN, CONNECTION ADMIN, PROCESS, RELOAD, SET USER,
EVENT, SHOW DATABASES
ON *.* TO 'mariadbmon'@'10.0.0.%';
GRANT SELECT ON mysql.user TO 'mariadbmon'@'10.0.0.%';
GRANT SELECT ON mysql.global_priv TO 'mariadbmon'@'10.0.0.%';
-- Usuário do serviço (lê as permissões para autenticar a aplicação)
CREATE USER 'maxscale'@'10.0.0.%' IDENTIFIED BY 'senha-servico';
GRANT SHOW DATABASES ON *.* TO 'maxscale'@'10.0.0.%';
GRANT SELECT ON mysql.user TO 'maxscale'@'10.0.0.%';
GRANT SELECT ON mysql.global_priv TO 'maxscale'@'10.0.0.%';
GRANT SELECT ON mysql.db TO 'maxscale'@'10.0.0.%';
GRANT SELECT ON mysql.tables_priv TO 'maxscale'@'10.0.0.%';
GRANT SELECT ON mysql.columns_priv TO 'maxscale'@'10.0.0.%';
GRANT SELECT ON mysql.procs_priv TO 'maxscale'@'10.0.0.%';
GRANT SELECT ON mysql.proxies_priv TO 'maxscale'@'10.0.0.%';
GRANT SELECT ON mysql.roles_mapping TO 'maxscale'@'10.0.0.%';
Os privilégios mudam por versão. Até o MariaDB 10.10, a documentação usa SUPER em lugar de parte da lista detalhada; no 10.11, inclua também READ_ONLY ADMIN. Preserve os privilégios de monitoramento e de leitura necessários e confira os grants oficiais da versão escolhida, em vez de copiar uma lista de outra versão. Use hosts restritos e senhas fortes; a faixa 10.0.0.% é apenas um exemplo de laboratório.
A configuração
# /etc/maxscale.cnf
[maxscale]
threads = auto
[db1]
type = server
address = 10.0.0.11
port = 3306
protocol = MariaDBBackend
[db2]
type = server
address = 10.0.0.12
port = 3306
protocol = MariaDBBackend
# Monitor: acompanha a topologia e faz o failover
[Monitor-Replicacao]
type = monitor
module = mariadbmon
servers = db1,db2
user = mariadbmon
password = senha-monitor
replication_user = repl
replication_password = troque-esta-senha
monitor_interval = 2000ms
failcount = 3 # 3 verificações falhas (~6 s) antes de agir
auto_failover = true # promove uma réplica se o primário cair
auto_rejoin = true # tenta reintegrar o antigo primário se os GTIDs permitirem
enforce_read_only_slaves = true # aplica read_only; privilégios elevados podem contorná-lo
# Serviço: divide leitura e escrita
[Servico-RW]
type = service
router = readwritesplit
servers = db1,db2
user = maxscale
password = senha-servico
transaction_replay = true # refaz no novo primário a transação interrompida
master_reconnection = true
causal_reads = local # a sessão lê o que acabou de escrever
# Porta onde a aplicação se conecta
[Listener-RW]
type = listener
service = Servico-RW
protocol = MariaDBClient
port = 4006
systemctl enable --now maxscale
maxctrl list servers
O maxctrl list servers deve mostrar o db1 como Master, Running e o db2 como Slave, Running. A partir de agora, a aplicação deixa de se conectar ao db1 e passa a se conectar ao MaxScale, na porta 4006. Esse é o ajuste de endpoint; valide também reconexão, tratamento de erros e compatibilidade com replay de transações. O replay não cobre todo tipo de falha ou transação; não o trate como garantia de execução exatamente uma vez.
"Gravei e não li": o problema do atraso da réplica
Quem divide leitura e escrita esbarra logo nisto. O usuário salva o cadastro, a tela recarrega, a leitura vai para a réplica, que ainda não recebeu a alteração, e o cadastro "sumiu". O usuário clica em salvar de novo e duplica o registro.
O causal_reads = local acompanha o GTID das escritas da mesma conexão e espera sua aplicação na réplica antes de ler, com tratamento de timeout pelo roteador. Não garante causalidade automaticamente em outra conexão do pool: salvar um cadastro e recarregar a tela pode usar conexões diferentes. Avalie global quando as conexões passam pelo mesmo serviço, e confira os limites entre instâncias diferentes do MaxScale.
Antes do MaxScale 24.02, todos os backends precisam incluir last_gtid em session_track_system_variables. Desde o 24.02, o MaxScale configura esse acompanhamento automaticamente nas novas conexões de backend. Confirme os detalhes da versão instalada na documentação do Readwritesplit.
Testando o failover (antes de precisar dele)
Primeiro, a troca planejada, que é o que você usa para manutenção, como atualizar a versão do MariaDB:
# Promove o db2 a primário e rebaixa o db1 a réplica, de forma controlada
maxctrl call command mariadbmon switchover Monitor-Replicacao db2 db1
maxctrl list servers
Depois, o teste que interessa, que é a queda de verdade:
# No db2 (primário após o switchover): simula a queda
systemctl stop mariadb
# No host administrativo: aguarde o db1 ser promovido a Master
watch -n1 maxctrl list servers
# No db2: reinicie; se elegível ao auto_rejoin, volta como réplica de db1
systemctl start mariadb
Faça isso com a aplicação de teste rodando e gravando. Meça a indisponibilidade: ela depende da detecção, dos timeouts, do atraso da réplica e da recuperação das conexões. Teste também falhas de rede e divergências, não apenas o desligamento limpo do serviço.
Atenção: em replicação assíncrona, o failover pode perder transações que o primário confirmou mas ainda não tinha enviado a nenhuma réplica. A semissíncrona reduz muito essa janela. Nas versões que documentam esse valor, auto_failover = safe recusa a promoção quando os GTIDs observados indicam perda de dados. Isso não garante perda zero: uma escrita após a última consulta do monitor pode não ter sido observada antes da queda. Confirme o suporte na versão instalada.
E se o MaxScale cair?
O MaxScale vira o ponto de entrada da aplicação, então ele também não pode ser um ponto único de falha. A solução usual é rodar dois MaxScale atrás de um IP virtual (com keepalived, por exemplo) ou de um balanceador. Os monitores dos dois podem ser configurados para cooperar, de modo que só um deles execute o failover de cada vez (parâmetro cooperative_monitoring_locks).
Replicação + MaxScale ou Galera?
| Replicação + MaxScale | Galera Cluster | |
|---|---|---|
| Escrita | Em um servidor (o primário) | Em qualquer nó |
| Replicação | Assíncrona ou semissíncrona | Virtualmente síncrona; aplicação e leitura ainda exigem atenção à causalidade |
| Latência de escrita | Baixa | Maior (todos os nós precisam concordar) |
| Distância entre servidores | Tolera bem servidores longe um do outro | Funciona melhor com nós próximos |
| Topologia prática | 2 bancos no exemplo; redundância do proxy é separada | Em geral 3 participantes para quorum; pode haver arbitrador |
| Tempo de recuperação | Depende do monitor, réplica e aplicação | Depende do quorum, roteamento e aplicação |
Para a maioria das aplicações, replicação com MaxScale é o caminho mais simples e com melhor desempenho de escrita. O Galera faz sentido quando você precisa de replicação virtualmente síncrona e quorum, aceitando os custos de latência e operação. Nenhuma dessas escolhas dispensa testes de durabilidade e recuperação.
O que isso significa para você
- Ative GTID e
log_slave_updatesem todos os servidores, mesmo que hoje você só tenha um. - Prefira
mariadb-backupem bases grandes com versões compatíveis; use dump quando precisar de portabilidade. - Ligue a semissíncrona e monitore
Rpl_semi_sync_master_status. - Ligue a replicação paralela para a réplica não ficar para trás.
- Coloque o MaxScale na frente, com
auto_failover,auto_rejoinecausal_reads. - Teste o failover com a aplicação rodando, antes que a vida real teste por você.
Tudo do lado do servidor (GTID, semissíncrona, replicação paralela) é nativo e gratuito no MariaDB Community. O MaxScale completa o quadro com a parte que mais tira o sono: decidir sozinho, às 3h da manhã, o que fazer quando o primário cai.
Leia também
- MariaDB Galera Cluster: multi-master síncrono que já vem na caixa
- Backup no MariaDB sem parar o banco: mariadb-backup, incremental e restauração até o minuto exato
- ALTER TABLE sem downtime no MariaDB: mudando tabelas gigantes com o sistema no ar
- MariaDB: thread pool gratuito para milhares de conexões
- Até onde o MariaDB aguenta? As perguntas que todo CTO faz
- MariaDB Spider: sharding transparente com tabelas distribuídas
Todos os artigos sobre MariaDB →
Documentação oficial consultada
- MariaDB Monitor: grants, failover, switchover e rejoin
- Readwritesplit: causal reads e transaction replay
- Replicação semissíncrona
- Versões sob BSL
- Histórico dos termos comerciais
Nota técnica: os exemplos foram revisados com a documentação, mas não executados em um servidor MariaDB nesta publicação. Valide versão, caminhos, privilégios, restrições e comportamento em laboratório antes de usar em produção. As capas são imagens fotográficas geradas por IA, não resultados de testes.
Me acompanhe no LinkedIn e no Instagram. Para fortalecer sua base em SQL, conheça meu livro Aprendendo MySQL. Conheça também a HTI Tecnologia.