DevGuideTK
← Trilha Backend

Teorema CAP e consistência

A escolha obrigatória entre consistência e disponibilidade quando a rede se divide, e os níveis de consistência entre os dois extremos.

O CAP não é uma escolha feita uma vez para o sistema inteiro. Os bancos modernos permitem ajustar a consistência por operação, e um mesmo sistema costuma tratar o saldo de um jeito e o contador de curtidas de outro.

Como os sistemas se posicionam

SistemaComportamento padrãoO que pode ser ajustado
PostgreSQL / MySQL com um principalConsistente no principal; réplicas assíncronas podem atrasarReplicação síncrona; de onde cada leitura é feita
etcd, ZooKeeper, ConsulConsistentes (CP): sem maioria de nós, recusam escritasPouco; foram feitos para isso
Cassandra, ScyllaDBDisponíveis (AP)Nível de consistência por consulta: ONE, QUORUM, ALL
DynamoDBLeituras eventuais por padrãoLeitura fortemente consistente por consulta, com custo maior
MongoDBLeituras no principalWrite concern e read concern por operação; leitura em secundários
Redis com réplicasReplicação assíncronaPouco; uma falha do principal pode perder escritas recentes

A tabela mostra que "o banco X é CP" ou "é AP" é uma simplificação. O que importa é como ele está configurado e de onde cada leitura é feita.

Replicação síncrona ou assíncrona

Num banco relacional com réplicas, a decisão principal é se o principal espera as réplicas antes de confirmar uma escrita.

AssíncronaSíncrona
Confirmação da escritaAssim que gravada no principalSó depois que a réplica também gravou
Latência da escritaMenorMaior: soma a ida e volta até a réplica
Se o principal cairEscritas recentes podem se perderNenhuma escrita confirmada se perde
Leitura na réplicaPode estar atrasadaAinda pode estar atrasada na aplicação, conforme a configuração
Se a réplica cairNada muda para as escritasAs escritas param, salvo se houver outra réplica síncrona

No PostgreSQL, a replicação síncrona é configurada em synchronous_standby_names, e o nível remote_apply em synchronous_commit faz o principal esperar até a réplica aplicar a alteração, e não só gravá-la. É o que garante que uma leitura na réplica, logo depois da confirmação, veja o dado novo. Cada nível mais forte custa mais latência em todas as escritas.

Quórum: consistência ajustável

Bancos com várias cópias equivalentes, como Cassandra e DynamoDB, usam quórum. Com N cópias de cada dado:

  • uma escrita é confirmada quando W cópias gravaram;
  • uma leitura consulta R cópias e fica com a versão mais recente.

Se R + W > N, toda leitura consulta pelo menos uma cópia que recebeu a última escrita.

Configuração com N = 3R + WComportamento
W = 1, R = 12O mais rápido e disponível; leituras podem ser velhas
W = 2, R = 2 (quórum)4Leituras veem a última escrita; tolera uma cópia fora do ar
W = 3, R = 14Leituras rápidas e atualizadas; uma cópia fora do ar impede escritas

Conflitos: quando os dois lados aceitam escritas

Um sistema que escolhe disponibilidade durante uma partição aceita escritas nos dois lados. Quando a rede volta, as cópias têm versões diferentes do mesmo dado, e alguém precisa decidir qual vale.

EstratégiaComo funcionaRisco
A última escrita venceFica a versão com o horário mais recentePerde escritas em silêncio; depende de relógios sincronizados
Vetores de versãoDetecta escritas concorrentes e guarda as duas versõesA aplicação precisa saber juntar as versões
Tipos de dados que se juntam sozinhos (CRDTs)Estruturas projetadas para combinar versões sem conflito: contadores, conjuntosSó servem para certos tipos de dado
Evitar o conflitoCada dado tem um único lugar onde pode ser escritoPerde disponibilidade de escrita nesse dado durante a partição

A "última escrita vence" é o padrão de vários bancos e é aceitável para dados em que perder uma alteração rara não importa. Para dados que não podem perder escritas, ela é uma armadilha.

Ler as próprias escritas

Na maioria dos sistemas com réplicas, a consistência forte para todos é cara demais, e a eventual é fraca demais na tela do próprio usuário. Ler as próprias escritas resolve o caso mais visível.

TécnicaComo funcionaLimite
Ler do principal logo depois de escreverPor alguns segundos depois de uma escrita, as leituras daquele usuário vão para o principalO prazo é um palpite: se a réplica atrasar mais, o problema volta
Token de posiçãoA escrita devolve a posição no registro de alterações do principal; a leitura só usa a réplica se ela já chegou láExige guardar o token na sessão ou no cliente
Ler sempre do principal o que o usuário pode editarTelas de edição e perfil leem do principal; listagens públicas, da réplicaMais carga no principal
Afinidade de sessãoO usuário é atendido sempre pela mesma réplicaNão resolve a primeira leitura depois da escrita, que foi para o principal

O token de posição é a técnica mais precisa, e o nível Implementação a mostra funcionando no PostgreSQL.

Como decidir por tipo de dado

DadoConsistência necessáriaAbordagem
Saldo, estoque na compra, reserva de assentoForteLer e escrever no principal, com transação
Perfil, configurações, endereçoLer as próprias escritasToken de posição ou leitura no principal nas telas de edição
Lista de pedidos do próprio usuárioLer as próprias escritasIdem
Catálogo, páginas públicasEventual, com atraso pequenoRéplica e cache
Contadores, curtidas, visualizaçõesEventualRéplica, cache, até agregação em lote
Relatórios e análisesEventual, atraso de minutos ou horasRéplica dedicada ou armazém analítico

Resumo da decisão

  1. Falhas de rede vão acontecer: decida, para cada dado, se prefere erro ou dado velho.
  2. Decisões de negócio (saldo, estoque, aprovação) leem do principal.
  3. O que o usuário acabou de salvar precisa aparecer para ele: ler as próprias escritas.
  4. O resto vai para réplicas e caches, com atraso aceitável definido.
  5. Escritas concorrentes em vários lugares exigem uma estratégia de conflito escolhida de propósito.

O próximo nível mostra o token de posição no PostgreSQL nas cinco linguagens, com o erro que torna o token inútil.

Prompts para aprofundar

Ao abrir no ChatGPT ou no Claude, o prompt é enviado ao serviço escolhido pela URL e também fica copiado na sua área de transferência.

  • Definir a consistência de cada dado

    Quero definir, para cada tipo de dado do meu sistema, qual consistência é necessária e de onde ele deve ser lido.
    
    Peça que eu liste os principais dados e telas do sistema. Se eu não tiver uma lista, use uma loja virtual como exemplo: saldo de pontos, estoque, carrinho, perfil, histórico de pedidos, catálogo, avaliações e relatórios.
    
    Para cada dado:
    1. Pergunte o que acontece se a leitura vier com alguns segundos de atraso, e com alguns minutos.
    2. Proponha o nível de consistência mínimo.
    3. Proponha de onde ler: principal, réplica, cache, ou réplica com garantia de ler as próprias escritas.
    
    Termine com uma tabela que eu possa guardar na documentação do projeto, e com os riscos de alguém mover uma dessas leituras para outro lugar sem perceber.
  • Quórum e consistência ajustável

    Quero entender como bancos com várias cópias equivalentes, como Cassandra e DynamoDB, permitem ajustar a consistência.
    
    Explique:
    1. O que são N, W e R, e por que R + W > N garante ler a última escrita.
    2. Uma tabela com combinações para N = 3 e o que cada uma tolera de falha.
    3. Como isso aparece na prática: níveis de consistência por consulta no Cassandra e leitura fortemente consistente no DynamoDB, com o custo de cada opção.
    4. O que acontece com escritas concorrentes nesses sistemas e as estratégias de conflito: última escrita vence, vetores de versão e CRDTs.
    
    Depois me apresente três casos de uso e pergunte qual configuração eu usaria em cada um. Corrija as minhas respostas.
  • Garantir que o usuário veja o que salvou

    Meu sistema usa réplicas de leitura, e os usuários às vezes não veem o que acabaram de salvar. Quero escolher a melhor forma de resolver.
    
    Pergunte qual banco, linguagem e framework eu uso, e como as leituras são distribuídas hoje.
    
    Compare, para o meu caso:
    1. Ler do principal por alguns segundos depois de cada escrita do usuário.
    2. Token de posição do registro de alterações, conferido na réplica.
    3. Ler sempre do principal nas telas em que o usuário edita dados.
    4. Afinidade de sessão.
    5. Replicação síncrona com aplicação na réplica antes da confirmação.
    
    Para cada opção, diga o que ela garante, o que custa e onde falha. Termine com a recomendação e o roteiro de implementação.