trilha

Tecnologias/02 - Bancos de Dados/01 - Relacionais/02 - MySQL6 min

Replicação e Alta Disponibilidade

Conceito

A replicação foi historicamente o maior diferencial do MySQL: fácil de montar, fácil de entender. É também o lugar onde mais gente perde dados sem perceber, porque o padrão é assíncrono.

O binlog: um log lógico, separado do redo

Redo log (InnoDB) Binlog (servidor)
Nível físico (páginas) lógico (eventos de linha ou statements)
Serve para recuperação de crash replicação e PITR
Escopo o motor o servidor inteiro

Três formatos, e a escolha importa:

binlog_format Grava Problema
STATEMENT o SQL executado não determinístico: NOW(), RAND(), UUID() divergem na réplica
ROW (padrão) as linhas antes/depois seguro; volume maior
MIXED statement, e row quando inseguro intermediário

Use ROW. É o único seguro e é pré-requisito para CDC (Debezium lê binlog em formato ROW, assim como lê o WAL lógico do Postgres).

O fluxo, e onde ele quebra

PRIMÁRIO                                        RÉPLICA
COMMIT
  ├─ redo (fsync)  ──── durabilidade local
  └─ binlog (fsync) ─── evento
        │
        └──[rede]──> I/O thread → relay log → applier thread(s) → aplica
                       ↑ recebeu                 ↑ APLICOU (é aqui que o lag mora)

Dois estágios, dois lags diferentes. SHOW REPLICA STATUS mostra Seconds_Behind_Source, que mede o applier — e é uma medida notoriamente enganosa (ela mede a diferença de timestamp do evento em processamento, não a fila real).

GTID: o que tornou failover viável

Antes, a réplica era posicionada por (arquivo de binlog, offset) — e num failover era preciso descobrir manualmente qual offset do novo primário corresponde ao que a réplica já tinha. Erro humano garantido.

Com GTID (gtid_mode = ON), cada transação recebe um id global uuid_do_servidor:número. A réplica sabe exatamente o conjunto que já aplicou (gtid_executed) e pede o resto. Failover e re-apontamento tornam-se declarativos:

CHANGE REPLICATION SOURCE TO SOURCE_HOST='novo-primario', SOURCE_AUTO_POSITION = 1;

GTID é pré-requisito prático para qualquer HA moderna (InnoDB Cluster, Orchestrator, ProxySQL com failover).

Replicação paralela: como o applier deixou de ser gargalo

O primário escreve com N threads; a réplica aplicava com uma. Em carga de escrita alta, o lag só crescia. A solução:

replica_parallel_workers = 8
replica_parallel_type    = LOGICAL_CLOCK   # aplica em paralelo transações que
                                           # confirmaram juntas no primário
binlog_transaction_dependency_tracking = WRITESET   # ainda mais paralelismo:
                                           # transações que tocam linhas diferentes
replica_preserve_commit_order = ON         # mantém a ordem de commit visível

WRITESET é o ajuste que mais reduz lag em cargas reais, e muita gente não conhece: ele detecta independência por conjunto de linhas escritas, não por janela de commit.

Semi-síncrono: fechando a janela de perda

rpl_semi_sync_source_enabled = 1
rpl_semi_sync_source_wait_for_replica_count = 1
rpl_semi_sync_source_timeout = 1000        # ms; depois volta a assíncrono

O COMMIT espera uma réplica confirmar o recebimento (não a aplicação). Reduz a janela de perda de "tudo que não replicou" para "quase nada". Note o timeout: passado ele, o primário volta a assíncrono sozinho — protege disponibilidade e reabre a janela de perda silenciosamente.

As topologias de HA

Solução Como funciona Failover
Async + Orchestrator/ProxySQL replicação clássica + ferramenta externa automático, com ressalvas
Semi-sync + ferramenta idem, com janela de perda menor automático
Group Replication / InnoDB Cluster grupo com consenso (Paxos), certificação de conflito automático e nativo
Galera / Percona XtraDB Cluster replicação virtualmente síncrona automático
Gerenciado (RDS, Aurora) o provedor resolve automático

InnoDB Cluster = Group Replication + MySQL Shell + MySQL Router (o proxy que esconde a troca de primário do app). É a resposta oficial da Oracle para HA, e o mais próximo de "vem na caixa" — algo que o Postgres não tem.

Trade-offs

Assíncrono é rápido e perde dados no failover. Se o primário confirma e cai antes de enviar o binlog, aquela transação não existe no novo primário — e pode reaparecer se o antigo voltar, criando divergência. Semi-síncrono reduz; consenso (Group Replication) resolve, ao custo de latência de rede em cada commit.

Group Replication multi-primário parece atraente e cobra caro. Escrever nos três nós gera certificação de conflito: quando dois nós alteram a mesma linha, um dos commits é abortado depois de a aplicação achar que deu certo. Na prática, quase todo mundo roda single-primary. Ver Availability vs Consistency.

Ler da réplica é a otimização mais tentadora e mais perigosa. Com lag assíncrono, um fluxo "cria e lê" quebra intermitentemente. As saídas: ler do primário depois de escrever (read-your-writes por rota), ou usar GTID — enviar WAIT_FOR_EXECUTED_GTID_SET para garantir que a réplica alcançou a sua transação antes de ler.

O trade-off central: a facilidade da replicação MySQL é real e vem com um padrão inseguro. binlog_format=ROW + gtid_mode=ON + semi-síncrono + réplica paralela com WRITESET transformam o padrão frágil em algo defensável — e nenhum deles é ligado por você por acidente.

Exemplo prático

Configuração de um par primário/réplica defensável

# --- comum ---
server_id                 = 1              # único por servidor!
gtid_mode                 = ON
enforce_gtid_consistency  = ON
binlog_format             = ROW
binlog_row_image          = FULL           # MINIMAL é menor, mas complica CDC
sync_binlog               = 1              # fsync do binlog a cada commit
innodb_flush_log_at_trx_commit = 1
log_replica_updates       = ON             # a réplica pode ser primário depois

# --- na réplica ---
read_only                 = ON
super_read_only           = ON             # impede até o root de escrever por acidente
replica_parallel_workers  = 8
replica_parallel_type     = LOGICAL_CLOCK
replica_preserve_commit_order = ON
binlog_transaction_dependency_tracking = WRITESET
sync_binlog = 1 e innodb_flush_log_at_trx_commit = 1

Só os dois juntos dão durabilidade real. Com sync_binlog = 0, uma queda pode deixar o InnoDB à frente do binlog: o dado existe localmente e nunca foi replicado. É a configuração que produz "a réplica está com dado diferente do primário".

Read-your-writes com GTID

// 1) escreve no primário e captura o GTID da transação
var gtid string
_ = primario.QueryRowContext(ctx, `SELECT @@SESSION.gtid_executed`).Scan(&gtid)

// 2) antes de ler na réplica, espera ela alcançar aquele GTID (timeout 1s)
var res int
if err := replica.QueryRowContext(ctx,
	`SELECT WAIT_FOR_EXECUTED_GTID_SET(?, 1)`, gtid).Scan(&res); err != nil || res != 0 {
	// não alcançou em 1s: leia do primário em vez de mostrar dado velho
	return lerDoPrimario(ctx)
}
return lerDaReplica(ctx)

Esse padrão resolve de forma correta o que normalmente é "resolvido" com um sleep(200ms) espalhado pelo código.

Monitoramento

SHOW REPLICA STATUS\G
-- Replica_IO_Running / Replica_SQL_Running : devem ser 'Yes'
-- Seconds_Behind_Source                    : indicativo, não confie sozinho
-- Retrieved_Gtid_Set vs Executed_Gtid_Set  : a fila real (recebido - aplicado)
-- Last_SQL_Error                           : por que a réplica parou
-- lag por worker e fila real, no performance_schema
SELECT * FROM performance_schema.replication_applier_status_by_worker\G
SELECT channel_name, service_state, count_transactions_in_queue
  FROM performance_schema.replication_applier_status;

Como isso falha em produção

Sintoma Causa
Réplica parou com erro de duplicidade escrita direta na réplica; ligue super_read_only
Dado divergente entre primário e réplica binlog_format=STATEMENT com função não determinística; ou sync_binlog=0
Lag crescendo em pico de escrita applier sem paralelismo, ou transação gigante no primário
Escrita perdida após failover replicação assíncrona; use semi-sync ou Group Replication
Seconds_Behind_Source = 0 mas dado velho métrica enganosa; compare Retrieved vs Executed GTID
Dois primários aceitando escrita split brain: failover sem fencing do nó antigo
CDC quebrando após rotação de binlog retenção de binlog (binlog_expire_logs_seconds) menor que a janela de indisponibilidade do consumidor

Aprofundamento aplicado

Replicado não significa aplicado

Uma réplica pode ter recebido eventos e ainda não os ter aplicado. O caminho crítico é receber, manter os logs e executar as transações. Atraso pode nascer na rede ou no applier; aumentar banda não resolve uma aplicação serializada por conflitos.

GTIDs identificam transações e ajudam a raciocinar sobre o conjunto executado. Eles não provam que o candidato a primário contém toda transação que algum cliente recebeu como confirmada, independentemente da configuração de replicação. Referência: replicação com GTIDs.

Ensaio de failover

  1. Registre uma escrita identificável e a confirmação percebida pelo cliente.
  2. Interrompa o primário no ambiente de teste.
  3. Escolha a réplica conforme os dados realmente disponíveis.
  4. Garanta que o antigo primário não continue aceitando escritas.
  5. Confira reconexão, presença da escrita e tratamento de operações incertas.

Resultado esperado: medir separadamente tempo de recuperação e eventual perda de dados. Uma conexão encerrada durante COMMIT pode exigir reconciliação por identificador de operação, porque o cliente não conhece automaticamente o resultado.

Relacionado


Parte de MOC MySQL · Replication (docs)

Buscar

Busca por título, seção e texto das notas