Consistência Eventual vs Forte
Em sistemas distribuídos, dados replicados podem ficar temporariamente desincronizados entre nós. Consistência define quão "atual" é a leitura que um cliente recebe após uma escrita — e a escolha entre modelos tem consequências diretas em disponibilidade, latência e complexidade do código.
Intenção
Consistência define a garantia sobre o valor que um cliente lê após uma escrita ter sido confirmada em algum nó do sistema distribuído. Em um banco de dados com réplicas, a escrita ocorre no primário mas as leituras podem servir de qualquer réplica — e réplicas assíncronas podem estar atrás. A questão é: o quanto de "atrás" é aceitável para o seu caso de uso?
Este não é um problema técnico com uma resposta certa: é um tradeoff de negócio. Um sistema de saldo bancário e um feed de timeline do Twitter têm respostas completamente diferentes para "quão stale pode ser uma leitura?".
Problema
Quando um sistema distribui dados entre múltiplos nós para ganhar disponibilidade e performance, toda escrita precisa ser propagada para as réplicas. Essa propagação tem custo: leva tempo, pode falhar e cria um intervalo durante o qual réplicas diferentes têm versões diferentes do dado.
- Se você espera que todas as réplicas confirmem antes de responder ao cliente (consistência forte): a escrita é mais lenta, pois depende da réplica mais lenta do conjunto. Se qualquer réplica estiver inacessível, você pode precisar recusar a escrita ou a leitura.
- Se você confirma ao cliente assim que o primário aceita (consistência eventual): a escrita é rápida, mas leituras imediatas de réplicas podem retornar o valor antigo por alguns milissegundos — ou segundos em alta carga.
- O problema é que ambos os comportamentos parecem corretos no ambiente de desenvolvimento (banco local, latência zero entre nós) e só ficam visíveis em produção, sob carga, com réplicas reais.
Como funciona
Consistência Forte (Strong Consistency)
Após uma escrita ser confirmada, qualquer leitura subsequente em qualquer nó do sistema retorna o valor atualizado. Para isso, o sistema precisa de algum mecanismo de coordenação: ou a escrita é síncrona para todas as réplicas (todas confirmam antes do cliente receber ok), ou usa um protocolo de consenso como Raft (usado no etcd, CockroachDB, TiDB) ou Paxos (usado no Google Spanner, Zookeeper).
O custo é latência de escrita: você paga o RTT para as réplicas mais lentas ou geograficamente distantes a cada escrita. Em sistemas multi-região com réplicas na Europa e na Ásia, isso pode significar 150–300 ms de latência de escrita garantida.
Consistência Eventual (Eventual Consistency)
Após uma escrita no nó primário, as réplicas receberão a atualização eventualmente — sem garantia de quando. Em condições normais de rede, o lag costuma ser de alguns milissegundos a poucos segundos. Em alta carga ou problemas de rede, pode ser mais.
O sistema confirma a escrita ao cliente assim que o primário aceita, sem esperar as réplicas. Leituras de réplicas podem retornar o valor antigo até a propagação completar. Isso permite maior disponibilidade e menor latência de escrita, ao custo de leituras potencialmente stale.
Modelos intermediários
Entre os extremos existem garantias mais específicas que permitem tradeoffs mais precisos:
- Read-Your-Writes (read-after-write): você sempre lê o que você acabou de escrever, mesmo que outros clientes ainda vejam o valor antigo. Implementado via sticky reads (o cliente sempre lê do primário após escrever) ou tokens de causalidade (o cliente carrega a versão mínima que o servidor deve ter para atender a leitura).
- Monotonic Reads: uma vez que o cliente leu um valor, ele nunca lerá um valor mais antigo. Importante em sistemas onde o usuário faz múltiplas leituras — ver dados "voltarem no tempo" é confuso e gera bugs difíceis de reproduzir.
- Consistência Causal: operações que têm relação causal (B depende de A) são vistas na ordem correta por todos os nós. "Você curtiu um post que eu não vi ainda" é uma violação de consistência causal. Mais forte que eventual, mais fraca que forte.
CAP Theorem
O Teorema CAP (Brewer, 2000) afirma que em caso de Partição de rede (P — nós não conseguem se comunicar), um sistema distribuído precisa escolher entre:
- Consistência (C): toda leitura retorna o valor mais recente escrito, ou um erro. O sistema recusa servir leituras de nós que possam estar desatualizados durante a partição.
- Disponibilidade (A): toda requisição recebe resposta (possivelmente com dado desatualizado). O sistema continua operando mesmo que os nós estejam em diferentes lados da partição.
Consistência forte → sistema escolhe C, sacrificando A durante partição. Consistência eventual → sistema escolhe A, mantendo disponibilidade mas podendo retornar dados stale.
PACELC — o teorema mais completo
O CAP só fala de partições. O PACELC (Daniel Abadi, 2012) adiciona: mesmo sem partição, há tradeoff entre Latência (L) e Consistência (C).
PACELC: se Partição → escolha entre A e C
senão (Else) → tradeoff entre L e C
Exemplos:
DynamoDB: PA/EL — prioriza disponibilidade e baixa latência
Cassandra: PA/EL — idem; consistência configurável por operação
PostgreSQL: PC/EC — prioriza consistência; mais latência em writes
Spanner: PC/EC — consistência forte global; alta latência relativa
MongoDB: PA/EC — disponível em partição; configurável (w:majority)
Muitos bancos NoSQL permitem configurar o nível por operação (DynamoDB
ConsistentRead, Cassandra ConsistencyLevel),
permitindo tradeoffs diferentes para leituras diferentes na mesma aplicação.
Quando usar cada modelo
- Consistência forte: saldo bancário, inventário de e-commerce com estoque crítico, sistemas de votação, reserva de assentos, qualquer operação onde ler dado antigo tem consequência real de negócio (vender o mesmo assento duas vezes, autorizar saque de valor que não existe).
- Consistência eventual: timeline de redes sociais, contadores de views e likes (lag de segundos é aceitável), catálogo de produtos (ver preço de ontem por alguns ms é inofensivo), feature flags, recomendações, buscas — qualquer dado onde stale tem impacto desprezível.
- Read-Your-Writes: perfil do usuário — você acabou de atualizar seu avatar e quer ver a mudança. Para outros usuários, eventual é OK, mas para você mesmo a garantia é importante.
- Causal Consistency: threads de comentários (a resposta ao comentário deve aparecer depois do comentário original), feeds de atividade social onde a ordem causal importa para o contexto.
Prós e contras
Consistência Forte — Prós
- Semântica simples: você nunca precisa pensar em "será que esse dado está atualizado?".
- Essencial para operações financeiras e de inventário crítico.
- Facilita o raciocínio sobre o código: não há necessidade de lógica de "eventual retry" ou "compensação por stale read".
Consistência Forte — Contras
- Maior latência de escrita: o sistema espera confirmação de todas (ou maioria das) réplicas.
- Menor disponibilidade durante partições: se réplicas ficam inacessíveis, o sistema pode recusar operações.
- Não escala bem geograficamente: replicação síncrona entre regiões adiciona 100–300 ms de latência de escrita.
Consistência Eventual — Prós
- Alta disponibilidade: o sistema continua operando mesmo com réplicas temporariamente inacessíveis.
- Baixa latência de escrita: confirma sem esperar réplicas.
- Escala bem geograficamente: réplicas assíncronas em qualquer região sem impacto na latência de escrita.
Consistência Eventual — Contras
- Complexidade no código de aplicação: você precisa lidar com a possibilidade de leituras stale.
- Leituras imprevisíveis: em alta carga o lag pode ser segundos — o design deve tolerar isso explicitamente.
- Conflitos em escritas concorrentes: dois nós que aceitaram escritas conflitantes precisam de estratégia de resolução (LWW — Last Write Wins, CRDT, ou resolução manual).
Armadilhas comuns
1. Assumir consistência forte em banco com réplicas assíncronas
O erro mais comum: o código escreve no banco e imediatamente faz uma leitura — mas a leitura vai para uma réplica de leitura (read replica) que ainda não processou a escrita. O usuário vê o dado antigo. Isso parece um bug intermitente porque o lag varia e em desenvolvimento (banco local sem réplicas) o bug nunca aparece.
Prática: em operações onde Read-Your-Writes é necessário,
leia do primário (não da réplica) imediatamente após escrever. ORMs modernos
têm configuração para isso (ex.: after_write_primary_reads no
Prisma, read_preference=primary no MongoDB, ou simplesmente
usar a mesma conexão do write).
2. Tratar "eventual" como "imediato na prática"
Em desenvolvimento com um banco local, a propagação é instantânea. Em produção, com réplicas reais e carga real, o lag pode ser de segundos. Arquiteturas que assumem "eventual demora menos de 100 ms" funcionam bem na maioria do tempo e falham silenciosamente quando a carga aumenta ou a rede piora. O design deve tolerar lag de segundos, não de milissegundos.
3. Não documentar o modelo de consistência da API
Se o seu endpoint de leitura usa consistência eventual, documente isso explicitamente. O chamador precisa saber que pode receber dados de até X segundos atrás, para poder decidir se isso é aceitável para o seu caso de uso. Consistência não documentada gera surpresas em produção e bugs difíceis de diagnosticar em integrações.
4. Confundir consistência com durabilidade
Consistência define quão atual é a leitura que você recebe. Durabilidade
define se uma escrita sobrevive a falhas (crash do servidor, falha de disco).
São dimensões ortogonais: um sistema pode ter consistência eventual e alta
durabilidade (DynamoDB com replicação em 3 zonas) ou consistência forte com
durabilidade configurável (MongoDB com w:1 vs w:majority).
Confundir os dois leva a decisões de arquitetura equivocadas.
Arquiteturas e padrões relacionados
Replicação de Banco é o mecanismo físico que implementa os modelos de consistência: replicação síncrona habilita consistência forte; replicação assíncrona resulta em consistência eventual. Entender consistência é essencial para configurar replicação corretamente.
CQRS separa explicitamente o modelo de escrita (consistência forte no Command Store) do modelo de leitura (consistência eventual nas projeções/read models). Isso torna o tradeoff de consistência explícito na arquitetura em vez de implícito na configuração do banco.
SQL vs NoSQL tem forte relação com consistência: bancos SQL tradicionais oferecem consistência forte por padrão (ACID); bancos NoSQL historicamente priorizaram disponibilidade e consistência eventual (BASE), embora muitos modernos (CockroachDB, Spanner, DynamoDB com transações) ofereçam consistência forte configurável.
Sharding adiciona outra dimensão: consistência entre shards diferentes é especialmente difícil — uma transação que cruza dois shards não tem uma réplica única de verdade, exigindo protocolos de commit distribuídos para garantir consistência forte.