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
| Sistema | Comportamento padrão | O que pode ser ajustado |
|---|---|---|
| PostgreSQL / MySQL com um principal | Consistente no principal; réplicas assíncronas podem atrasar | Replicação síncrona; de onde cada leitura é feita |
| etcd, ZooKeeper, Consul | Consistentes (CP): sem maioria de nós, recusam escritas | Pouco; foram feitos para isso |
| Cassandra, ScyllaDB | Disponíveis (AP) | Nível de consistência por consulta: ONE, QUORUM, ALL |
| DynamoDB | Leituras eventuais por padrão | Leitura fortemente consistente por consulta, com custo maior |
| MongoDB | Leituras no principal | Write concern e read concern por operação; leitura em secundários |
| Redis com réplicas | Replicação assíncrona | Pouco; 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íncrona | Síncrona | |
|---|---|---|
| Confirmação da escrita | Assim que gravada no principal | Só depois que a réplica também gravou |
| Latência da escrita | Menor | Maior: soma a ida e volta até a réplica |
| Se o principal cair | Escritas recentes podem se perder | Nenhuma escrita confirmada se perde |
| Leitura na réplica | Pode estar atrasada | Ainda pode estar atrasada na aplicação, conforme a configuração |
| Se a réplica cair | Nada muda para as escritas | As 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
Wcópias gravaram; - uma leitura consulta
Rcó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 = 3 | R + W | Comportamento |
|---|---|---|
| W = 1, R = 1 | 2 | O mais rápido e disponível; leituras podem ser velhas |
| W = 2, R = 2 (quórum) | 4 | Leituras veem a última escrita; tolera uma cópia fora do ar |
| W = 3, R = 1 | 4 | Leituras 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égia | Como funciona | Risco |
|---|---|---|
| A última escrita vence | Fica a versão com o horário mais recente | Perde escritas em silêncio; depende de relógios sincronizados |
| Vetores de versão | Detecta escritas concorrentes e guarda as duas versões | A 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, conjuntos | Só servem para certos tipos de dado |
| Evitar o conflito | Cada dado tem um único lugar onde pode ser escrito | Perde 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écnica | Como funciona | Limite |
|---|---|---|
| Ler do principal logo depois de escrever | Por alguns segundos depois de uma escrita, as leituras daquele usuário vão para o principal | O prazo é um palpite: se a réplica atrasar mais, o problema volta |
| Token de posição | A 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 editar | Telas de edição e perfil leem do principal; listagens públicas, da réplica | Mais carga no principal |
| Afinidade de sessão | O usuário é atendido sempre pela mesma réplica | Nã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
| Dado | Consistência necessária | Abordagem |
|---|---|---|
| Saldo, estoque na compra, reserva de assento | Forte | Ler e escrever no principal, com transação |
| Perfil, configurações, endereço | Ler as próprias escritas | Token de posição ou leitura no principal nas telas de edição |
| Lista de pedidos do próprio usuário | Ler as próprias escritas | Idem |
| Catálogo, páginas públicas | Eventual, com atraso pequeno | Réplica e cache |
| Contadores, curtidas, visualizações | Eventual | Réplica, cache, até agregação em lote |
| Relatórios e análises | Eventual, atraso de minutos ou horas | Réplica dedicada ou armazém analítico |
Resumo da decisão
- Falhas de rede vão acontecer: decida, para cada dado, se prefere erro ou dado velho.
- Decisões de negócio (saldo, estoque, aprovação) leem do principal.
- O que o usuário acabou de salvar precisa aparecer para ele: ler as próprias escritas.
- O resto vai para réplicas e caches, com atraso aceitável definido.
- 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.