Consistência eventual
Como projetar telas e regras de negócio quando a leitura pode vir desatualizada.
As decisões com dados eventualmente consistentes são quatro: quais garantias de leitura oferecer, como resolver conflitos de gravação, como desenhar as telas e onde exigir a fonte da verdade.
Garantias de leitura
| Garantia | Como implementar | Custo |
|---|---|---|
| Ler o que gravou | Guardar o LSN, a versão ou o horário da última gravação do usuário, e só ler de uma cópia que já passou dele; ou ler do principal por alguns segundos depois de gravar | Algumas leituras voltam ao principal |
| Leituras monotônicas | Afinidade: o mesmo usuário sempre na mesma réplica; ou guardar a versão mais nova já vista e não aceitar uma mais velha | Afinidade desequilibra a carga se as réplicas forem poucas |
| Consistência causal | Levar junto com cada dado a versão das dependências (um vetor de versões, ou o LSN) | Mais metadados em cada mensagem |
| Ler sempre a fonte | Ler do principal | Sem os ganhos das réplicas e do cache |
No teste do nível Conceito, sem afinidade, o mesmo pedido alternou entre enviado e pago a cada leitura; com afinidade, cada usuário viu um valor estável. A afinidade por usuário costuma ser o mínimo para qualquer sistema que leia de réplicas.
Como resolver conflitos de gravação
Só existem conflitos quando mais de uma cópia aceita gravações. Com um banco principal, eles não aparecem. Quando aparecem:
| Estratégia | Como funciona | Perde dados? |
|---|---|---|
| Evitar: um dono por dado | Cada registro só é gravado numa região ou num nó, por exemplo pelo cliente | Não, mas exige roteamento |
| A última gravação vence | Fica a gravação de maior horário | Sim: a simultânea e a de relógio atrasado (o teste ficou com 2 em vez de 10) |
| Versão e conferência | A gravação leva a versão que leu; se outra gravou antes, recusa e pede para o usuário refazer | Não, mas o usuário vê o conflito |
| Guardar as duas versões | O sistema mantém os valores em conflito e alguém escolhe depois (o carrinho mescla os itens) | Não |
| CRDT | A estrutura mescla sozinha, sempre com o mesmo resultado | Não, para as operações que a estrutura cobre |
"A última gravação vence" é aceitável para dados em que perder uma atualização não importa (a hora do último acesso). Para todo o resto, prefira evitar o conflito com um dono por dado, ou detectar com versão.
Qual CRDT para cada caso
| Dado | CRDT |
|---|---|
| Contador que só cresce (visualizações) | G-Counter |
| Contador que cresce e diminui (curtidas, com descurtir) | PN-Counter: dois G-Counters, um para somas e outro para subtrações |
| Conjunto com adições e remoções (etiquetas, membros) | OR-Set |
| Um valor que pode ser sobrescrito | LWW-Register, ciente de que perde gravações simultâneas, ou MV-Register, que guarda todas |
| Texto editado por várias pessoas | CRDTs de sequência, nas bibliotecas Automerge e Yjs |
Como desenhar as telas
| Padrão | Como funciona | Quando usar |
|---|---|---|
| Atualização otimista | A tela mostra a mudança na hora e confirma (ou desfaz) quando o servidor responder | Curtidas, marcar como lido, mover cartões |
| Estado intermediário explícito | "Pagamento em processamento" até o evento de confirmação chegar | Processos assíncronos, com filas e serviços externos |
| Mostrar a idade do dado | "Atualizado há 2 minutos" | Painéis e relatórios lidos de cópias |
| Devolver o resultado da gravação | A resposta da gravação traz o registro completo, e a tela usa esse registro em vez de buscar de novo | Formulários de edição |
| Recarregar com a versão | A tela pede "a versão 7 ou mais nova" e espera um pouco se a cópia ainda não chegou lá | Telas que leem projeções alimentadas por eventos |
O quarto padrão resolve muita coisa com pouco: se a resposta do PUT já traz o endereço atualizado, a tela não precisa ler de nenhuma réplica logo depois de salvar.
Onde exigir a fonte da verdade
A regra: cópias para mostrar, fonte para decidir. Saldo, estoque disponível para venda, limites, permissões e qualquer regra que não pode ser violada leem o dado na fonte, com as proteções de concorrência do tópico de níveis de isolamento. Telas, listagens, contagens e relatórios podem ler as cópias.
Para processos que atravessam vários serviços, cada um com seus dados, a consistência entre eles também é eventual. O caminho são eventos com entrega garantida e consumidores idempotentes, e sagas com compensação quando um passo falha, como mostram os tópicos de garantias de entrega e de saga e outbox.
Resumo da decisão
- Afinidade por usuário em toda leitura de réplicas, para o tempo não voltar.
- Ler o que gravou nas telas que mostram o que o usuário acabou de alterar, ou devolver o registro na resposta da gravação.
- Um dono por dado para evitar conflitos; versão para detectá-los; CRDTs onde há gravações independentes de verdade.
- "A última gravação vence" só onde perder uma atualização não importa.
- Telas honestas: atualização otimista, estados intermediários e a idade dos dados.
- Decisões na fonte da verdade; cópias só para mostrar.
O próximo nível mostra o contador que converge nas cinco linguagens, comparado com "a última gravação vence".
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.
Projetar a consistência do meu sistema
Meu sistema tem réplicas, cache ou serviços que trocam eventos, e quero projetar a consistência de forma consciente. Pergunte quais cópias de dados existem, como elas são atualizadas e quais telas e regras de negócio leem de cada uma. Para cada leitura, diga se ela pode usar uma cópia, qual garantia precisa (ler o que gravou, monotônica) e como implementá-la. Para cada decisão de negócio, diga se ela precisa da fonte da verdade.
Telas para dados eventualmente consistentes
Quero que as telas do meu sistema lidem bem com dados que podem estar atrasados. Pergunte quais telas existem, quais processos são assíncronos e qual framework de front-end eu uso. Proponha, tela por tela, o padrão adequado: atualização otimista, estado intermediário explícito, idade do dado, devolver o registro na resposta da gravação ou recarregar com a versão. Mostre o código de um deles no meu framework.
Escolher como resolver conflitos
Meu sistema vai aceitar gravações em mais de um lugar (várias regiões, ou aplicativo que funciona sem rede). Pergunte quais dados podem ser alterados em paralelo e o que acontece de errado se uma alteração se perder. Para cada dado, compare: um dono por dado, a última gravação vence, versão com conferência, guardar as duas versões e CRDT. Recomende uma estratégia e as bibliotecas ou recursos do banco que eu poderia usar.