Day 47 of 100·System Design Day·557 words

Consistency models, replication, CAP and PACELC

The vocabulary of "how wrong can a read be" — mapped to your actual consistency windows.

Concept → your projects

Consistency models on a ladder (weakest→strongest): eventual (replicas converge, no bound) → read-your-writes (you see your own writes) → causal (ordering preserved for related ops) → linearizable (one total order; each read sees the latest write, as if a single register) → serializable (transactional).

CAP under partition: a partitioned network forces a choice — refuse writes (CP: consistent) or accept divergent writes (AP: available). Postgres-primary-failover debates are exactly this: automatic failover with async replication = AP window (promoted replica may lack recent commits → lost writes or split-brain); no automatic failover = CP (human intervention, downtime).

PACELC adds the non-partition case: else, choose latency or consistency — synchronous replication (stronger, +RTT per commit), asynchronous (faster, replication lag window). Every "we use async replicas" system is an EL choice, stated or not.

Map it to your systems (the exercise that makes this concrete)

Zoth — the layering you must be able to recite:

  • Decision layer (strong): single Postgres, all state transitions conditional-update CAS. A user never sees a half-transitioned request. Linearizable-enough for the domain (single-register semantics per request row).
  • Provisioning layer (eventual, bounded): approval → outbox row (same commit — atomic) → relay ≤5 s → RabbitMQ → emitter ≤seconds (or retries/DLQ). The window from "user sees ACTIVE" to "GitHub membership exists" is bounded and observable (outbox backlog age). Plus drift reconciliation every 5 min as the catch-all.
  • Read-your-writes subtlety: the approver sees ACTIVE immediately (strong layer); the end user checking GitHub sees eventual. Different observers, different models — state which observer a claim is about and you sound like you've operated distributed systems.

Traverse:

  • Writes strong: DB-first; response 201/202 only after commit. (Day 45's enqueue gap: post-commit work can be delayed/lost-then-reconciled — eventual for derived state.)
  • Reads: cache-aside with 120–600 s TTLs = bounded staleness, per-data-type (Day 43's table). Not read-your-writes! User submits, stats endpoint may lag minutes — accepted because stats are cosmetic. The revision schedule itself reads from DB (not cache) — the scheduler can't act on stale data; only projections of it are cacheable. Know which reads are which in your own system.
  • Queues: BullMQ at-least-once = eventual effects ordering, made safe by idempotency (Day 20).

Replication realities (neither project runs replicas — know the plan): Postgres streaming replication is physical WAL replay (Day 33); sync vs async = the PACELC latency choice per commit; failover tooling (Patroni) exists because split-brain is the AP failure — two primaries accepting writes. Your single-node honesty: "availability was traded for consistency-and-simplicity at the decision layer; the design's answer to node loss is recovery (WAL/PITR in traverse), not failover. The upgrade path is: streaming replica + Patroni for zoth's decision layer (CP with downtime-bounded failover), because an IAM control plane should refuse writes rather than diverge."

The interview trap to avoid

"CAP means you must choose C or A" — no: under partition. Normal operation can have both; PACELC's E/L is the everyday trade. Second trap: "eventual consistency means read-your-writes" — no: eventual says nothing about your own writes. Anchor with your concrete windows (5 s provisioning, 120–600 s read staleness) and you'll never sound like a quiz answer.

Bulletproof your resume

Say these answers aloud before moving on

These are the follow-ups an interviewer will actually ask about your projects.

01

Is zoth strongly or eventually consistent?

Layered: decisions strongly (single Postgres, CAS transitions), provisioning eventually with a bounded, observable window — the outbox commits atomically with the approval, relay publishes within 5 s, emitter lands within seconds, and drift detection reconciles within 5 minutes. So an approver sees ACTIVE immediately; GitHub membership converges in bounded time; and the bound is measurable via outbox backlog age, which is the operational answer to 'how eventual is your eventual?'

02

What does PACELC add over CAP?

The no-partition trade: replication is synchronous (consistency at +1 RTT per commit) or asynchronous (latency wins, replica lag window). CAP only speaks during partitions. My systems chose EL implicitly — single node, zero replication lag — and I can state what each upgrade costs: a sync replica adds an RTT per commit (fine for human-paced IAM), an async one adds a divergence window that failover may have to discard.

03

Would you run zoth's Postgres as AP with automatic failover?

No — an IAM control plane should be CP: refuse writes during a primary-loss window rather than risk two primaries granting conflicting access. Practically: streaming replica + Patroni with a conservative failover threshold, synchronous replication for the decision tables, and the outbox/emitter pipeline already tolerating the pause (backlog, retries). The cost is availability measured in failover seconds-minutes; the benefit is never having to explain divergent grants to an auditor.