🧠FFVAcademy
🔄

Modelos de Consistência: strong, eventual, causal, read-your-writes

17 min de leitura·+85 XP
Pré-requisitos (0/1)0%

Recomendamos completar os pré-requisitos antes de seguir, mas nada te impede de continuar.

"Eventualmente consistente" e "fortemente consistente" são slogans que escondem uma hierarquia rica. Este módulo explica a escada de modelos — linearizability, sequential, causal, eventual — e as session guarantees(read-your-writes, monotonic reads, bounded staleness) que resolvem a maioria dos problemas reais sem pagar o custo de consistência global.

A escada de consistência (do mais forte ao mais fraco)

🗺️ Hierarquia de consistência

  Strict / Linearizability           ← mais forte, mais caro
         │
         ▼
  Sequential consistency
         │
         ▼
  Causal consistency
         │
         ▼
  PRAM / FIFO consistency
         │
         ▼
  Session guarantees                 ← prático e suficiente em 80%
  (read-your-writes, monotonic r/w)
         │
         ▼
  Eventual consistency               ← mais fraco, mais rápido
ModeloGarantiaPreço típico
LinearizabilityOrdem global + tempo real; parece single-threadedQuorum/consenso; latência alta
SequentialOrdem total coerente com ordem de cada proc.Menor que linearizability, mas ainda exige coordenação
CausalPreserva happens-before; concorrentes livresVector clocks / dependency tracking
PRAM / FIFOCada escritor visto em ordem por todosMuito barato, raramente suficiente
Session guaranteesConsistência dentro da sessão do clienteSticky session ou LSN tracking
EventualSem novas escritas, convergeMínimo; resolução LWW/CRDT/manual

Linearizability: o modelo que parece 'um banco só'

Linearizability é o padrão mental default do desenvolvedor single-node: "o que escrevi agora, qualquer leitura depois vê". Em sistema distribuído isso exige coordenação — commit em quorum, ou consenso (Raft/Paxos) para decidir ordem global.

text
Operações de 3 clientes em paralelo:

Client A: ──── write(x=1) ─── [ack] ─────────────────────────────
Client B: ─────── read(x) ──────── [retorna 1] ──────────────────
Client C: ─────────────── read(x) ─────── [retorna 1] ───────────

Linearizability exige: tudo parece ter ocorrido em UM instante entre
início e fim de cada op, respeitando ordem real. Se B começou READ
APÓS o ack de A, B DEVE ver x=1.
⚠️
Linearizability global em múltiplas regiões tem latência mínima limitada pela velocidade da luz — round-trip entre São Paulo e Virginia é ~200ms. Aplicações globais PAGAM essa latência ou aceitam modelos mais fracos. Spanner usa TrueTime para mitigar, mas ainda tem commit-wait.

Causal consistency: preservando o 'porque'

Causal consistency (Lamport/Ahamad) define que toda ordem causal é respeitada por todos observadores. Se operação A happens-before B, ninguém vê B sem A. Operações concorrentes (sem causa-efeito) podem aparecer em qualquer ordem.

text
Cenário clássico de chat:
  Alice posta:  "Acabei de me casar!"              (evento e1)
  Bob responde: "Parabéns!"                         (evento e2, depende de e1)

  Carol abre o app e lê os dois. Causal exige que Carol veja e1 ANTES de e2.
  Eventual não garante — Carol poderia ver só "Parabéns!" até o post chegar.
  Linearizability garante, mas cobra ordem total global — caro demais.

Vector clocks ou dependency tracking resolvem causal com custo razoável.
💡
Produtos como Facebook, LinkedIn e Twitter consomem artigos e papers sobre causal como base. Sistemas como COPS, Eiger, TARDiS implementam causal+ em geo-distribuição. Um feed "bom o suficiente" em 10 regiões costuma ser causal — mais forte que eventual, mais barato que linearizability.

Session guarantees: o que resolve UX real

GuaranteeO que garanteQuando importa
Read-your-writesDepois de escrever, suas leituras refletemAtualização de perfil, submit de form
Monotonic readsLeituras sucessivas não "voltam no tempo"Feed, lista paginada
Monotonic writesSuas escritas aplicam em ordemUpdates sequenciais no mesmo recurso
Writes-follow-readsSe você leu X, suas escritas vêm depoisComentário depois de ler post
Bounded stalenessLeitura atrasa no máximo Δ tempo ou N versõesDashboard, métricas
python
# Read-your-writes em Postgres com read replicas — pattern com LSN no cookie
import psycopg

def write(conn, user_id: str, bio: str) -> int:
    with conn.cursor() as cur:
        cur.execute("UPDATE users SET bio=%s WHERE id=%s RETURNING pg_current_wal_lsn()", (bio, user_id))
        lsn = cur.fetchone()[0]
    conn.commit()
    return lsn                 # envia para client (cookie/header)

def read(user_id: str, client_lsn: str | None) -> dict:
    # Se o client tem LSN, obrigue leitura em replica com replay >= LSN
    if client_lsn:
        replica = pick_replica_at_or_after(client_lsn)      # sua função de routing
        if not replica:
            replica = PRIMARY                                # fallback
    else:
        replica = pick_any_replica()
    with replica.cursor() as cur:
        cur.execute("SELECT bio FROM users WHERE id=%s", (user_id,))
        return {"bio": cur.fetchone()[0]}

Bounded staleness: o contrato do 'quase tempo real'

Em dashboards, relatórios, alimentação de BI e feature stores, você aceita atraso — desde que limitado. Bounded staleness define o limite: "leitura atrasa no máximo 5 segundos ou 100 versões". Sistemas como Azure Cosmos DB expõem essa opção como nível de consistência.

CasoModelo adequadoPor que
Transferência bancáriaLinearizabilityDinheiro não divergente
Leaderboard de jogoBounded staleness (~5s)Pequeno atraso é ok; ranking não precisa ser exato
Feed socialCausalOrdem reply-to importa; ordem global não
Carrinho de comprasEventual + CRDTGanho de disponibilidade > custo de resolver conflito
Cache de sessãoRead-your-writesUsuário precisa ver seus próprios updates
Auditoria/complianceLinearizabilityOrdem importa para auditor
AnalyticsEventualAggregates toleram delay e inconsistência minor

Implementando read-your-writes sem quebrar o banco

📋 App com primary Postgres + 3 read replicas async; equipe reporta 'atualizei mas ao voltar na página apareceu o antigo'

Cookie com LSN + routing para replica que replayou até o LSN

Mantém benefício de read replicas (escalabilidade), não força tudo no primary. Overhead mínimo — um cookie e um check de replay status.

Alt: Sticky to primary por N segundossimples; derrota o propósito da replica durante a janela

Alt: Strong consistency everywhereperformance tanks; não precisa disso para o resto das queries

💡
Em microserviços, esse mesmo princípio aplica em event-driven: guarde o offset de Kafka que seu serviço leu; garanta que downstream (projeções CQRS) já tenha processado esse offset antes de ler. É read-your-writes em versão event-sourced.

Conflict resolution: last-write-wins, CRDT e merge

EstratégiaComo funcionaRisco
Last-Write-Wins (LWW)Timestamp ganha o mais recente; perde outrosSilently drops writes; clock skew perigoso
Vector clocks + dequeue de conflitos para appIdentifica concorrência; app decide mergeComplexidade; exige UI de resolução
CRDT (Counter, OR-Set, LWW-Map, RGA)Estruturas matematicamente comutativas; merge automáticoOverhead de metadata; não serve para tudo
Operational Transform (OT)Transforma operações concorrentes para manter intençãoComplexo; usado em Google Docs legado
Semantic mergeRegras por tipo de dado (carrinho: união; saldo: soma de deltas)Trabalho de domínio; erro fica invisível
python
# OR-Set simples (CRDT) — adiciona e remove sem perder operações concorrentes
from dataclasses import dataclass, field
import uuid

@dataclass
class ORSet:
    adds:    dict[str, set[str]] = field(default_factory=dict)   # item -> set de uids
    removes: dict[str, set[str]] = field(default_factory=dict)

    def add(self, item: str) -> None:
        uid = str(uuid.uuid4())
        self.adds.setdefault(item, set()).add(uid)

    def remove(self, item: str) -> None:
        for uid in self.adds.get(item, set()):
            self.removes.setdefault(item, set()).add(uid)

    def value(self) -> set[str]:
        return {
            it for it, uids in self.adds.items()
            if uids - self.removes.get(it, set())
        }

    def merge(self, other: "ORSet") -> "ORSet":
        r = ORSet()
        for it in set(self.adds) | set(other.adds):
            r.adds[it] = self.adds.get(it, set()) | other.adds.get(it, set())
        for it in set(self.removes) | set(other.removes):
            r.removes[it] = self.removes.get(it, set()) | other.removes.get(it, set())
        return r

Perguntas típicas

Linearizability é o 'certo' e os outros são compromissos?

Errado olhar assim. Linearizability é o MAIS FORTE, mas tem custo fixo de latência que nem sempre é justificável. "Certo" é o modelo mais fraco que preserva os invariantes do seu domínio. Para um feed, causal basta. Para dinheiro, nada menos que linearizability.

Redis é linearizable?

Single-node sim. Redis Cluster não — é AP. Para garantias fortes em Redis distribuído, use Redis Sentinel/Cluster com cuidado e tolere read from stale replica em reads não-críticas. RedLock foi questionado como algoritmo de lock distribuído (Kleppmann 2016). Para coordenação séria, prefira etcd/ZooKeeper.

Como testar se meu sistema honra o modelo que eu afirmo?

Use Jepsen-style testing: inject partições, clock skew, pausas longas; rode workload com operações e verifique linearizability dos históricos. Elle e Jepsen são ferramentas consolidadas. Existem papers mostrando que MongoDB, Redis, Cassandra divergiram do que anunciavam em versões específicas — testar importa.

Cassandra LWW em texto livre é seguro?

Só se você pode perder a outra escrita sem bug de produto. Ex: update de "última posição GPS" — LWW serve. Update de "saldo" — NÃO. Em Cassandra, LWW decide por timestamp; clock skew entre nós causa escolhas erradas. Para campos com merge semântico (set de tags, counter), use tipos específicos (set, counter).
Take-aways. Consistência é uma escada. Linearizability é o topo, caro e raramente necessário. Causal é o sweet spot para sociais/colaboração. Session guarantees (read-your-writes, monotonic reads, bounded staleness) resolvem 80% das dores de UX sem custo global. Eventual sem session é armadilha. Resolução de conflito é projeto — LWW perde dados silenciosamente; CRDT salva em estruturas certas. Próximo módulo: como sistemas distribuídos concordam — Raft e consensus.
🧩

Quiz rápido

4 perguntas · Acerte tudo e ganhe o badge 🎯 Gabarito

Continue lendo