CAP e PACELC: o teorema que define toda arquitetura distribuída
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
| 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 |
PACELC: o trade-off sem falha
┌───────────────┐
│ Há partição? │
└───────┬───────┘
│
┌──────────────┬──────────────┐
│ SIM │ NÃO (Else) │
▼ ▼ ▼
┌───────────────┐ ┌─────────────────────────┐
│ Escolha: │ │ Escolha: │
│ P(A) │ │ L (Latency) │
│ P(C) │ │ C (Consistency) │
└───────────────┘ └─────────────────────────┘
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
┌────────────────┐
│ Client writes │
└────────┬───────┘
│
┌────────────┼────────────┐
▼ ▼ ▼
Replica A Replica B Replica C
(leader) (follower) (follower)
│ │ │
│ majority ack required │
└────────────┴────────────┘
│
▼
Commit sucedeu
Durante partição: minoria rejeita escritas (vira indisponível);
maioria segue funcionando e mantém consistência.
┌────────────────┐
│ Client writes │
└────────┬───────┘
│
┌────────────┼────────────┐
▼ ▼ ▼
Replica A Replica B Replica C
│ │ │
│ qualquer ack serve │
└────────────┴────────────┘
│
▼
Commit local sucedeu
(replicação async)
Durante partição: todos lados aceitam escritas;
depois da partição, faz resolução (last-write-wins, CRDT, vector clocks).
| 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'
📋 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 |
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) │ └─────────────────────┘ └─────────────────────┘
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?
acks=all e min.insync.replicas correto, Kafka garante durabilidade forte. Sem isso (acks=1), vira basicamente AP — mais rápido, mas pode perder mensagens em falha. Config é tudo.❓ Por que etcd é PC/EC se já tem Raft?
Quiz rápido
4 perguntas · Acerte tudo e ganhe o badge 🎯 Gabarito