CAP e PACELC: o teorema que define toda arquitetura distribuída
Ao terminar: Você escolhe entre consistência e disponibilidade sabendo qual das duas está sendo sacrificada, e por quê.
CAP é o teorema mais citado e mais mal-explicado de sistemas distribuídos. "Você escolhe 2 de 3" é slogan didático, não verdade técnica. Este módulo explica a formulação real (Gilbert & Lynch, 2002), estende com PACELC (Abadi, 2012) — que cobre o trade-off de latência em operação normal — e aterrissa em decisões práticas: Postgres vs DynamoDB, Cassandra vs Spanner, polyglot persistence em fintech.
O teorema, da forma precisa
CAP (Gilbert & Lynch, 2002): Em um sistema distribuído com múltiplos nós, na presença de uma partição de rede, você não pode garantir simultaneamente consistência linearizável e disponibilidade total. Tem que escolher um dos dois enquanto a partição dura.
| Termo | O que é | Quando aparece |
|---|---|---|
| Consistency (C) | Toda leitura retorna a escrita mais recente (linearizability) | Sempre que há múltiplas réplicas |
| Availability (A) | Toda request a nó não-falho retorna resposta (não falha por indisponibilidade) | Quando a rede ou outro nó falha |
| Partition tolerance (P) | Sistema continua operando apesar de mensagens perdidas entre nós | Sempre que há rede — ou seja, sempre |
P não é "opcional". Redes reais tem jitter, lag, switches que pifam. Assumir "sem partições" é assumir magia. Então o trade-off real é CP ou AP durante partição — não "escolha 2 de 3".
PACELC: o trade-off sem falha
PACELC (Daniel Abadi, 2012) estende CAP observando que, mesmo sem partição, sistemas distribuídos enfrentam trade-off entre latência e consistência. Commit síncrono em múltiplas réplicas dá consistência forte, mas aumenta latência. Commit local rápido + replicação assíncrona reduz latência mas permite leituras de réplicas obsoletas.
| Sistema | Partição (P) | Else (E) | Classificação |
|---|---|---|---|
| DynamoDB (eventual) | A (disponível) | L (low latency) | PA/EL |
| Cassandra (default) | A | L | PA/EL |
| Spanner | C (consistente) | C (consistent) | PC/EC |
| CockroachDB | C | C | PC/EC |
| MongoDB (W=majority) | A ou C (config) | C | PA/EC ou PC/EC |
| Postgres + sync replicas | C | C | PC/EC |
| Postgres + async replicas | A (leitura stale) | L em leitura | PA/EL em leitura |
CP e AP na prática: quem é quem
| Categoria | CP | AP |
|---|---|---|
| Comportamento em partição | Lado minoritário fica indisponível | Ambos aceitam escrita |
| Garante linearizability | Sim | Não (eventual ou causal) |
| Latência típica | Maior (quorum) | Menor (commit local) |
| Exemplos | Postgres, Spanner, Cockroach, etcd, ZooKeeper | Dynamo, Cassandra, Riak, CouchDB |
| Resolução pós-partição | Não precisa — não divergiu | Last-write-wins, CRDT, vector clocks, app-level merge |
| Caso de uso forte | Dinheiro, estoque, auth, coordenação | Feed, logs, carrinho, sessão, catálogo |
O mito do 'AP sempre' e do 'CP sempre'
"Escolhemos AP porque não podemos ficar indisponíveis" — frase comum em pitch, raramente verdadeira. Em bancos, uma transferência duplicada é um bug catastrófico (compliance, reconciliação manual). Em um feed social, uma curtida duplicada ou perdida é invisível. Cada domínio tem seu regime.
📋 Carrinho de e-commerce (itens adicionados/removidos pelo usuário)
Usuário em outro device adiciona item, partição de rede rola, depois reconcilia. OR-Set (CRDT) resolve conflitos automaticamente. Indisponibilidade de 'não consegui adicionar ao carrinho' é pior que divergência breve.
📋 Transferência bancária entre contas
Saldo errado = perda financeira + compliance issue. Melhor rejeitar transação ('tente novamente em alguns segundos') do que aceitar e divergir. Postgres + replicação síncrona, ou Spanner-like.
📋 Timeline de posts em uma rede social
Ver post 200ms mais tarde é aceitável. Indisponibilidade do feed é inaceitável. Cassandra/Scylla com RF=3, CL=QUORUM para escrita e CL=ONE para leitura é clássico.
Quorum: o dial contínuo entre CP e AP
Sistemas como Cassandra e Dynamo expõem W (réplicas que precisam ack na escrita) e R (réplicas consultadas na leitura). Com total de N réplicas, se W + R > N você tem strong consistency (há overlap garantido). Caso contrário, eventual.
| N / W / R | Regime | Trade-off |
|---|---|---|
| N=3 / W=1 / R=1 | AP puro | Baixíssima latência; alta disponibilidade; eventual |
| N=3 / W=3 / R=1 | CP na escrita | Escrita cara; leitura rápida; strong |
| N=3 / W=1 / R=3 | CP na leitura | Escrita rápida; leitura cara; strong |
| N=3 / W=2 / R=2 (QUORUM) | Balance | W+R>N: strong; tolera 1 falha |
| N=5 / W=3 / R=3 | Strong + alta tolerância | Tolera 2 falhas; mais caro |
Qual é o enunciado preciso do teorema, e qual é o mal-entendido mais comum?
Polyglot persistence: diferentes stores para diferentes regimes
Fintech típica (AI-native, 2026):
┌─────────────────────┐ ┌─────────────────────┐
│ Saldo / Transações │ │ Perfil / Prefs │
│ Postgres + WAL │ │ DynamoDB │
│ sync replica │ │ eventual │
│ (CP / EC) │ │ (AP / EL) │
└─────────────────────┘ └─────────────────────┘
┌─────────────────────┐ ┌─────────────────────┐
│ Analytics event │ │ Rate limit counter │
│ Kafka → Clickhouse │ │ Redis (AP) │
│ (AP / EL) │ │ sliding window │
└─────────────────────┘ └─────────────────────┘
┌─────────────────────┐ ┌─────────────────────┐
│ Config / Coord. │ │ Search / Embeddings│
│ etcd (Raft, CP) │ │ OpenSearch + vector│
│ (PC / EC) │ │ (AP / EL) │
└─────────────────────┘ └─────────────────────┘Polyglot persistence não é "moda". É resposta direta a CAP/PACELC: dado diferente tem regime diferente. Escolher um único banco para tudo força trade-offs inadequados em algum lugar. Custo operacional é real, mas menor que o bug de arquitetura errada.
Código: simulação de partição em Python
# Simulação didática: 2 réplicas, partição, CP vs AP
from dataclasses import dataclass
from typing import Literal
@dataclass
class Replica:
name: str
data: dict[str, str]
partitioned_from: set[str]
mode: Literal["CP", "AP"]
def write(self, peers: list["Replica"], key: str, value: str) -> bool:
if self.mode == "CP":
reachable = [p for p in peers if p.name not in self.partitioned_from]
# CP: precisa de maioria (quorum)
needed = (len(peers) + 1) // 2 + 1
if len(reachable) + 1 < needed:
return False # indisponível
self.data[key] = value
for p in reachable:
p.data[key] = value
return True
else: # AP
self.data[key] = value # commit local sempre
for p in peers:
if p.name not in self.partitioned_from:
p.data[key] = value
return True
a = Replica("A", {}, set(), "CP")
b = Replica("B", {}, set(), "CP")
c = Replica("C", {}, set(), "CP")
# Sem partição — escrita funciona
print(a.write([b, c], "x", "1")) # True
# Partição: A isolado de B e C
a.partitioned_from = {"B", "C"}
b.partitioned_from = {"A"}
c.partitioned_from = {"A"}
# CP: A isolado não tem quorum → escrita falha
print(a.write([b, c], "x", "2")) # False (esperado)
# Já em AP, A aceitaria e divergiria; depois resolve (LWW/CRDT/etc)Perguntas típicas
❓ Se minha aplicação é só 1 banco Postgres, CAP ainda se aplica?
❓ Microserviços têm CAP?
❓ Kafka é CP ou AP?
❓ Por que etcd é PC/EC se já tem Raft?
Take-aways. CAP não é "escolha 2 de 3" — é "em partição, escolha entre C e A". P é dado. PACELC estende: mesmo sem falha há trade-off entre latência e consistência. Spanner/Cockroach = PC/EC. Dynamo/Cassandra = PA/EL. Polyglot persistence é a resposta certa — cada domínio no regime adequado. Quorum (W, R, N) é o dial contínuo entre CP e AP. Próximo módulo detalha os modelos de consistência (strong, eventual, causal, read-your-writes) e quando cada um é o mínimo aceitável.
Perguntas frequentes
❓ O que o teorema CAP realmente diz?
❓ O que o PACELC acrescenta ao CAP?
❓ Dá para ter consistência e disponibilidade?
Fixando
O que a extensão do teorema acrescenta ao trade-off original?
Como o quórum permite ajustar o comportamento de forma contínua?
Terminou de ler?
Marcar como concluído registra o XP, mantém sua sequência e coloca 3 cartas deste módulo na fila de revisão espaçada.
Próximos passos sugeridos
Temas deste módulo
Discussão
Carregando comentários…