CAP, PACELC e os modelos de consistência
- sistemas-distribuidos
- arquitetura
- banco-de-dados
Publicado em por Gabriel Dias · 5 min de leitura · Engenharia
"Pelo teorema CAP, a gente escolheu AP."
Essa frase aparece em toda discussão de arquitetura, e na maior parte das vezes quem a diz não sabe o que está dizendo. Não por má-fé, mas porque a versão popularizada do teorema é uma simplificação que perdeu o sentido no caminho.
O enunciado correto
A versão popular é "escolha dois entre consistência, disponibilidade e tolerância a partição". Ela sugere que você senta numa mesa e escolhe.
Mas partição de rede não é uma escolha. É uma coisa que acontece com você. Cabo cortado, switch com defeito, zona de disponibilidade isolada, regra de firewall errada.
O enunciado correto é:
Quando existe uma partição de rede, você precisa escolher entre continuar respondendo com risco de dado divergente, ou parar de responder para preservar a consistência.
Só isso. E note o "quando": fora da partição, o CAP não diz absolutamente nada.
Sistema CP: durante a partição, o lado minoritário recusa escrita. Você perde disponibilidade e mantém a correção. É o comportamento de etcd, ZooKeeper, Consul, e de um Postgres com replicação síncrona.
Sistema AP: durante a partição, os dois lados continuam aceitando escrita, e depois alguém reconcilia. Você mantém disponibilidade e aceita divergência temporária. É o comportamento de Cassandra e DynamoDB em configurações permissivas.
E vale dizer: a maioria dos sistemas reais é configurável entre os dois, e a configuração importa mais que o rótulo do produto.
PACELC: a parte que você usa todo dia
Daniel Abadi propôs uma extensão que deveria ser mais famosa que o original:
Se tem Partição, escolha entre Disponibilidade e Consistência. Else, escolha entre Latência e Consistência.
A segunda metade é a que governa o seu cotidiano, porque partição é rara e o "else" é o resto do tempo.
- Toda vez que você lê de uma réplica em vez do primário, você trocou consistência por latência.
- Toda vez que usa replicação assíncrona, idem.
- Toda vez que coloca cache na frente, idem.
Nenhuma partição envolvida. Só decisões de projeto que quase nunca são registradas como decisões, e que produzem os bugs mais confusos de depurar.
A escada da consistência
Consistência não é sim ou não. É uma escada, do mais forte ao mais fraco.
Linearizável. Tudo se comporta como se existisse uma cópia única e as operações acontecessem uma de cada vez, na ordem real do relógio. É o mais intuitivo e o mais caro: exige coordenação a cada operação, o que significa idas e voltas na rede.
Sequencial. Todo mundo vê a mesma ordem, mas ela não precisa bater com o tempo real.
Causal. Se A causou B, todo mundo vê A antes de B. Operações sem relação causal podem aparecer em ordens diferentes para observadores diferentes. É um ponto de equilíbrio muito bom, e é o que sistemas de mensagem e de feed geralmente querem: você nunca vê a resposta antes da pergunta.
Eventual. Se você parar de escrever, em algum momento todos convergem. Não diz quando. É o mais barato e o mais fácil de vender errado.
As garantias de sessão: o atalho prático
Aqui está o conceito que resolve a maior parte dos problemas reais sem pagar o preço da consistência forte.
Em vez de garantir uma propriedade global do sistema, você garante propriedades para uma sessão de usuário:
Leia sua própria escrita. O usuário sempre vê o que ele mesmo acabou de gravar.
Leitura monotônica. O usuário nunca vê o relógio andar para trás: se ele já viu um dado novo, não pode ver o antigo depois.
Escrita monotônica. As escritas dele são aplicadas na ordem em que ele as fez.
Por que isso importa: quase todo bug de consistência que chega ao suporte é violação de uma dessas três. O usuário salva o perfil, é redirecionado, a tela lê da réplica, mostra o dado antigo, e ele salva de novo.
Como implementar "leia sua própria escrita"
Três abordagens, da mais simples à mais correta:
- Janela de primário. Depois de uma escrita, leia do primário por alguns segundos para aquele usuário. Marque na sessão ou num cookie. Resolve noventa por cento dos casos com pouquíssimo código.
- Posição do log. Guarde na sessão a posição do log de replicação no momento da escrita, e só aceite ler de uma réplica que já passou daquele ponto. Mais correto, mais trabalhoso, e exige suporte do driver ou do proxy.
- Rotas críticas sempre no primário. Decisão explícita, documentada, para um conjunto pequeno de rotas.
A pior abordagem, que é a mais comum, é não fazer nada e tratar cada reclamação como caso isolado.
O que perguntar na próxima reunião
Troque "forte ou eventual?" por três perguntas melhores:
Qual garantia o usuário precisa perceber? Quase sempre a resposta é "ele precisa ver a própria alteração".
Qual é o lag real da nossa réplica, no p99, na última semana? Se ninguém souber, esse é o primeiro trabalho.
O que acontece durante uma partição, e nós já testamos isso? A resposta honesta na maioria dos times é "não sabemos", e descobrir num teste controlado é bem melhor que descobrir num incidente.
Um fechamento honesto
A consistência forte não é sempre a resposta certa, e a eventual não é sempre suficiente. O que separa uma boa decisão de uma ruim não é o nível escolhido, e sim ter escolhido conscientemente e ter escrito por quê.
Um ADR de meia página respondendo "por que aceitamos leitura eventual nesta rota, e como mitigamos o efeito no usuário" vale mais do que qualquer discussão sobre siglas.
Um e-mail por semana, sem enrolação
O que eu aprendi construindo software e IA na semana, em texto curto e direto, sem newsletter de 3 mil palavras. Cancele quando quiser.