A maioria dos diagramas de replicação mostra um primário, duas réplicas e setas entre eles. A imagem é verdadeira e quase inútil durante um incidente, porque não diz qual nó aceita escritas agora, qual réplica pode servir dados atrasados nem o que acontece quando o primário para de responder.
Um diagrama de replicação útil responde a quatro perguntas. Este guia trabalha essas perguntas com PostgreSQL, Patroni e etcd e depois desenha o resultado. As mesmas perguntas valem para MySQL, para outros gerenciadores de failover e para serviços gerenciados.
Quatro perguntas que todo diagrama de replicação precisa responder
- Para onde vão as escritas? Precisa haver exatamente um primário, e os clientes precisam de um jeito de encontrá-lo depois de um failover.
- Quão atrasadas as leituras podem estar? Cada réplica é síncrona ou assíncrona, e as assíncronas ficam para trás.
- Quem decide um failover? Alguma coisa precisa detectar a falha, escolher uma réplica e promovê-la.
- O que impede dois primários? Um primário isolado precisa parar de aceitar escritas antes que outro nó seja promovido.
Replicação síncrona e assíncrona
A replicação por streaming do PostgreSQL é assíncrona por padrão. O primário confirma um commit antes de o standby recebê-lo, então uma queda pode perder as transações que ainda não tinham chegado. A documentação do PostgreSQL diz isso com todas as letras: a quantidade de dados perdidos é proporcional ao atraso de replicação no momento do failover.
A replicação síncrona faz os commits esperarem por um ou mais standbys listados em synchronous_standby_names. Quanto eles esperam depende de synchronous_commit:
synchronous_commit |
Um commit retorna depois que o standby síncrono o tiver |
|---|---|
remote_apply |
Aplicado, então consultas no standby já o veem |
on (padrão) |
Gravado em armazenamento durável |
remote_write |
Entregue ao sistema operacional, ainda sem flush |
local |
Sem esperar o standby; só o flush local |
off |
Sem esperar nada, nem o disco local |
O preço é a latência: todo commit inclui uma ida e volta até o standby, então mantenha os standbys síncronos em uma zona próxima, não em outra região. E se o único standby síncrono sumir, os commits podem esperar para sempre. Defina mais de um candidato ou deixe um gerenciador de failover mudar a configuração por você.
Réplicas de leitura e roteamento de leituras
Uma réplica assíncrona é um bom lugar para relatórios e endpoints com muitas leituras, desde que o código saiba que os dados podem estar atrasados. No primário, a view pg_stat_replication mostra write_lag, flush_lag e replay_lag de cada standby; replay_lag aproxima quanto tempo leva até um commit ficar visível na réplica.
Decida por consulta quais podem ler dados atrasados. Um usuário que acabou de salvar um formulário e recarrega a página deve ler do primário. Um dashboard pode ler da réplica.
Failover: quem decide
O PostgreSQL não detecta sozinho um primário com falha nem promove um standby; a documentação deixa isso para ferramentas externas. O Patroni é uma escolha comum. Cada nó roda um agente do Patroni, e os agentes compartilham estado por um armazenamento de configuração distribuído como o etcd:
- O primário mantém uma chave de líder com TTL e a renova a cada ciclo (
loop_wait, 10 segundos por padrão). - Se não conseguir renovar a chave por
ttlsegundos (30 por padrão), a chave expira e as réplicas elegíveis começam uma corrida pela liderança. - O melhor candidato pega a chave e se promove; os outros passam a segui-lo.
Os clientes encontram o novo primário por meio de um proxy. A API REST do Patroni responde a GET /primary com HTTP 200 só no nó que tem a chave de líder, então o HAProxy pode mandar as escritas para o nó que disser sim:
listen primary
bind *:5000
option httpchk OPTIONS /primary
http-check expect status 200
default-server inter 3s fall 3 rise 2 on-marked-down shutdown-sessions
server pg-a 10.0.1.10:5432 check port 8008
server pg-b 10.0.2.10:5432 check port 8008
server pg-c 10.0.3.10:5432 check port 8008
Um segundo listener na porta 5001 verifica /replica?lag=16MB, o que tira de cena uma réplica com mais de 16 MB de WAL de atraso. Rode o HAProxy em mais de uma máquina, ou ele vira o ponto único de falha que você estava tentando eliminar.
Split-brain: o que impede dois primários
Dois primários aceitando escritas significam dois históricos que divergem, e um deles vai para o lixo. A documentação do PostgreSQL chama o remédio de STONITH: é preciso avisar ao primário antigo que ele não é mais o primário. O Patroni empilha várias defesas:
- O lease. Um primário que não consegue atualizar a chave de líder para de aceitar escritas: o Patroni para ou rebaixa o PostgreSQL. Se o próprio armazenamento ficar inacessível por mais tempo que
retry_timeout, o Patroni rebaixa o primário, a menos que o modo failsafe do DCS esteja ativado. - Um watchdog. Se o processo do Patroni travar, um watchdog do Linux reinicia a máquina. O Patroni o configura para disparar 5 segundos antes de o TTL expirar.
- Um quórum. O etcd precisa da maioria dos membros, dois de três, para aceitar uma escrita. Três membros em três zonas sobrevivem à perda de uma zona, e uma minoria isolada não consegue eleger ninguém.
- Um único processo no comando. Só o Patroni pode iniciar o PostgreSQL. Um serviço do sistema que reinicia o Postgres sozinho depois de uma queda pode trazer de volta um segundo primário.
Quando o primário antigo volta, ele precisa entrar de novo como réplica. O pg_rewind ressincroniza o diretório de dados dele com o novo primário sem copiar blocos que não mudaram; o Patroni o executa quando use_pg_rewind está ativado.
# Patroni dynamic configuration (patronictl edit-config)
ttl: 30
loop_wait: 10
retry_timeout: 10
synchronous_mode: on # promote only a node that has every acknowledged commit
postgresql:
use_pg_rewind: true
parameters:
wal_log_hints: "on"
# Local patroni.yml tags
# pg-a: noloadbalance: true (failover target, no reads)
# pg-c: nosync: true, nofailover: true (reads only)
Com synchronous_mode ativado, o Patroni só promove um standby que ele sabe ter todos os commits informados como bem-sucedidos. A contrapartida é a disponibilidade: se o primário cair enquanto nenhum standby síncrono estiver conectado, o Patroni não promove ninguém, e as escritas ficam paradas até alguém intervir.
Replicação não é backup
Um DROP TABLE chega a todas as réplicas em milissegundos. Mantenha o arquivamento contínuo de WAL e backups base regulares, que permitem a recuperação point-in-time para qualquer momento depois do backup base, e teste as restaurações com regularidade.
O diagrama
Conteúdo do diagrama
- Order service
- HAProxy
- AZ a
- AZ b
- AZ c
- pg-a · sync standby
- pg-b · primary
- pg-c · async replica
- etcd
- WAL archive + base backups
- SQL
- writes :5000
- reads :5001
- WAL (sync)
- WAL (async)
- leader key, TTL 30 s
- races if key expires
- archived WAL
Como ler:
- Escritas. O serviço de pedidos se conecta ao HAProxy. A porta 5000 vai para o pg-b, o único nó que responde 200 a
/primary. - Leituras. A porta 5001 vai para o pg-c, uma réplica assíncrona em outra zona. O pg-a não atende leituras.
- Replicação. O pg-b envia WAL ao pg-a de forma síncrona (linha contínua) e ao pg-c de forma assíncrona (tracejada).
- Failover. O pg-b renova a chave de líder no etcd. Se a chave expirar, o pg-a, o standby síncrono, disputa a liderança. O pg-c tem a tag
nofailover. - Recuperação. O pg-b arquiva o WAL e os backups base em um armazenamento de objetos.
- Metas. A nota no canto superior esquerdo traz RPO, RTO, o orçamento de atraso e a retenção de backups.
O que rotular em um diagrama de replicação
- O modo em cada seta de replicação, síncrona ou assíncrona, e o orçamento de atraso das réplicas assíncronas.
- O papel de cada réplica: candidata a failover, réplica de leitura ou as duas coisas.
- Onde fica o lock de líder e o TTL dele, ou o serviço gerenciado que faz esse papel.
- Como os clientes encontram o primário: um proxy, um nome DNS ou um IP virtual.
- Domínios de falha: zonas de disponibilidade como molduras, incluindo onde rodam os membros do consenso.
- Metas de RPO e RTO, para que quem revisa possa conferir o design contra elas.
O que os serviços gerenciados fazem por você
Bancos de dados gerenciados respondem às mesmas perguntas, mas algumas respostas são fixas. Em uma instância Amazon RDS Multi-AZ, o standby é síncrono, fica em outra zona e não atende leituras. O failover aponta o registro DNS para o standby e normalmente leva de 60 a 120 segundos. Um cluster Multi-AZ tem dois standbys legíveis, usa replicação semissíncrona e normalmente faz o failover em menos de 35 segundos. No diagrama, o endpoint substitui o HAProxy e a AWS substitui o Patroni e o etcd. Mesmo assim, você rotula o atraso, o roteamento de leituras e as metas.
Teste o failover
Troque o primário de propósito, por exemplo em toda janela de manutenção com patronictl switchover. A documentação do PostgreSQL recomenda alternar com regularidade, porque isso também testa o mecanismo de failover. Meça por quanto tempo as escritas falham, confira se o HAProxy acompanha e confirme que nenhum commit confirmado ficou faltando. Para failover entre regiões, veja o diagrama de arquitetura multirregião da AWS.
Monte no Archboard
- Entre com Google ou GitHub e crie um diagrama.
- Adicione três nós Moldura do pack Básico para as zonas de disponibilidade e, em cada um, um nó Postgres do pack Dados: pg-a (standby síncrono), pg-b (primário) e pg-c (réplica assíncrona).
- Acima das zonas, adicione um Contêiner do pack Modelo C4 para o serviço de pedidos e um Balanceador de carga do pack Rede para o HAProxy.
- Abaixo delas, adicione um Contêiner do pack Modelo C4 para o etcd e um Repositório de objetos do pack Dados para o arquivo de WAL.
- Rotule as conexões: "writes :5000", "reads :5001", "WAL (sync)" contínua, "WAL (async)" tracejada, "leader key, TTL 30 s" do pg-b para o etcd e uma conexão tracejada do pg-a para o etcd para a corrida pela liderança.
- Adicione um bloco Markdown do pack Básico com RPO, RTO, orçamento de atraso e retenção de backups.
O exemplo usa 11 nós de diagrama e fica dentro do plano Free. Para manter o diagrama atualizado conforme o cluster muda, veja Diagramas de arquitetura que não envelhecem e, para o método geral, o exemplo de diagrama de design de sistemas.
Perguntas frequentes
Uma réplica de leitura é um backup? Não. Exclusões e migrations com erro também são replicadas. Backups com arquivamento de WAL permitem voltar a um momento anterior ao erro.
Replicação síncrona significa zero perda de dados? Para commits que um standby síncrono confirmou, sim, desde que o failover promova só esse standby. Commits aceitos enquanto nenhum standby síncrono estava conectado existem apenas no primário.
Por que três membros no etcd e não dois? O etcd precisa de maioria para aceitar escritas. Dois membros não aguentam a perda de nenhum; três aguentam a perda de um e mantêm a maioria de um lado de uma partição de rede.
Posso usar DNS em vez de um proxy para encontrar o primário? Sim, é o que os serviços gerenciados fazem. Mantenha TTLs de DNS curtos e garanta que os clientes não guardem as resoluções em cache por muito tempo; a AWS recomenda um TTL de cache de DNS da JVM de no máximo 60 segundos.
Com que frequência devemos testar o failover? Com regularidade suficiente para que o time confie nele, por exemplo um switchover planejado em toda janela de manutenção, mais um teste de falha não planejada em staging.