Pular para conteúdo

Replicação

Dez minutos antes da abertura das vendas, o banco do catálogo está a 90% de CPU — quase tudo vindo de SELECTs da página do evento. O time adiciona duas réplicas de leitura e direciona as leituras para elas. A CPU cai para 30%. Então o canal de suporte se acende: "Eu paguei, o e-mail de confirmação chegou, mas Meus ingressos está vazio!" Nada se perdeu. A leitura seguinte do fã simplesmente foi para uma réplica que ainda não tinha recebido a escrita.

A replicação — manter uma cópia dos mesmos dados em várias máquinas — é a ferramenta de escalabilidade e disponibilidade mais usada em bancos de dados, e ela vem exatamente com esse tipo de surpresa. Esta página cobre as três formas de replicar (líder único, multilíder e sem líder), o que o atraso de replicação faz com os usuários e o que acontece quando o líder morre.


Por que replicar

Objetivo Como a replicação ajuda Exemplo na BoxOffice
Disponibilidade se um nó morre, outro tem os dados e assume o primário do inventário cai às 10:01; um standby é promovido em segundos
Escala de leitura as leituras se espalham por várias cópias as leituras do catálogo vão para 2–5 réplicas
Latência uma cópia perto do usuário uma réplica em outra região para as páginas de eventos
Durabilidade a perda de um disco ou de uma máquina não perde dados confirmados um standby síncrono guarda toda reserva confirmada

A replicação não escala escritas: toda cópia precisa aplicar todas as escritas. Para isso você precisa de particionamento.


Replicação com líder único

Um nó, o líder (primário), aceita todas as escritas. Ele registra cada mudança no seu log — o write-ahead log (WAL) no PostgreSQL, o binlog no MySQL — e envia o log para os seguidores (réplicas, standbys), que o reaplicam na mesma ordem. As leituras podem ir para qualquer nó1.

flowchart LR
    app["serviço catalog"] -->|"escritas"| P[("primário")]
    P -->|"fluxo de WAL"| R1[("réplica 1")]
    P -->|"fluxo de WAL"| R2[("réplica 2")]
    app -->|"leituras"| R1
    app -->|"leituras"| R2
    app -->|"leituras que precisam ser atuais"| P

Esse é o modelo da streaming replication do PostgreSQL, da replicação do MySQL, do SQL Server Always On, dos replica sets do MongoDB e da maioria dos serviços gerenciados (réplicas de leitura do Amazon RDS, Cloud SQL, Azure Database). É simples de raciocinar porque existe exatamente uma ordem de escritas — a do líder.

Síncrona, assíncrona, semissíncrona

A decisão-chave é quando o líder diz ao cliente "confirmado":

sequenceDiagram
    participant C as Cliente
    participant P as Primário
    participant S as Standby síncrono
    participant A as Réplica assíncrona
    C->>P: COMMIT da reserva A-12
    P->>P: grava o WAL localmente
    P->>S: envia o WAL
    S-->>P: gravado em disco
    P-->>C: confirmado
    P->>A: envia o WAL (depois)
    Note over A: pode ficar ms … s atrás
Modo O líder responde "confirmado" depois de… Durabilidade se o líder for perdido Latência de escrita Risco de disponibilidade
Assíncrona gravar no próprio disco as últimas escritas ainda não enviadas são perdidas a menor nenhum vindo das réplicas
Síncrona (todas as réplicas) todas as réplicas confirmarem nenhuma perda a maior: a réplica mais lenta decide qualquer réplica fora do ar bloqueia as escritas
Semissíncrona / um standby síncrono uma réplica confirmar, as outras assíncronas nenhuma perda se esse standby sobreviver + uma ida e volta na rede se o standby síncrono morrer, outro precisa assumir o papel
Armazenamento com quorum (Aurora: 4 de 6 cópias em 3 zonas)4 um quorum de escrita dos nós de armazenamento confirmar sobrevive à perda de uma zona + ida e volta até o quorum tolera nós lentos

No PostgreSQL, isso são as configurações synchronous_commit e synchronous_standby_names5; ANY 1 (s1, s2) significa "espere qualquer um dos dois standbys" — um quorum que tolera um standby lento. Dentro de uma região, uma ida e volta síncrona custa cerca de um milissegundo; entre continentes custa de dezenas a centenas de milissegundos, e é por isso que réplicas entre regiões são quase sempre assíncronas.


Atraso de replicação e suas anomalias

Uma réplica assíncrona fica atrás do líder pelo atraso de replicação (replication lag): normalmente milissegundos, mas segundos ou minutos quando o líder recebe uma rajada de escritas, uma réplica reaplica uma transação enorme ou a rede está congestionada. O atraso cria três anomalias visíveis para o usuário2:

Anomalia O que o usuário vê Garantia que a evita
Ler a própria escrita falha paga, abre Meus ingressos, não vê nada consistência read-your-writes (ler as próprias escritas, read-after-write)
O tempo anda para trás atualiza a página: o ingresso aparece; atualiza de novo: sumiu (a segunda leitura caiu numa réplica mais atrasada) leituras monotônicas (monotonic reads)
Efeito antes da causa vê a resposta "estorno aprovado" antes do pedido de estorno numa conversa leituras de prefixo consistente (consistent prefix reads)
sequenceDiagram
    participant F as Fã
    participant O as serviço order
    participant P as Primário
    participant R as Réplica (atraso de 800 ms)
    F->>O: POST /orders/9001/confirm
    O->>P: UPDATE orders SET status = 'PAID'
    P-->>O: confirmado
    O-->>F: 200 "Pagamento confirmado"
    F->>O: GET /me/tickets
    O->>R: SELECT … WHERE customer_id = 7
    R-->>O: (ainda nenhuma linha)
    O-->>F: "Você não tem ingressos"

Qual a chance de isso acontecer? Num modelo ilustrativo — atraso distribuído exponencialmente em torno de uma média, e o próximo clique do fã indo para uma réplica — a resposta depende inteiramente do atraso naquele momento:

2026-11-01T21:55:00.427809 image/svg+xml Matplotlib v3.11.2, https://matplotlib.org/

Num dia tranquilo o bug é invisível; durante a abertura das vendas — justamente quando há mais fãs olhando — a maioria deles esbarra nele. É por isso que bugs de atraso sobrevivem aos testes.

Corrigindo o read-your-writes

Técnica Como Trade-off
Ler do líder depois de uma escrita por N segundos depois que um usuário escreve, direcione as leituras daquele usuário para o primário (um timestamp na sessão ou num cookie) simples; o primário recebe algumas leituras extras
Ler do líder os dados do próprio usuário "meus pedidos", "meus ingressos" sempre no primário; catálogo nas réplicas regra simples; não escala se a maioria das leituras for "minha"
Esperar a réplica alcançar a escrita devolve sua posição no log (LSN no PostgreSQL, GTID no MySQL); a próxima leitura só vai para uma réplica depois que ela reaplicou essa posição (pg_last_wal_replay_lsn()) preciso; exige encanamento na camada de acesso a dados
Réplica fixa por usuário faça hash do usuário para uma réplica garante leituras monotônicas, não read-your-writes
Réplica síncrona para leituras leia de um standby síncrono sem atraso, mas as escritas pagam a latência

Experimente

O simulador começa onde começa o chamado do suporte: o fã segurou A-12 há 300 ms, e o atraso é de 1 s (o REPLICA_DELAY=1s do Hands-on 6). Aperte ler minhas reservas: db-2 responde sem A-12, uma leitura desatualizada. Dê play até os pontos chegarem (1 s para db-2, 1,5 s para db-3) e leia de novo: atualizada. Recomece com ler as próprias escritas ligado: a leitura vai ao líder e vem atualizada na hora. Depois segure um assento novo em cada modo: async confirma em 2 ms, semi-sync em 1.003 ms, sync em 1.503 ms (espera db-3, o mais lento). Por fim, recomece em async e derrube o líder com A-12 em trânsito: db-2 é promovido sem ela e a reserva se perde; em semi-sync ou sync nada se perde (veja a seção de failover abaixo).

Monitore o atraso como métrica de primeira classe — pg_stat_replication.replay_lag no PostgreSQL, ReplicaLag no Amazon RDS — e crie alertas sobre ele (veja Observabilidade). Uma réplica minutos atrás é pior do que nenhuma réplica.

No código do BoxOffice: leituras roteadas pelo tipo de transação (lab 6)

O Hands-on 6 — Réplica e pooling dá ao inventory dois pools atrás de um único DataSource: uma transação marcada @Transactional(readOnly = true) (o mapa de assentos) recebe uma conexão da réplica, todas as outras recebem o primário. É a linha "ler do líder os dados do próprio usuário" da tabela: o find lê a reserva do próprio fã com um @Transactional comum, no primário, porque o fã acabou de criá-la. O lab então mede o bug que isso evita: lendo o mapa de assentos logo depois de uma reserva, cerca de 6% dos fãs não viram a própria reserva com a réplica na mesma máquina, e 98% com REPLICA_DELAY=1s.

services/inventory/src/main/java/boxoffice/inventory/DataSourceConfig.java (two pools, one DataSource)
/**
 * Two connection pools — primary and replica — behind one DataSource.
 * A transaction marked @Transactional(readOnly = true) gets a replica connection;
 * every other transaction gets a primary connection. The choice happens when the
 * transaction runs its first statement (that is what "lazy" means here).
 */
@Configuration
public class DataSourceConfig {

    @Bean
    @ConfigurationProperties("inventory.datasource.primary")
    HikariDataSource primaryDataSource() {
        return new HikariDataSource();
    }

    @Bean
    @ConfigurationProperties("inventory.datasource.replica")
    HikariDataSource replicaDataSource() {
        return new HikariDataSource();
    }

    @Bean
    @Primary
    DataSource dataSource(HikariDataSource primaryDataSource, HikariDataSource replicaDataSource) {
        var routing = new LazyConnectionDataSourceProxy(primaryDataSource);
        routing.setReadOnlyDataSource(replicaDataSource);     // Spring Framework 6.2+
        return routing;
    }

Experimente. No cenário db-replicas, um primário recebe as escritas e as réplicas atendem as leituras. Adicione réplicas e veja a vazão de leitura subir enquanto a carga do primário fica estável; depois aumente a taxa de escrita e veja o atraso de replicação crescer — e, com ele, a fração de leituras desatualizadas.


Pool de conexões

As réplicas multiplicam para onde as consultas podem ir; os pools de conexões decidem quantas podem rodar ao mesmo tempo. No PostgreSQL, cada conexão é um processo separado do sistema operacional, com memória própria; milhares de conexões ociosas custam RAM e CPU de verdade — e o banco não fica mais rápido além de algumas poucas consultas concorrentes por núcleo de CPU.

Dimensione os pools com a Lei de Little, não no chute: conexões ocupadas = consultas por segundo × tempo de consulta. Com 2.000 consultas/s × 5 ms = 10 conexões ocupadas em média. Um pool de 20–30 absorve as rajadas; um pool de 500 só acrescenta contenção.

O problema nos microsserviços: 20 pods × um pool de 50 = 1.000 conexões num único primário. Um pooler de conexões como o PgBouncer (ou o RDS Proxy, ou o Cloud SQL Auth Proxy com pooling) fica entre eles, multiplexando milhares de conexões de clientes em poucas dezenas de conexões com o servidor. No modo transaction pooling, uma conexão com o servidor é emprestada por transação, então o estado de sessão (SET, tabelas temporárias, alguns usos de prepared statements) não pode vazar de uma transação para outra. Você vai medir isso no Hands-on 6.

No código do BoxOffice: o PgBouncer na frente do PostgreSQL (lab 6)

No Hands-on 6 o inventory se conecta pelo PgBouncer em modo transação, e o PostgreSQL vê no máximo 20 conexões de servidor vindas dele, não importa quantas conexões de cliente cheguem. Numa execução de teste durante a venda relâmpago, o pg_stat_activity mostrou cerca de 60 conexões da aplicação (30 das três réplicas do catalog, 10 do auth, 20 do PgBouncer). Com max_connections reduzido a 80 e um pool de 40 no inventory, a mesma pilha falha com too many clients already quando o inventory se conecta direto, e continua funcionando pelo pooler, que transforma "clientes demais" em fila. As migrações continuam indo direto ao primário: o Flyway usa locks de sessão, que o pooling por transação não mantém.

infra/pgbouncer/pgbouncer.ini (transaction pooling)
; transaction pooling: a server connection is lent for ONE transaction, then returned
pool_mode = transaction
; real connections to PostgreSQL per (database, user) pair
default_pool_size = 20
; client connections PgBouncer accepts (all services and replicas together)
max_client_conn = 1000

Failover: quando o líder morre

Se um seguidor morre, ele se reconecta e alcança o líder a partir do log. Se o líder morre, o sistema precisa fazer failover:

sequenceDiagram
    participant M as Gerenciador de failover (ex.: Patroni + etcd)
    participant P as Primário antigo
    participant S as Standby
    participant A as Aplicações
    M->>P: health check
    Note over P: sem resposta por 10 s
    M->>S: promove a primário
    S-->>M: promovido (nova timeline)
    M->>A: atualiza DNS / IP virtual / proxy
    A->>S: escritas retomadas
    Note over P: se voltar, precisa ser isolado (fencing)<br/>e reentrar como seguidor

Cada passo esconde um problema difícil:

  • A detecção usa timeouts. Curto demais: uma pausa de GC ou um soluço de rede dispara um failover desnecessário. Longo demais: minutos sem escritas.
  • Escritas perdidas. Com replicação assíncrona, as escritas confirmadas pelo líder antigo mas ainda não enviadas desaparecem. Se eram reservas de assentos, o novo primário pode vender os mesmos assentos de novo.
  • Split brain. Se o líder antigo estava apenas particionado, e não morto, dois nós podem aceitar escritas. Os sistemas evitam isso com consenso sobre quem é o líder (o Patroni guarda a chave do líder no etcd; veja Consenso) e com fencing: o líder antigo é isolado (STONITH, "shoot the other node in the head") ou suas escritas são rejeitadas por fencing tokens.
  • Os clientes precisam acompanhar. Por DNS, IP virtual, proxy ou um driver que conhece a topologia.

Os serviços gerenciados empacotam tudo isso — Amazon RDS Multi-AZ com standby síncrono, Aurora com armazenamento em quorum, alta disponibilidade do Cloud SQL, HA com redundância de zona no Azure — com failovers que costumam levar de dezenas de segundos a alguns minutos; a pergunta de design para você é o que a aplicação faz enquanto isso (novas tentativas com backoff, veja Confiabilidade).

No código do BoxOffice: um failover à mão, e o antigo primário voltando (lab 6)

O Hands-on 6 — Failover percorre o diagrama acima sem nada automatizando. A reserva de um fã responde 201, o primário é derrubado antes que a réplica assíncrona a receba, a réplica é promovida com pg_promote() e o nome db passa para ela (um alias de rede do Docker, a versão local de um endpoint DNS): todo serviço se reconecta em cerca de 15 s sem mudar configuração, e a reserva dá 404 — a escrita perdida da lista acima. Ligar o antigo primário de novo dá dois primários que discordam sobre o mesmo assento. Depois de isolá-lo, este script o traz de volta como seguidor: o pg_rewind descarta a história que o novo primário nunca teve, o que só funciona porque o primário roda com wal_log_hints = on.

infra/postgres/rejoin-as-replica.sh (rewind, then follow the new primary)
# pg_rewind copies back only the blocks that changed since the timelines split,
# discarding the writes the old primary made that never reached the new one.
pg_rewind --target-pgdata="$PGDATA" --progress \
  --source-server="host=$NEW_PRIMARY user=$POSTGRES_USER password=$POSTGRES_PASSWORD dbname=$POSTGRES_DB"

# Same as pg_basebackup -R: start as a standby, streaming from the new primary.
touch "$PGDATA/standby.signal"
echo "primary_conninfo = 'host=$NEW_PRIMARY user=replicator password=${REPLICATION_PASSWORD:-replicator}'" \
  >> "$PGDATA/postgresql.auto.conf"

Experimente. O cenário db-failover é um checkout de venda relâmpago num cluster com um primário e um seguidor assíncrono: 1.500 req/s, metade escritas. Em t = 90 s o primário morre. Execute-o e leia o relatório: com a semente padrão cerca de 1.600 escritas se perdem (confirmadas pelo primário antigo, nunca enviadas ao seguidor) e a disponibilidade cai para cerca de 90,6%, porque toda escrita falha durante os 30 s de failover. Selecione orders-db e mude Replicação para sync: as escritas perdidas caem para 0, mas a disponibilidade não se mexe. Volte para async e ponha o Tempo de failover em 5 s: a disponibilidade sobe para cerca de 98%, e as escritas voltam a se perder. Os dois controles respondem a perguntas diferentes: o modo de replicação define quanto você perde (o RPO), a detecção e a promoção definem quanto tempo você fica fora (o RTO). A solução possível (abra no playground completo ↗) combina replicação por quórum com 2 seguidores, 2 shards e failover de 5 s: 0 escritas perdidas, cerca de 99,2% de disponibilidade.


Replicação multilíder

Na replicação multilíder (multi-leader, multi-primary, ativo-ativo), vários nós aceitam escritas e replicam entre si de forma assíncrona. Motivos típicos: um líder por região, para que as escritas sejam locais; clientes offline-first (um app móvel ou um editor colaborativo é, na prática, um líder); um segundo data center que precisa aceitar escritas durante uma partição.

O preço são os conflitos de escrita: dois líderes aceitam escritas diferentes no mesmo registro ao mesmo tempo.

Estratégia de resolução Como funciona Risco
Last write wins (LWW) mantém a escrita com o maior timestamp descarta silenciosamente a outra escrita; os relógios derivam (veja Tempo e Relógios)
Merge merge específico da aplicação (união de tags, soma de contadores) só funciona para dados mescláveis
CRDTs tipos de dados replicados livres de conflito, que se mesclam automaticamente (contadores, conjuntos, texto) tipos de dados limitados; mais metadados
Evitar conflitos direcionar todas as escritas de um registro para um líder "de origem" volta ao líder único, por registro

BoxOffice: onde o multilíder cabe — e onde não pode entrar

Descrições de eventos editadas pela equipe das casas de show em duas regiões: os conflitos são raros e LWW ou uma regra "o último editor vence, mantendo o histórico" é aceitável.

Inventário de assentos: um conflito é uma venda dupla. Duas regiões aceitando reservas para o mesmo assento é exatamente o bug da página de transações, em escala continental. O inventário continua com líder único por evento — se a BoxOffice se expandir para outro continente, o inventário de cada evento vive numa única região "de origem".


Replicação sem líder

O artigo do Dynamo da Amazon (2007)3 popularizou um terceiro design, usado por Cassandra, ScyllaDB e Riak: nenhum líder. O cliente (ou um nó coordenador) envia cada escrita para todas as N réplicas de uma chave e a considera bem-sucedida quando W delas confirmam; lê de R réplicas e fica com a versão mais nova.

Experimente

O simulador começa na configuração usual: N = 3, W = 2, R = 2, réplica C fora do ar. Dê play: toda escrita cai em A e B, toda leitura consulta A e B, e nenhuma leitura vem desatualizada. Ponha W = 1 e R = 1: agora cerca de metade das leituras perde a última escrita (o quadro calcula C(U − W, R) / C(U, R): 50% com 2 réplicas vivas, 67% quando C volta). Volte a W = 2, R = 2 e clique em C para religá-la: ela volta com uma versão antiga, e mesmo assim as leituras por quórum continuam atualizadas; ligue o read repair e veja C se atualizar. Por fim, derrube também B: com uma réplica viva, escritas e leituras falham, porque o coordenador não consegue juntar 2 respostas.

Se R + W > N, todo conjunto de leitura se sobrepõe a todo conjunto de escrita em pelo menos uma réplica, então uma leitura vê a última escrita confirmada (com ressalvas sob escritas concorrentes e falhas — quoruns sozinhos não são linearizáveis). Com N = 3:

W R R + W > N? Comportamento
1 1 não o mais rápido, maior disponibilidade; as leituras podem estar desatualizadas
2 2 sim a configuração "quorum" usual; tolera 1 nó fora do ar para leituras e escritas
3 1 sim leituras rápidas; um único nó fora do ar bloqueia as escritas
1 3 sim escritas rápidas; um único nó fora do ar bloqueia as leituras

As escritas perdidas são reparadas por read repair (uma leitura que vê uma versão antiga grava a nova de volta), hinted handoff (um vizinho guarda as escritas de um nó fora do ar e as reaplica depois; com um sloppy quorum, esses guardiões temporários até contam para W) e anti-entropia (comparação em segundo plano entre réplicas com árvores de Merkle). O Cassandra expõe a escolha por consulta como níveis de consistência ONE, QUORUM, LOCAL_QUORUM, ALL.


Comparando os designs

Líder único Multilíder Sem líder
Escritas aceitas por um nó vários líderes quaisquer W de N réplicas
Conflitos de escrita nenhum (uma ordem) sim — precisam ser resolvidos sim — versões, LWW ou siblings
Failover necessário, delicado outro líder continua desnecessário
Read-your-writes por truques de roteamento por líder com R + W > N, quase sempre
Bom para a maior parte do OLTP: pedidos, inventário, usuários escritas multirregião, clientes offline alta disponibilidade de escrita, séries temporais, carrinhos
Exemplos PostgreSQL, MySQL, RDS, MongoDB MySQL group replication (multi-primary), CouchDB, editores colaborativos Cassandra, ScyllaDB, Riak, DynamoDB internamente

BoxOffice: o plano de replicação

  • catalog: primário + 2 réplicas de leitura assíncronas; as páginas de eventos leem das réplicas (um segundo de atraso é aceitável), atrás do cache.
  • inventory: primário + um standby síncrono em outra zona de disponibilidade — uma reserva perdida é uma venda dupla, então nenhuma escrita confirmada pode se perder no failover. Todas as reservas e confirmações vão para o primário; o mapa de assentos pode vir de uma réplica ou do cache.
  • order: primário + réplica assíncrona para relatórios; Meus ingressos lê do primário por 30 segundos depois de uma compra (timestamp na sessão) e da réplica no resto do tempo.
  • Alerta de atraso: acionar o plantão se alguma réplica ficar mais de 5 s atrás por 2 minutos.

Na entrevista

  • "Adicionar réplicas de leitura" nunca é a resposta completa: acrescente "…e eu trato o atraso de replicação: leituras que precisam refletir as escritas do próprio usuário vão para o primário".
  • Diga qual é a escolha de durabilidade: assíncrona (pode perder as últimas escritas no failover) vs um standby síncrono (nenhuma perda, + uma ida e volta por commit). Amarre isso aos dados: dinheiro e assentos → standby síncrono.
  • Para bancos sem líder, escreva R + W > N e diga o que isso garante e o que não garante (não é uma transação).
  • Mencione o tempo de failover e o que os clientes fazem enquanto isso: novas tentativas com backoff, escritas idempotentes.

Boas práticas

  • Deixe o destino da leitura explícito no código, por transação — quem lê o código precisa ver quais consultas podem estar desatualizadas. Na BoxOffice: @Transactional(readOnly = true) vai para a réplica pelo DataSourceConfig (lab 6, quadro acima).
  • Dê ao driver todos os hosts e deixe-o encontrar o primário — com jdbc:postgresql://db1,db2/boxoffice?targetServerType=primary a aplicação se reconecta ao novo primário depois de um failover, sem novo deploy.
  • Rode as migrações direto no primário — mudanças de schema precisam de uma sessão de verdade, não de uma transação emprestada pelo pool. Na BoxOffice: DB_MIGRATION_HOST=db enquanto a aplicação passa pelo PgBouncer (lab 6).
  • Ensaie o failover periodicamente — uma troca planejada a cada trimestre mede o seu RTO real e prova que todo cliente segue o novo primário.
  • Mantenha relatórios longos fora do standby de failover — uma consulta longa num hot standby conflita com o replay e o atrasa, então o standby com que você conta para o failover fica para trás; dê aos relatórios sua própria réplica.

Armadilhas comuns

  • Ler as próprias escritas de uma réplica — a página Meus ingressos vazia.
  • Usar réplicas para tomar decisões: verificar a disponibilidade do assento numa réplica e depois escrever no primário reintroduz a corrida check-then-act, com atraso extra.
  • Réplicas como backup: um DELETE sem WHERE é replicado em milissegundos. Backups e recuperação para um ponto no tempo são outra coisa (veja Recuperação de Desastres).
  • Atraso não monitorado: uma réplica horas atrás continua respondendo consultas — com dados de horas atrás.
  • Conexões demais vindas de muitos pods com pools grandes; use um pooler e dimensione os pools com a Lei de Little.
  • Failover automático sem fencing: split brain é pior do que indisponibilidade.

Como se conecta

flowchart LR
    WHY["por que replicar: disponibilidade,<br/>escala de leitura, latência"] --> SL["líder único"]
    SL --> SYNC["síncrona vs assíncrona<br/>durabilidade vs latência do commit"]
    SL --> LAG["atraso de replicação"]
    LAG --> RYW["ler as próprias escritas,<br/>leituras monotônicas"]
    RYW -.-> CM["modelos de consistência<br/>→ Distribuídos"]
    SL --> FO["failover: Patroni + etcd<br/>→ Consenso distribuído"]
    SL --> POOL["pool de conexões<br/>PgBouncer"]
    WHY --> ML["multilíder<br/>conflitos, LWW"]
    WHY --> LL["sem líder<br/>R + W > N"]
    LL --> PART["armazenamentos particionados<br/>→ Particionamento"]

Componentes da paleta: sql (um primário com réplicas de leitura; um standby síncrono para assentos e dinheiro) · nosql (quóruns sem líder: Cassandra, DynamoDB) · app (roteia leituras e escritas, faz retry com backoff durante um failover) · cache (fica na frente das réplicas do catálogo) · dns (um jeito de os clientes seguirem o novo líder). Cenários: db-replicas, db-failover, multi-region. Continua em: Particionamento, Distribuídos — consistência, Distribuídos — consenso, Confiabilidade — recuperação de desastres, Hands-on 6 (réplica de leitura, PgBouncer), Exercício: cache + banco.



  1. KLEPPMANN, M.; RICCOMINI, C. Designing Data-Intensive Applications. 2. ed. O'Reilly. Capítulo "Replication". ↩

  2. TERRY, D. B. et al. Session Guarantees for Weakly Consistent Replicated Data. Proceedings of the 3rd International Conference on Parallel and Distributed Information Systems (PDIS), 1994. ↩

  3. DeCANDIA, G. et al. Dynamo: Amazon's Highly Available Key-value Store. ACM SOSP, 2007. ↩

  4. VERBITSKI, A. et al. Amazon Aurora: Design Considerations for High Throughput Cloud-Native Relational Databases. ACM SIGMOD, 2017. ↩

  5. PostgreSQL Global Development Group. High Availability, Load Balancing, and Replication. Documentação do PostgreSQL. ↩