MÓDULO 07 · CONCEITO 07 DE 12

CAP, PACELC & os limites fundamentais

Eric Brewer (PODC, 2000), prova de Lynch & Gilbert (2002), PACELC de Daniel Abadi (2010). Os teoremas que articulam o que é fundamentalmente impossível em sistema distribuído — e como cada banco escolhe seu trade-off.

Tempo de leitura ~22 min Pré-requisito Conceitos 05 e 06 (replicação, sharding) Próximo Auto-scaling

Em julho de 2000, no Symposium on Principles of Distributed Computing (PODC) em Portland, Eric Brewer — então professor em Berkeley e cofundador do Inktomi — fez uma keynote com a tese que ficaria famosa: "You can have at most two of: Consistency, Availability, Partition tolerance". A formulação era curta, provocadora, e empiricamente apoiada por anos de experiência construindo sistemas distribuídos. Em 2002, Seth Gilbert e Nancy Lynch (MIT) provaram formalmente o teorema, e CAP virou referência universal — talvez a mais famosa em sistemas distribuídos.

A formulação canônica diz: em sistema distribuído, você pode ter consistência (leituras retornam o último write), disponibilidade (toda request recebe response não-erro), e tolerância a particionamento (sistema continua funcionando mesmo com falha de rede entre nós), mas não os três simultaneamente. Quando uma partição de rede acontece (e ela vai acontecer — rede falha, switch trava, datacenter perde conexão), você escolhe: prefere consistência (recuse requests para manter coerência) ou disponibilidade (responda mas possivelmente com dado obsoleto)?

Em 2010, Daniel Abadi (Yale) publicou um refinamento importante chamado PACELC. A observação: CAP só fala do trade-off durante particionamento, mas particionamento é raro. O resto do tempo, há um trade-off diferente: latência (esperar replicação síncrona) vs consistência (aceitar dado potencialmente atrasado de replica). PACELC articula os dois: "if Partition, choose between Availability and Consistency; Else, choose between Latency and Consistency". É formulação mais precisa para sistemas reais, e é a que sêniores modernos articulam.

Este conceito articula os teoremas em precisão. Definições rigorosas (consistência forte vs eventual vs read-your-writes), o que cada banco moderno escolhe, as armadilhas comuns de interpretação (a mais famosa: "CAP diz que você escolhe 2 de 3" — formulação imprecisa), e como aplicar o pensamento em decisões reais. O foco é articular para discussão, não para prova matemática — a prova está nos papers; o que importa é o vocabulário e o critério.

As três propriedades, em precisão

As três letras de CAP têm definições técnicas que vale fixar. Confusão entre versões informais e técnicas é fonte de mal-entendidos.

Consistency (C). No teorema de Brewer, é especificamente linearizability (Herlihy & Wing, 1990): toda operação parece acontecer atomicamente em algum ponto entre seu início e fim, e operações respeitam a ordem real do tempo. Não confunda com "consistency" no sentido ACID (que envolve invariantes da aplicação e é diferente).

Availability (A). Toda request a um nó não-falhado recebe uma response não-erro em tempo finito. Não exige que a response seja a mais recente; exige que seja alguma response.

Partition tolerance (P). Sistema continua operando mesmo se alguma mensagem entre nós for perdida ou atrasada arbitrariamente. Em sistema realmente distribuído (multi-nó conectado por rede), partition tolerance não é opcional — você precisa dela porque rede falha. A escolha real é entre C e A durante a partição.

O cenário canônico — partição de rede

Para fixar a tensão, considere o cenário canônico. Três nós (A, B, C) replicando dado X. Cliente faz write em A; A propaga para B e C. Tudo bem.

Imagine agora que rede entre A e {B, C} cai. A está isolado. Cliente continua chegando em A com novo write; cliente também continua chegando em B e C com leituras de X. O que A faz?

Opção CP (consistency + partition tolerance, sacrifica A): A recusa o write (ou trava esperando majoritário). Cliente recebe erro; o sistema "para de funcionar" para esse cliente até partição se resolver. Garantia: leituras sempre veem o último valor confirmado.

Opção AP (availability + partition tolerance, sacrifica C): A aceita o write, retorna ok ao cliente. B e C continuam servindo leituras com valor antigo (sem saber que A atualizou). Quando partição se cura, A propaga; possivelmente conflitos de versão precisam ser resolvidos. Garantia: sistema sempre responde, mas respostas podem ser inconsistentes temporariamente.

Não há terceira opção. Aceitar o write em A significa que B e C podem mostrar stale data; recusar significa que A indisponível para o cliente durante a partição. Cada banco distribuído escolhe uma das duas — e essa escolha, articulada em vocabulário CAP, define o caráter do sistema.

As escolhas dos bancos modernos

Cada banco distribuído faz sua escolha. Conhecer a escolha de cada um é parte do vocabulário de senior.

Sistemas CP (preferem consistência)

HBase (Apache, baseado em Bigtable). Strong consistency. Em partição, parte do sistema fica indisponível.

MongoDB com majoritário. Configurado para writes precisarem majority (mais da metade dos nós), MongoDB é CP. Em partição minoritária, leituras e writes ficam indisponíveis.

Spanner (Google, 2012). External consistency global via TrueTime. CP, mas com availability altíssima (~99.999%) graças a clocks sincronizados e algoritmos avançados.

CockroachDB. Inspirado em Spanner. CP, com Raft consenso para majoritário.

etcd, ZooKeeper, Consul. Sistemas de coordenação. Sempre CP — coordenar exige consistência.

Em PACELC, esses sistemas são tipicamente "PC/EC" (preferem consistência durante partição e durante operação normal).

Sistemas AP (preferem disponibilidade)

Cassandra. Eventual consistency por padrão, com tunable consistency (ONE, QUORUM, ALL). Você escolhe per-query. Default é AP.

DynamoDB. AP por padrão. Eventual reads são default (mais baratos); strong reads disponíveis a custo maior.

Riak. AP, baseado em Dynamo. Conflict resolution explícita (siblings).

CouchDB. AP, multi-master com MVCC.

Em PACELC, esses são tipicamente "PA/EL" (preferem disponibilidade em partição, latência em operação normal).

Bancos relacionais tradicionais

PostgreSQL/MySQL standalone. Não são "distribuídos" no sentido CAP — são single-instance com replicação opcional. Single instance: trivialmente CP (não tem nós para particionar). Com streaming replication assíncrona: parecem AP (replicas podem estar atrasadas), mas writes ainda são CP no primário.

Aurora, RDS Multi-AZ. Storage distribuído com quorum-based commit. Em essência CP no nível de write.

Em PACELC, classificação varia conforme configuração de replicação síncrona ou assíncrona.

PACELC — o trade-off durante operação normal

A inovação de Daniel Abadi (paper 2010, "Consistency Tradeoffs in Modern Distributed Database System Design") foi articular o trade-off fora de partição. Esse é o caso 90%+ do tempo em sistemas reais; particionamentos são raros mas catastróficos.

Durante operação normal, o sistema escolhe entre Latência e Consistência: espera replicação síncrona em majoritário (mais lento, mais consistente) ou commita rápido e replica em background (mais rápido, eventualmente consistente).

A formulação completa de PACELC: "if there is a Partition, choose Availability or Consistency; Else (no partition), choose Latency or Consistency". Um sistema é então classificado por suas duas escolhas.

PA/EL: durante partição, prefere Availability; durante operação normal, prefere Latency. Cassandra, DynamoDB, Riak.

PC/EC: durante partição, prefere Consistency; durante operação normal, prefere Consistency. HBase, BigTable, MongoDB (majoritário), Spanner.

PA/EC: durante partição, Availability; durante operação normal, Consistency. Difícil de caracterizar; alguns sistemas com modos configuráveis.

PC/EL: durante partição, Consistency; durante operação normal, Latency. Vários sistemas com replicação assíncrona mas majoritário forte; PNUTS do Yahoo é exemplo mencionado por Abadi.

Tipos de consistência — espectro contínuo

"Consistência" não é binário. Há um espectro contínuo entre "linearizability completo" e "eventual consistency total". Conhecer o espectro ajuda articular o que cada sistema oferece.

Linearizability (strong consistency). Mais forte. Toda operação parece atômica em algum ponto entre início e fim; ordem real do tempo respeitada. É o que Brewer chamou de "C" em CAP.

Sequential consistency. Operações vistas em mesma ordem por todos os clientes, mas ordem não precisa coincidir com tempo real.

Causal consistency. Operações causalmente relacionadas vistas em ordem; outras podem ser arbitrárias.

Read-your-writes consistency. Cliente sempre vê suas próprias modificações. Conceito 05 do módulo cobriu.

Monotonic reads. Após ler valor V de X, leitura subsequente não retorna valor anterior a V.

Eventual consistency. Sem novos writes, todas as réplicas eventualmente convergem. Sem garantias intermediárias.

Sistemas modernos frequentemente oferecem várias consistências configuráveis. Cassandra: CONSISTENCY ONE (eventual), QUORUM (forte se majoritário), ALL (linearizability). DynamoDB: eventual reads vs strong reads. Aprender a configurar por query, não globalmente, é parte da maturidade.

Aplicando o teorema na prática

Como o teorema vira decisão real? Em três passos.

1. Articule a tolerância da aplicação. Pergunte: "se durante partição de rede, parte do sistema fica fora do ar para alguns usuários, isso é aceitável? Ou preferimos servir todos com risco de dado obsoleto?". A resposta varia por feature.

Sistema bancário transferindo dinheiro: prefere indisponibilidade temporária a permitir double-spend. CP. Sistema de feed social: prefere mostrar timeline ligeiramente atrasada do que erro. AP. E-commerce: depende — saldo de estoque (CP) vs visualização de produto (AP) podem conviver no mesmo sistema com bancos distintos.

2. Escolha o banco que casa. Banco que faz a escolha errada para o caso é fonte de bug. Sistema com requirement de strong consistency em DynamoDB precisa configurar strong reads em todas as queries — pagando latência. Mais simples: usar Postgres single-instance (trivialmente CP).

3. Configure por feature. Bancos modernos com tunable consistency permitem ajustar por query. Cassandra: leituras de feed em ONE (rápido, eventual); pagamentos em QUORUM (forte). DynamoDB: idem. Trade-off articulado por endpoint, não global.

Os mitos sobre CAP

Brewer e outros engenheiros têm articulado por anos mitos comuns sobre CAP. Vale corrigir três deles.

Mito 1: "Você escolhe 2 de 3." Essa é a formulação popular, mas é imprecisa. P (partition tolerance) não é opcional em sistema distribuído — você não pode "escolher não tolerar partição"; partições acontecem. A escolha real é entre C e A durante a partição. Brewer mesmo escreveu em 2012 (artigo "CAP Twelve Years Later: How the Rules Have Changed") que a formulação 2-de-3 induz mau raciocínio.

Mito 2: "Sistemas CP são sempre indisponíveis." Sistemas CP em prática são altíssima disponibilidade — Spanner, Aurora, etcd têm 99.99%+ de uptime. CAP só fala durante partição real; partições reais são raras. Em sistema bem desenhado, CP entrega disponibilidade efetiva muito alta.

Mito 3: "AP significa eventual consistency necessariamente fraca." Sistemas AP modernos podem oferecer consistências mais fortes que "puramente eventual" — read-your- writes, monotonic reads, causal consistency. DynamoDB com session consistency, Cassandra com QUORUM, Riak com vector clocks. AP não é "sem consistência"; é "consistência que não bloqueia durante partição".

O cenário CAP em três sistemas reais

Para concretizar, vale ver exatamente o que cada sistema faz em partição.

C# — Postgres com majoritário (CP via Patroni)
// configuração com Patroni para HA
// 3 nós Postgres + etcd para consensus
// majority (2/3) decide quem é primary

# patroni.yml
synchronous_mode: true
synchronous_node_count: 1
synchronous_mode_strict: false

postgresql:
  parameters:
    synchronous_commit: 'on'
    synchronous_standby_names: 'ANY 1 (*)'

// em partição: minoria fica como replica read-only
// majoritário aceita writes
// se um cliente está no lado minoritário, ele lê stale data
//   ou escrita falha — escolha CP

// em código C#, comportamento é transparente
public async Task<Pedido> CriarAsync(CriarPedidoCmd cmd)
{
    // se primary perdeu majority, retorna erro
    // aplicação trata como falha temporária (retry/breaker)
    var p = new Pedido(cmd);
    _db.Pedidos.Add(p);
    await _db.SaveChangesAsync();   // pode falhar em partição
    return p;
}

Postgres + Patroni implementam CP via consensus etcd. Em partição minoritária, aplicação recebe erro de write — mais simples de raciocinar mas trade-off de availability.

Python — Cassandra com tunable consistency
from cassandra.cluster import Cluster
from cassandra.policies import ConsistencyLevel

cluster = Cluster(["node1", "node2", "node3"])
session = cluster.connect("app")

# leitura eventual (rápida, AP)
session.execute(
    "SELECT * FROM pedidos WHERE id = %s",
    [pedido_id],
    execution_profile="eventual",  # CONSISTENCY ONE
)

# leitura forte (mais lenta, CP-ish para majoritário)
session.execute(
    "SELECT * FROM pedidos WHERE id = %s",
    [pedido_id],
    execution_profile="strong",    # CONSISTENCY QUORUM
)

# write com QUORUM — espera majoritário antes de retornar
# em partição minoritária, falha (CP behavior)
session.execute(
    "INSERT INTO pedidos (id, ...) VALUES (%s, ...)",
    [pedido.id, ...],
    execution_profile="strong",    # CONSISTENCY QUORUM
)

# Cassandra é "tunable" — você escolhe por query
# default é AP, mas pode ser CP com QUORUM

Cassandra é exemplo canônico de tunable consistency. Cada query especifica seu requirement. Sistemas que precisam de strong consistency em apenas algumas queries conseguem em Cassandra; sistemas que querem tudo strong devem usar CP nativo.

Go — DynamoDB com strong vs eventual reads
package main

import (
    "context"
    "github.com/aws/aws-sdk-go-v2/service/dynamodb"
    "github.com/aws/aws-sdk-go-v2/service/dynamodb/types"
)

// eventual read (default, rápido, AP)
result, err := client.GetItem(ctx, &dynamodb.GetItemInput{
    TableName: aws.String("Pedidos"),
    Key: map[string]types.AttributeValue{
        "id": &types.AttributeValueMemberS{Value: pedidoID},
    },
    // ConsistentRead: aws.Bool(false), // default
})

// strong read (lento, CP-ish)
result, err = client.GetItem(ctx, &dynamodb.GetItemInput{
    TableName: aws.String("Pedidos"),
    Key: map[string]types.AttributeValue{
        "id": &types.AttributeValueMemberS{Value: pedidoID},
    },
    ConsistentRead: aws.Bool(true),  // 2x mais caro, sempre majoritário
})

// writes em DynamoDB são sempre strongly consistent
// (commit em majority requer)
// reads são tunable: eventual default, strong opcional

DynamoDB tem writes strong por construção; reads são eventual por padrão (mais rápidos e baratos). Strong reads custam ~2× mais. Saber quando precisar de cada um é trabalho do engenheiro.

Particionamento de rede na prática

Vale entender quando partições reais acontecem. Brewer e outros engenheiros catalogaram fontes comuns:

Falha de switch ou roteador. Datacenter perde conexão com outro datacenter, ou parte do datacenter perde conexão com o restante.

Saturação de rede (não falha total). Latência cresce dramaticamente; timeouts disparam; sistema se comporta como se partição existe, mesmo se rede tecnicamente está OK.

Garbage collection pause. Em sistemas com GC, pause longa pode parecer partição (nó deixa de responder por segundos). Heart-beat falha; outros nós o consideram morto. Conceito 04 do módulo 06 cobriu tail latency.

Asymmetric partition. A vê B; B não vê A. Rare mas existe. Sintomas estranhos.

Split brain. Particionamento simétrico onde cada lado pensa que é o majoritário. Sem consenso adequado, ambos aceitam writes. Quando partição se cura, conflito. Defesa: consensus algorithm robusto (Raft, Paxos).

A pesquisa Aphyr/Jepsen (Kyle Kingsbury, 2013-presente) documenta como bancos distribuídos reais se comportam sob partições. Os relatórios jepsen.io são leitura essencial — mostram que muitos bancos prometem mais que entregam, especialmente sob particionamentos reais.

O caso surpreendente — escolha local não-global

Bancos distribuídos modernos permitem escolha por operação, não globalmente. Cassandra, DynamoDB, MongoDB — todos têm tunable consistency. Sistema pode ser AP para leituras de feed e CP para writes de pagamento.

Essa flexibilidade muda o jogo. Em vez de "escolher o banco e ficar com a escolha", o engenheiro escolhe por endpoint. Strong consistency caro? Use só onde precisa. Eventual barato? Use no resto. É o que diferencia time que conhece a ferramenta de time que aplica regra fixa.

A maturidade de senior é conseguir articular, para cada operação importante: "essa precisa de strong consistency porque X" ou "essa tolera eventual porque Y" — e configurar por query.

armadilha em produção

Sistema escolhe banco AP achando que será mais simples; meses depois descobre que feature crítica precisa de strong consistency e o banco não entrega bem. Cenário comum: time escolhe DynamoDB para "performance e custo"; depois precisa implementar saldo bancário garantido — eventual reads dão resultado errado sob carga, strong reads dobram custo. Ou vice-versa: Postgres CP escolhido para tudo, mas feature de analytics em escala de petabytes não cabe. Defesa: articular consistency requirements antes de escolher banco; preferir bancos com tunable consistency quando há diversidade de operações; aceitar 2 bancos diferentes quando requirements divergem dramaticamente.

heurística do sênior

Para cada operação crítica, articule consistency requirement em vocabulário CAP/PACELC. "Pagamento: precisa linearizability — banco CP". "Listagem de produtos: tolera segundos de defasagem — banco AP é OK". "Saldo de carteira: precisa read-your-writes — escrevo em strong, leio em strong por algum tempo". "Counter de likes: tolera eventual — usa Cassandra/Redis com eventual." Articulando para cada operação, a escolha de banco vira derivada — não decisão anterior. Times maduros operam com 2-3 bancos diferentes, cada um servindo o que ele faz bem.

Por que importa para a sua carreira

CAP/PACELC é vocabulário de senior em sistemas distribuídos. Em entrevistas, "explique CAP e como aplicar" é pergunta clássica — a resposta forte menciona Brewer 2000, prova de Lynch & Gilbert 2002, refinamento PACELC de Abadi 2010, e articula mitos comuns. Em revisão de proposta de banco, identificar mismatch entre requirement e escolha de banco é serviço crítico. Em pos-mortem de "writes foram perdidos durante partição", articular como o banco escolheu AP e como mitigar é trabalho de senior. E em discussão de arquitetura, classificar cada sistema do stack em PACELC ("MongoDB configurado em majoritário é PC/EC"; "Redis Cluster é AP/EL") é vocabulário maduro que muda o tom da conversa.

Como praticar

  1. Classifique seu stack. Pegue cada banco/cache/queue do seu projeto. Para cada, articule a classificação PACELC. "Postgres single: trivialmente CP". "Redis: depende — cluster ou single?". "Kafka: AP/EL para producers, configurável para consumers." Documente em ADR. Esse exercício revela se você de fato entende o comportamento.
  2. Simulação de partição. Em ambiente staging, suba cluster de 3 nós (Postgres com Patroni, Cassandra, ou similar). Use iptables ou tc Linux para introduzir latência ou bloquear pacotes entre nós. Observe comportamento sob partição. Esse é o tipo de chaos engineering que prepara para incidentes reais.
  3. Tunable consistency em código. Em Cassandra ou DynamoDB local, implemente a mesma operação em duas configurações: eventual e strong. Meça latência e custo (em DynamoDB, consumed read units; em Cassandra, latência por consistency level). Documente quando vale cada um. Esse exercício faz a teoria virar prática.

Referências para aprofundar

  1. paper Towards Robust Distributed Systems — Eric Brewer (PODC keynote, 2000). Slides da keynote original. O ponto onde CAP foi cunhado. Curtinho, fundador.
  2. paper Brewer's Conjecture and the Feasibility of Consistent, Available, Partition-Tolerant Web Services — Seth Gilbert, Nancy Lynch (ACM SIGACT News, 2002). A prova formal do teorema. Densidade matemática alta; vale ler para entender exatamente o que o teorema diz e não diz.
  3. paper Consistency Tradeoffs in Modern Distributed Database System Design — Daniel Abadi (IEEE Computer, 2012). cs.umd.edu/~abadi/papers/abadi-pacelc.pdf — Onde PACELC é articulado em forma final. Refinamento que muda como sêniores discutem o tema.
  4. artigo CAP Twelve Years Later: How the Rules Have Changed — Eric Brewer (IEEE Computer, 2012). Doze anos depois do paper, Brewer atualiza o pensamento. Articula mitos comuns. Indispensável.
  5. livro Designing Data-Intensive Applications — Martin Kleppmann (O'Reilly, 2017). Cap. 9 (Consistency and Consensus) cobre CAP, PACELC, e o espectro de consistências em profundidade rara. O melhor tratamento em livro.
  6. livro Database Internals — Alex Petrov (O'Reilly, 2019). Cap. 14-15 cobrem consistency models e replicação distribuída com formalismo acessível.
  7. livro Distributed Systems (3ª ed.) — Maarten van Steen, Andrew Tanenbaum (gratuito, 2017). distributed-systems.net — Tratamento universitário canônico, gratuito. Cap. sobre consistency models é referência.
  8. artigo Jepsen Analyses — Kyle Kingsbury (jepsen.io, 2013-present). jepsen.io/analyses — Análises empíricas de bancos distribuídos sob partição. Mostra o que cada banco realmente faz vs o que promete. Indispensável.
  9. artigo Eventually Consistent — Werner Vogels (CACM, 2008). Vogels é CTO da AWS. Define eventual consistency e variações com clareza. Útil contexto histórico.
  10. artigo You Can't Sacrifice Partition Tolerance — Coda Hale (codahale.com, 2010). Argumenta que P não é opcional em sistemas distribuídos reais. Articulação clara de mito comum.
  11. docs Cassandra Tunable Consistency. cassandra.apache.org/doc/latest/cassandra/architecture/dynamo.html — Documentação canônica de como Cassandra implementa as opções AP/CP por query.
  12. vídeo Eric Brewer — A Decade of Building Web Services (várias palestras). YouTube. Brewer apresenta CAP retrospectivamente em vários eventos. Vale procurar pela mais recente.