🧠FFVAcademy
⚖️

CAP e PACELC: o teorema que define toda arquitetura distribuída

16 min de leitura·+80 XP

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.
TermoO 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ósSempre 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 visualizado

                ┌───────────────┐
                │  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.

SistemaPartição (P)Else (E)Classificação
DynamoDB (eventual)A (disponível)L (low latency)PA/EL
Cassandra (default)ALPA/EL
SpannerC (consistente)C (consistent)PC/EC
CockroachDBCCPC/EC
MongoDB (W=majority)A ou C (config)CPA/EC ou PC/EC
Postgres + sync replicasCCPC/EC
Postgres + async replicasA (leitura stale)L em leituraPA/EL em leitura

CP e AP na prática: quem é quem

🗺️ CP (Consistency under Partition)

                 ┌────────────────┐
                 │  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.
🗺️ AP (Availability under Partition)

                 ┌────────────────┐
                 │  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).
CategoriaCPAP
Comportamento em partiçãoLado minoritário fica indisponívelAmbos aceitam escrita
Garante linearizabilitySimNão (eventual ou causal)
Latência típicaMaior (quorum)Menor (commit local)
ExemplosPostgres, Spanner, Cockroach, etcd, ZooKeeperDynamo, Cassandra, Riak, CouchDB
Resolução pós-partiçãoNão precisa — não divergiuLast-write-wins, CRDT, vector clocks, app-level merge
Caso de uso forteDinheiro, estoque, auth, coordenaçãoFeed, 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)

AP com resolução determinística

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

CP com transação forte

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

AP com eventual consistency

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 / RRegimeTrade-off
N=3 / W=1 / R=1AP puroBaixíssima latência; alta disponibilidade; eventual
N=3 / W=3 / R=1CP na escritaEscrita cara; leitura rápida; strong
N=3 / W=1 / R=3CP na leituraEscrita rápida; leitura cara; strong
N=3 / W=2 / R=2 (QUORUM)BalanceW+R>N: strong; tolera 1 falha
N=5 / W=3 / R=3Strong + alta tolerânciaTolera 2 falhas; mais caro

Polyglot persistence: diferentes stores para diferentes regimes

text
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

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?

Menos, mas sim em replicação. Quando você adiciona read replica, primary + replica formam um sistema distribuído. Leitura na replica async pode ser stale — isso é o trade-off AP/EL na prática. Read-your-writes guarantee vem com session state (ler no primary) ou waits explícitos.

Microserviços têm CAP?

Sim, e é onde mais machuca: cada serviço tem seu DB, comunicação entre eles é rede. "Transação distribuída" passa a ser problema. Soluções: sagas (próximo módulo), outbox pattern, event-driven. Evite 2PC em prod moderna — mais no módulo de sagas.

Kafka é CP ou AP?

CP na lógica de ISR (in-sync replicas) e commit. Com 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?

Raft é o MEIO; CAP/PACELC descreve o COMPORTAMENTO. Raft implementa consenso, que é a primitiva para sistemas CP. etcd paga latência em operação normal (quorum writes) — EC. Em partição, lado minoritário fica indisponível — PC. Essa combinação (PC/EC) é o que Kubernetes quer para control plane: coordenação consistente, mesmo que mais lenta.
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.
🧩

Quiz rápido

4 perguntas · Acerte tudo e ganhe o badge 🎯 Gabarito

Continue lendo