Concept 19: CAP & PACELC — The Physics of Distributed Trade-offs
System Design Bible — File 19 Difficulty: ★★★★☆ Prerequisites: File 04 (Sharding — why data lives on many machines in the first place), File 08 (Consensus & Replication — replication models and the survey-level CAP tour that this file expands into its full treatment), File 11 (Databases — ACID, isolation levels, and what a transaction actually promises), File 18 (Distributed Consensus — quorums, linearizability, and why a CP system goes down during a partition), plus the fundamentals unpacked in Section 0.
Table of Contents#
- Prerequisites — What You Must Understand First
- Architectural Definition & "The Why"
- The Recursive Sub-Concept Tree
- Deep-Dive Mechanics — The Proof, and What a Partition Actually Feels Like
- The Three Choices — CP, AP, and the Myth of CA
- PACELC — The Half of the Trade-off You Feel Every Day
- The Consistency Model Ladder — Exhaustive Breakdown
- Tunable Consistency in Practice — The Knobs Real Databases Give You
- Beyond CAP — Harvest/Yield, CALM, CRDTs, and "Effectively CA"
- Pros, Cons & Trade-offs
- Interview Diagnostic Framework (Symptom → Solution)
- Real-World Engineering Scenarios
- Interview Gotchas & Failure Modes
- Whiteboard Cheat Sheet
- One-Sentence Summary for an Interviewer
0Prerequisites — What You Must Understand First#
CAP is the most cited and least understood idea in system design. Nearly every candidate can recite "consistency, availability, partition tolerance — pick two," and nearly none can say what any of the three words formally mean, which is why the recitation is worth zero points and often costs points. The five ideas below are what turn the slogan into a tool you can actually design with.
0.1What a network partition physically is (and the ugly shapes it takes)#
A network partition is any condition in which some messages between nodes are lost or delayed indefinitely. Note what that definition does not say: it does not require a cable to be cut, and it does not require a clean split into two halves. It is a statement about message delivery, not about topology.
The physical causes are mundane and constant at scale. A top-of-rack switch — the switch every server in a rack plugs into — fails, and 40 machines vanish from everyone else's view. A BGP misconfiguration (BGP, the Border Gateway Protocol, is how the internet's networks advertise which address ranges they can reach; a bad advertisement silently blackholes traffic) removes a datacenter from the internet. A firewall rule change drops traffic on one port in one direction. A NIC (network interface card — the hardware port that connects a server to the network) starts corrupting packets, so TCP checksums fail and every payload is silently retransmitted forever. Kubernetes network-policy changes, cloud provider inter-AZ link degradation, a fiber cut by a backhoe. Aphyr's "The Network Is Reliable" survey collected dozens of these from real postmortems; the empirical answer is that partitions are routine, not exotic — a large fleet sees them weekly.
Now the shapes, because assuming the clean two-halves picture is exactly how designs get broken:
Complete partition. The cluster splits into groups with no communication across the boundary. This is the textbook case and the easiest to handle, because majority quorums (File 18 §0.5) resolve it cleanly: one side has a majority and continues, the other side knows it doesn't and stops.
Partial partition. A can talk to B, and B can talk to C, but A cannot talk to C. There is no clean split — the graph is connected but not complete. This wrecks naive designs: B sees everyone as healthy and reports the cluster fine, while A and C each declare the other dead. Leader elections can flap forever as A and C alternately depose each other through B.
Asymmetric (one-way) partition. A's messages reach B, but B's replies never reach A. A sees a dead B; B sees a perfectly chatty A. Failure detectors built on "did I hear a heartbeat" produce contradictory conclusions on the two ends, which is how you get two nodes each convinced the other should step down.
Gray failure / partial-omission. Nothing is down — the link is just terrible. 30% packet loss, or latency spiked from 0.5 ms to 8 seconds. Every health check technically succeeds, eventually, so automated failover never triggers, but the system is unusable. Practitioners agree this is the worst failure mode, because "slow" is indistinguishable from "dead" (File 18 §0.2) and gray failures sit exactly in the ambiguous zone where your timeout is wrong no matter what you set it to.
Hold this: a partition is "some messages don't arrive," not "a cable was cut" — it is routine, frequently partial or one-way, and its nastiest form is a link that is merely slow, which is why you cannot design around partitions by assuming they are rare or clean.
0.2What "Available" means in CAP (it is much stricter than uptime)#
In the formal statement, availability means: every request received by a non-failing node must result in a (non-error) response. That's it — and every clause matters.
Every request, not 99.9% of them: the formal property admits no exceptions. Received by a non-failing node: a crashed server owes you nothing; availability is about what live nodes do. Must result in a response: an error is not a response, and — critically — hanging is not a response either. A system that blocks your write for 30 seconds waiting for a partition to heal is not available in the CAP sense, even if it eventually answers.
And note the two things the definition conspicuously omits: it says nothing about latency (a response after 10 seconds counts as available) and it says nothing about correctness (the response may be stale). Formal CAP availability is an extremely blunt instrument.
This is where the slogan misleads people most. Your business's "99.99% availability" SLA is a statistical property measured over time, with latency budgets and error budgets. CAP availability is an absolute property of every single request. So a system can be "CP" — sacrificing CAP-availability — and still hit four nines, because partitions that actually cost you a quorum are rare enough that the downtime fits inside the error budget. etcd is CP and Kubernetes control planes routinely run at four or five nines. Candidates who say "we can't use a CP system, we need high availability" have confused the formal term with the business term, and it is a very visible mistake.
Hold this: CAP-available means every live node answers every request without error and without hanging — an absolute, latency-blind, correctness-blind property, which is NOT the same thing as your uptime SLA; CP systems routinely deliver excellent real-world uptime.
0.3What "Consistent" means in CAP (it is not the C in ACID)#
The C in CAP is linearizability (File 18 §2.6), the strongest single-object consistency model: the system behaves as if there is exactly one copy of the data, and every operation takes effect instantaneously at some moment between its invocation and its response, in an order consistent with real time. The operational consequence: if a write completes at 10:00:00.000, every read that begins after that instant must observe it or something newer. No stale reads, ever, from any replica, for any client.
The C in ACID is a completely different animal: it means the database preserves your application's declared invariants — foreign keys, uniqueness constraints, CHECK conditions, "debits equal credits" — across a transaction (File 11 §7). It is about application-defined validity, not about replica agreement. A single-node database with no replicas at all has perfect ACID-C and the CAP question doesn't even arise for it.
Two vocabulary items complete the picture and are constantly needed in interviews:
Serializability is the strongest multi-object isolation level: the outcome of concurrent transactions equals some serial execution of them. It says nothing about real time — a serializable system may legally execute your transaction "in the past," returning stale data as long as the overall order is serial. Strict serializability = serializable + linearizable = "some serial order and it respects real time," and that's what Google Spanner means by external consistency.
So the precise mapping to say out loud: CAP's C is linearizability, a single-object real-time property about replicas; ACID's C is invariant preservation, an application property; serializability is about transaction interleaving; and strict serializability is the combination of the first and third. Getting this straight is one of the highest-signal things you can demonstrate on this topic.
Hold this: CAP-C = linearizability = "acts like one copy, respects real time." It is unrelated to ACID's C, and mixing them up is the single most common CAP error.
0.4Why the trade-off exists even when nothing is broken (the latency half)#
Here is the observation that makes PACELC necessary, and the reason CAP alone is an insufficient design tool.
Suppose you have replicas in Virginia and Frankfurt, and there is no partition at all — the network is perfectly healthy. A client writes to Virginia. Now: does the write return before or after Frankfurt has acknowledged it?
- If you wait for Frankfurt, the write is strongly consistent everywhere — and it costs ~90 ms, the round-trip time across the Atlantic, which is set by the speed of light in fiber (roughly 200,000 km/s, about two-thirds of light in vacuum) and cannot be optimized, purchased, or engineered away. Not by a better protocol, not by a bigger budget. It is physics.
- If you don't wait, the write returns in ~1 ms — and for some window, Frankfurt serves stale data.
That is the exact same consistency-versus-something trade-off, occurring during completely normal operation, and CAP does not describe it at all, because CAP only talks about the partition case. Yet this is the trade-off you live with 99.99% of the time. Every millisecond of user-visible latency has measured revenue impact (Amazon's oft-cited finding: 100 ms of added latency cost ~1% of sales), so this is not an academic concern.
Hold this: consistency costs coordination, coordination costs at minimum one round trip, and a round trip costs speed-of-light latency — so the consistency trade-off is present every single day, not only during partitions. That permanent, everyday half is exactly what PACELC adds and what makes it the more useful model.
0.5Replication and quorums — the machinery the trade-off operates on#
The trade-off only exists because there are copies, so you need the copy-management vocabulary. This is a compressed recap of File 08 §4 and File 18 §0.5; if these three shapes are unfamiliar, read those first.
Single-leader replication. One node accepts all writes and ships them to followers. Synchronous shipping (wait for followers before acknowledging) gives strong consistency but makes every write as slow as the slowest follower and blocks entirely if a follower is unreachable. Asynchronous shipping acknowledges immediately, giving low latency but a window in which followers are stale and, if the leader dies during that window, acknowledged writes are permanently lost.
Multi-leader replication. Several nodes accept writes (typically one per region) and replicate to each other. Local writes are fast everywhere — and concurrent writes to the same key on different leaders will conflict, so you must have a resolution policy, which is a design decision you cannot avoid.
Leaderless (Dynamo-style) replication. The client (or a coordinator on its behalf) writes to several replicas directly and reads from several, using the N/W/R parameters: N = how many replicas hold each key; W = how many must acknowledge a write; R = how many must respond to a read. The famous condition: if W + R > N, the read set and write set must overlap in at least one replica, so a read is guaranteed to encounter the latest acknowledged write. With N=3, W=2, R=2: 2+2 > 3, so any read quorum and any write quorum share a node. (Overlap guarantees the newest value is present in the responses; the system still needs versioning — timestamps or vector clocks, §2.9 — to tell which of the returned values is newest.)
Two supporting mechanisms you must be able to name because they are the machinery behind "eventual" actually converging: read repair (when a read discovers replicas holding different versions, the coordinator writes the newest back to the stale ones, healing on the read path) and anti-entropy (a background process where replicas compare their data — usually via Merkle trees, hash trees where each node hashes its children so two replicas can find their differing ranges in O(log n) comparisons instead of shipping everything — and reconcile differences).
Hold this: the CAP/PACELC choice is made concrete by where you put the leader and how you set N/W/R; "consistency vs availability" is not an abstraction but a literal decision about how many replicas you wait for.
1Architectural Definition & "The Why"#
1.1The Plain Definition#
The CAP theorem, conjectured by Eric Brewer in 2000 and proved by Seth Gilbert and Nancy Lynch in 2002, states:
In an asynchronous network, it is impossible for a distributed data store to simultaneously provide all three of: Consistency (linearizability, §0.3), Availability (every request to a live node gets a non-error response, §0.2), and Partition tolerance (the system continues to operate despite arbitrary message loss between nodes, §0.1).
And now the reframing that separates people who understand it from people who memorized it: "pick two of three" is wrong, or at best deeply misleading. Partition tolerance is not a design choice you get to decline. Networks partition (§0.1); if you "choose" not to tolerate partitions, what you have chosen is for your system to be broken or corrupted when one occurs, which is not an architecture, it's an oversight. So P is mandatory for any system spanning more than one machine, and the theorem's real content is:
When a partition occurs, you must choose: remain consistent and refuse to serve some requests (CP), or remain available and serve possibly-stale or conflicting data (AP). There is no third option.
The analogy that holds up under scrutiny: two bank branches with one shared ledger and a severed phone line. Customer walks into Branch A wanting to withdraw ₹50,000 from an account the branches jointly manage. Branch A cannot reach Branch B, so it cannot know whether the customer already withdrew at B five minutes ago. Exactly two policies exist. CP: "I cannot verify your balance; come back when the line is restored" — the customer is refused (unavailable) but the account can never be overdrawn (consistent). AP: "Here's your money, we'll sync later" — the customer is served (available) but if they withdrew at B too, the account goes negative and someone must reconcile it afterwards (inconsistent, requiring compensation).
Notice what the analogy makes obvious and the slogan hides: the right answer depends entirely on what's in the account. For a ₹500 withdrawal, banks historically chose AP and ate the rare overdraft, because refusing customers costs more than occasional overdrafts. For a ₹50 lakh wire transfer, they choose CP. The CAP choice is per-operation and per-business-rule, not per-database — and saying that in an interview is what a Staff-level answer sounds like.
1.2"The Why" — the physical facts that force this#
Three facts, each independently sufficient.
Information cannot travel faster than light, and a partition means it doesn't travel at all. During a partition, a node on side A physically cannot learn about a write that happened on side B. Not "doesn't yet" — cannot. So if A answers a read, its answer is either based on incomplete information (inconsistent) or it declines to answer (unavailable). There is no clever protocol that escapes this, because the constraint is on the movement of information, not on the software.
You cannot distinguish a partition from a slow network or a dead peer. From inside a node, all three look identical: silence (File 18 §0.2). So the node cannot even reliably know whether it is partitioned. It must act on a timeout — a guess. If a node ever decides "I'll just assume nobody else is writing," it is guaranteed to eventually be wrong, and that wrongness is split-brain (§2.6).
Reliability requires replication, and replication creates the possibility of disagreement. A single machine cannot be highly available — it has one power supply, one motherboard, one datacenter (File 08 §0.1). So you make copies. The instant copies exist, they can diverge. CAP is simply the formalization of the fact that you cannot have the fault-tolerance benefits of copies without inheriting the coordination costs of keeping them agreed.
Put together: replication is compulsory, partitions are inevitable, and information cannot cross a partition. The trade-off is therefore not an artifact of anyone's design — it is a property of the universe that every distributed system pays.
1.3What CAP/PACELC Solves — and Its Price#
| Problem it solves for you | Why the framework is the cure | How it shows up in practice |
|---|---|---|
| "Which database should we use?" answered by feature-checklist instead of failure behavior | Forces the real question: what does this system do when the network breaks? | Classifying candidates as PC/EC vs PA/EL (§5) before comparing anything else |
| Vendor claims of "highly available AND strongly consistent" | The theorem proves the claim is either qualified or false; you learn which qualification | Reading the fine print: Spanner is CP but its partitions are rare enough to be invisible (§8.4) |
| Teams arguing consistency vs speed with no shared vocabulary | Names the exact models (§6) so "consistent" stops being a mush word | "We need read-your-writes, not linearizability" ends a two-week argument |
| Designs that silently assume the network is reliable | P is non-optional; you must have a partition plan | Explicit partition-mode behavior and recovery/compensation design (§3.3) |
| Applying one consistency level to a whole application | The choice is per-operation, so you can mix | Cart = AP; checkout/payment = CP (§4.4) |
| Everyday latency that CAP alone can't explain | The ELSE branch of PACELC covers normal operation, which is 99.99% of the time (§0.4) | Choosing async cross-region replication and accepting a staleness window |
| "Eventual consistency" used as a synonym for "broken" | The ladder (§6) shows the useful models between "eventual" and "linearizable" | Adopting causal + session guarantees, which cover most user-visible weirdness cheaply |
The price the framework itself charges. CAP is a blunt model, and using it naively causes real design errors. It is binary (a system is CP or AP, with no notion of how stale or how unavailable), it assumes a single object (real applications touch many, and multi-object guarantees are a separate axis — File 11's isolation levels), it ignores latency entirely (§0.4 — the reason PACELC exists), it says nothing about partial availability (§8.1's harvest and yield), and its labels are applied to whole systems when real systems are tunable per-operation (§7). Treat CAP as the opening question, never the final answer: "CAP tells you the choice exists; PACELC tells you when it applies; the consistency ladder tells you what the actual options are."
2The Recursive Sub-Concept Tree#
2.1Sub-Concept: Linearizability (restated as the yardstick)#
Covered in §0.3 and File 18 §2.6; restated here because everything in §6 is measured against it. A system is linearizable if every operation appears to take effect atomically at a single instant between its call and its return, and that instant-order matches real time. The test question: if client A completes a write and then phones client B, can B's subsequent read possibly miss it? If yes, not linearizable.
It is a single-object property and it is composable — if every object in your system is individually linearizable, the whole system is linearizable — which is a nice property serializability does not have and which is why linearizability is the natural yardstick for CAP.
2.2Sub-Concept: Eventual Consistency#
Eventual consistency is the weakest useful guarantee: if writes stop, all replicas eventually converge to the same value. It is a liveness property (File 18 §0.3) — it promises something good happens someday, and it deliberately says nothing about when. Formally, "eventually" could be a year and the system is still compliant.
That vagueness is the honest criticism, and you should voice it: eventual consistency alone is nearly useless as a contract. What makes it practically usable is the pile of things layered on top: convergence mechanisms that make "eventually" mean typically milliseconds (read repair and anti-entropy, §0.5; hinted handoff, §2.8), a conflict-resolution policy so convergence is deterministic (§2.9), and session guarantees (§6.6) so a single user never sees their own writes vanish. In an interview, never say "we'll use eventual consistency" and stop. Say what converges it, how long the window is, and how conflicts resolve.
2.3Sub-Concept: BASE (the deliberate counterweight to ACID)#
BASE — Basically Available, Soft state, Eventually consistent — was coined as a knowing pun on ACID and describes the AP design philosophy.
Basically Available means the system always responds to requests, possibly with degraded or stale data rather than an error. Soft state means a node's state may change over time even without new writes, because replication and repair are still propagating — the state is not "settled" the way a committed ACID transaction's is. Eventually consistent is §2.2.
The trade-off, stated plainly: BASE buys you availability under partition and low latency always; it charges you application complexity, because the burden of handling stale reads, conflicts, and compensation moves out of the database and into your code. That transfer is often worth it (a shopping cart that never rejects an "add" is worth some merge logic) and often catastrophic (an inventory counter that goes negative is not worth any amount of availability). The senior framing: ACID vs BASE is not a quality ranking, it's a decision about where the complexity lives — inside the database, or inside your application.
2.4Sub-Concept: Staleness, and how to measure it#
Staleness is the gap between the newest committed value and what a read returns, and it's the missing dimension in CAP's binary framing. It has two units: time ("this read may be up to 500 ms behind") and versions ("this read may be up to 3 writes behind"). Real systems track it as replication lag — how far a follower trails its leader — which is the single most important metric on any asynchronously replicated system, because it is your inconsistency window, measured.
Why it matters: nobody actually needs "never stale"; they need "not stale enough to matter for this use case." A follower 200 ms behind is fine for a news feed, fine for analytics, and utterly unacceptable for a "did my payment go through" check. Making staleness a number you monitor rather than a binary you argue about is what turns this topic into engineering. Azure Cosmos DB even makes it a first-class contract — bounded staleness (§6.7) lets you specify the maximum lag in versions or seconds.
2.5Sub-Concept: Partition Tolerance (what it does and doesn't require)#
Partition tolerance means the system continues to function — as CP or AP — when arbitrary messages between nodes are lost, and returns to normal when the partition heals. It does not mean "the system keeps serving everyone during a partition" (that would be availability) and it does not mean "partitions don't happen."
What it concretely requires you to build: detection (recognizing you may be partitioned — timeouts, failed heartbeats, quorum loss), a defined partition mode (an explicit, designed behavior for the degraded state, rather than whatever the code happens to do), and recovery (merging state and compensating for anything wrong you did while degraded). §3.3 walks all three. A system that has never thought about partition mode is not P-tolerant; it just hasn't been tested yet — which is exactly what Jepsen (§2.11) exists to demonstrate.
2.6Sub-Concept: Split-Brain and Its Costs#
Split-brain is what AP looks like at its worst: a partition leaves two sides each accepting writes to the same logical data, so the data forks into two divergent histories. When the partition heals, you have two versions of reality and no automatic way to say which is right.
The costs are concrete. Lost updates — if you resolve by picking one side, the other side's writes are simply gone, and they were acknowledged to users. Constraint violations — both sides sold the last concert seat, both sides registered the same username, both sides allowed the withdrawal. Manual reconciliation — someone reads two ledgers at 3 a.m. and decides.
Defenses, in increasing strength: quorums (only a majority side may write — this is the CP answer and it eliminates split-brain by construction, File 18 §0.5); fencing tokens (the resource rejects writes carrying a stale epoch, File 18 §2.9); conflict-free data types (§8.2 — make the merge mathematically well-defined so a fork is harmless); and compensation (allow the fork, detect it later, and issue a business-level correction — the refund, the apology email, the restock).
2.7Sub-Concept: Quorum Overlap, and What It Does Not Give You#
From §0.5: W + R > N forces the read set and write set to share at least one replica, so a read always sees the latest acknowledged write among its responses.
Now the subtlety that interviewers use as a filter: quorum overlap alone does not give you linearizability. Three concrete gaps. (a) A write that fails partway — acknowledged by 1 replica when W=2 — is neither committed nor rolled back; a subsequent read may or may not see it, and two consecutive reads may disagree. (b) Concurrent writes need a rule to order them; if that rule is last-writer-wins by wall clock, clock skew (File 17 §0.3) can silently discard the actually later write. (c) Sloppy quorums (§2.8) break the overlap guarantee outright. Dynamo-style systems are explicit that they provide eventual, not linearizable, consistency even at W+R>N; getting linearizability requires read-repair-before-return plus a real consensus protocol underneath, which is what Cassandra's SERIAL consistency level (Paxos-based lightweight transactions) does at roughly 4× the latency of a normal write.
2.8Sub-Concept: Sloppy Quorums and Hinted Handoff#
A strict quorum requires the W acknowledgements to come from the N replicas that actually own the key. A sloppy quorum relaxes this: if some of the home replicas are unreachable, the write is accepted by whatever N healthy nodes are available, even ones outside the key's home set.
Hinted handoff is the companion mechanism: the stand-in node stores the write along with a hint — "this really belongs to node C" — and when C returns, the hint is delivered and the stand-in deletes its copy.
Pro: durability and write availability survive even when most home replicas are down, which is exactly what Dynamo wanted (Amazon's rule was that the shopping cart's "add to cart" must never fail). Con — and say this before the interviewer does: with a sloppy quorum, W + R > N no longer guarantees overlap, because the W writes may have landed on nodes that the R reads never contact. You have traded the read-your-write guarantee for write availability. That is a legitimate trade for a cart and an illegitimate one for a balance.
2.9Sub-Concept: Conflict Resolution — LWW, Vector Clocks, and Merge Functions#
If two replicas accepted different writes to one key, convergence requires a deterministic rule. Three families:
Last-Writer-Wins (LWW). Attach a timestamp; the highest wins. Pro: trivial, O(1) metadata, no client involvement — Cassandra's default. Con: it silently discards data, and if clocks are skewed the "winner" may genuinely be the older write. When: the value is a whole-object overwrite where losing one of two concurrent updates is acceptable (a user's profile theme), never for anything additive.
Version vectors / vector clocks. Each replica keeps a counter per replica; a value carries the vector of counters it saw. Comparing two vectors tells you whether one causally descends from the other (every counter ≥, so it's strictly newer — keep it) or whether they are concurrent (each has some counter higher — a genuine conflict). Pro: it never loses a write silently; it detects real conflicts precisely rather than guessing. Con: metadata grows with the number of writing replicas, and detecting a conflict means someone must resolve it — Dynamo pushed siblings (multiple concurrent versions) back to the client, and application authors hated it. When: correctness matters more than simplicity and the application genuinely can merge (Amazon's cart merged sibling carts by union, which is why a deleted item could reappear — a famous, deliberate bug).
Application-defined merge / CRDTs. Define the data type so merging is mathematically total: a grow-only counter merges by max per replica, an OR-set merges by union with tombstones for removals. Pro: conflicts become impossible by construction, so AP is safe. Con: only works for operations that commute, and metadata (tombstones especially) accumulates. Full treatment in §8.2.
2.10Sub-Concept: Read-Your-Writes and the Session Guarantees#
The user-visible symptoms of weak consistency are almost never "the data was mathematically non-linearizable"; they are four specific complaints, and each has a named, cheap fix. These are the session guarantees (Terry et al., from the Bayou project):
Read-your-writes (RYW): you always see your own writes. Symptom without it: you post a comment, the page reloads from a lagging follower, your comment isn't there, you post it again. Fix: route a user's reads to the leader for a period after they write, or route by sticky session, or pass the write's version and require the replica to have caught up.
Monotonic reads: you never see time move backwards. Symptom without it: refresh shows a comment, refresh again (different replica, further behind) and it's gone. Fix: pin a client to one replica, or carry a "highest version seen" token and refuse replicas behind it.
Monotonic writes: your own writes apply in the order you issued them. Symptom without it: you rename a file then delete it, and the delete lands first on some replica, leaving a renamed file forever.
Writes-follow-reads (session causality): if you read X and then write Y in response, everyone who sees Y also sees X. Symptom without it: a reply appears before the message it replies to.
The single most valuable practical takeaway in this file: most "eventual consistency is unusable" complaints are actually session guarantee failures, and session guarantees are dramatically cheaper than linearizability — they're implemented with sticky routing and version tokens, not with consensus round trips. Offer this in an interview and you will look like someone who has shipped systems: "I don't need linearizability here; I need read-your-writes and monotonic reads, and I'll get them with sticky sessions plus a version token."
2.11Sub-Concept: Jepsen (how these claims get tested)#
Jepsen is Kyle Kingsbury's testing framework that runs a real database cluster, injects real faults (partitions with iptables, process pauses, clock skew, node kills), records a history of client operations, and then checks the recorded history against a formal consistency model — asking, for instance, "is this history linearizable?" It matters because a decade of Jepsen reports found that a large fraction of databases did not provide the guarantees their documentation claimed, especially under partition and clock skew.
Two things this buys you in an interview: the vocabulary to say "the claimed guarantee and the verified guarantee are different things — has it been Jepsen-tested?", and the humility to distrust your own untested partition-handling code. Real systems now advertise Jepsen results as a feature.
3Deep-Dive Mechanics — The Proof, and What a Partition Actually Feels Like#
3.1The Gilbert–Lynch proof, in six lines#
The formal proof is short enough to reproduce on a whiteboard, and doing so is a strong signal.
Assume a system with two nodes, N1 and N2, both replicating value V, initially V0. Assume it is consistent (linearizable), available, and partition tolerant. Now partition N1 from N2 — every message between them is lost.
- A client writes V1 to N1. N1 must accept it (availability requires a non-error response).
- N1 tries to replicate to N2. All messages are lost (partition).
- Another client reads from N2. N2 must respond (availability).
- N2 has no way to know about V1 — no information crossed the partition — so it returns V0.
- The read completed after the write completed, and returned an older value. That violates linearizability.
- Therefore the three properties cannot hold simultaneously. ∎
The proof's shape is the lesson: the contradiction comes from step 4 being forced by physics, not by implementation. Any system in that position must either make N1 refuse the write / N2 refuse the read (sacrificing availability — CP), or return V0 (sacrificing consistency — AP).
3.2The partition timeline — what each side does#
t0: HEALTHY t1: PARTITION t2: HEAL & RECOVER
┌────────┐ sync ┌────────┐ ┌────────┐ ╳ ┌────────┐ ┌────────┐ ┌────────┐
│ N1 V0 │◄─────────►│ N2 V0 │ │ N1 │ ╳ │ N2 │ │ N1 │ │ N2 │
└────────┘ └────────┘ └────────┘ ╳ └────────┘ └────────┘ └────────┘
│ │
══ CP PATH (majority side wins) ══════════════════════════════════════════════
N1 side has quorum (2 of 3) │ N1: accept W(V1) N2: minority →
│ commit REFUSE all reads+writes
│ (error, not hang)
Client on N2 side: ERROR "no quorum, retry" ← available? NO. correct? YES.
Heal → N2 catches up from N1's log → zero divergence, nothing to reconcile.
══ AP PATH (both sides serve) ═════════════════════════════════════════════════
N1: accept W(V1) locally │ N2: accept W(V2) locally, serve stale V0
Both clients get 200 OK │ ← available? YES. correct? NO.
Heal → TWO versions of the key exist → resolve:
LWW (pick higher timestamp, LOSE the other write) §2.9
or vector clocks (detect concurrent → return siblings) §2.9
or CRDT merge (union/max → no loss, by construction) §8.2
+ business compensation for anything already acted on §3.3Read the diagram as two commitments, not two implementations. CP commits to "I will never be wrong, and I will sometimes be down." AP commits to "I will never be down, and I will sometimes be wrong — and I own the cleanup." The cleanup is real work that must be designed before the partition, which is what the next subsection is about.
3.3Brewer's three-step partition strategy (the practical mechanics)#
In "CAP Twelve Years Later" (2012), Brewer reframed CAP away from "pick two" into an operational recipe. This is the most useful thing to know about CAP in practice, and almost nobody brings it up in interviews.
Step 1 — Detect the partition. You cannot handle what you don't notice. Mechanisms: heartbeat timeouts, loss of quorum (File 18), replication lag exceeding a threshold, health checks failing across an AZ boundary. Every detector is a guess built on a timeout (§0.1's gray failures make this genuinely hard), so tune it as in File 18 §0.2 and — critically — make entry into partition mode explicit and observable, so operators see "we are degraded" rather than inferring it from mysterious latency.
Step 2 — Enter an explicit partition mode, limiting operations by risk. This is where the per-operation nature of the choice (§1.1) cashes out. You do not put the whole system in one mode. You classify operations by the cost of being wrong:
- Operations safe to continue — reads of rarely-changing data, appends to logs, adds to a cart, "like" counts, analytics events. These commute or tolerate staleness, so serve them (AP behavior) and mark responses as potentially stale.
- Operations to restrict — anything enforcing a global invariant: uniqueness, non-negative inventory, "at most one," money movement. Refuse these (CP behavior) with a clear error, or degrade them into a pending state that a human or a later process confirms.
- Operations to make safe by pre-partitioning the resource — the classic trick: split the invariant so each side owns a disjoint slice. If you have 100 units of inventory and two sides, give each side 50 to sell independently. Neither can oversell; the cost is that each side might report "sold out" while the other has stock. This converts a global invariant into two local ones and is the single most elegant partition-mode technique. Airlines do exactly this with seat inventory per booking system; ATM daily withdrawal limits are the same idea (the bank caps its risk at ₹X per customer per day, then happily runs AP).
Step 3 — Recover and compensate. When the partition heals, you must (a) merge the divergent state, using the resolution rules of §2.9 or CRDT merges of §8.2 — and log every conflict rather than silently resolving, because silent LWW is how data disappears without a postmortem; and (b) compensate for invariant violations that already happened. Compensation is a business action, not a database action: refund the double-charged customer, restock or apologize for the oversold item, cancel the duplicate booking with a voucher, send the correction email. Airlines overbook deliberately and compensate with vouchers; that is a CAP partition-mode strategy formalized as a business policy.
The senior sentence: "Partition tolerance isn't a checkbox — it's detect, degrade per-operation by risk, then merge and compensate. If a design has no compensation story, it hasn't actually chosen AP; it has chosen 'be wrong and hope.'"
4The Three Choices — CP, AP, and the Myth of CA#
Full RULE 4 treatment. The canonical taxonomy here is small but each member has real internal variety.
4.1CP — Consistent and Partition-tolerant#
What it is. During a partition, the system refuses requests it cannot serve correctly. Correctness is preserved absolutely; availability is sacrificed on the minority side.
Mechanism. Majority quorums (File 18 §0.5). Writes require a majority of replicas to acknowledge, so only one side of any partition can ever commit — the majority side. The minority side, on detecting it lacks quorum, returns errors rather than stale data. Linearizable reads additionally require confirming leadership (ReadIndex or a lease, File 18 §5.3), so a partitioned leader stops serving reads too.
Worked example. 5-node cluster split 3–2. The 3-side elects/keeps a leader, commits writes normally, and serves linearizable reads: fully operational. The 2-side cannot reach 3 acknowledgements, so every write fails fast with "no quorum" and every linearizable read fails too. When the partition heals, the 2-side replays the leader's log and catches up. Divergence: zero. Reconciliation work: none.
Why it works. Two majorities cannot be disjoint, so at most one side can commit — a single history is preserved by arithmetic, not by hope.
Pros. Application code is dramatically simpler: no conflict resolution, no compensation logic, no "is this value stale?" reasoning anywhere in your business logic. Invariants (uniqueness, balances, "at most one owner") are enforceable. Recovery after a partition is automatic and lossless. Debugging is tractable because there is one history.
Cons. The minority is hard down for writes and linearizable reads — for a 3-node cluster, losing 2 nodes means total write outage. Every write pays a majority round trip plus a durable flush (File 18 §7.1), so ~1–2 ms in-region and 100 ms+ cross-region. Write throughput does not scale by adding nodes. Quorum loss requires an unsafe manual recovery.
Complexity/cost. 1 network RTT + 1 fsync per write; availability during a partition = only the majority side; tolerates ⌊(N−1)/2⌋ failures.
When to use it. Anywhere an invariant must never be violated: account balances and ledgers, inventory and seat reservations, unique identifiers and usernames, cluster metadata and leader election, configuration, locks, and any control plane. Rule of thumb: if being wrong requires a human to clean up, choose CP. Real systems: etcd, ZooKeeper, Consul, HBase, Spanner, CockroachDB, MongoDB with majority write concern, Kafka with acks=all + min.insync.replicas.
4.2AP — Available and Partition-tolerant#
What it is. During a partition, every reachable node keeps answering, serving possibly-stale data and accepting writes that may conflict, with reconciliation deferred to after the heal.
Mechanism. Leaderless or multi-leader replication (§0.5) with low W and R, sloppy quorums and hinted handoff (§2.8) so writes land somewhere even when home replicas are unreachable, then convergence through read repair and anti-entropy (§0.5) with an explicit conflict-resolution rule (§2.9).
Worked example. Cassandra, N=3, W=1, R=1, split 2–1. A client on the 2-side writes cart={A,B}; it is acknowledged by one node. A client on the 1-side writes cart={A,C}; also acknowledged. Both users got a 200. After the heal, read repair and anti-entropy discover the divergence. With default LWW, the higher timestamp wins and one user's item vanishes; with an application-level union merge (Dynamo's approach), the cart becomes {A,B,C} and no addition is lost — at the cost that a previously removed item can resurrect.
Why it works. Nothing forces coordination, so nothing can block. Correctness is recovered afterwards — if your merge rule is sound.
Pros. Serves through partitions, node failures, and whole-AZ loss with no write outage. Writes go to the nearest replica, so latency is low and uniform globally — a Tokyo user writes to Tokyo. Throughput scales close to linearly with nodes because there is no leader bottleneck and no coordination round trip.
Cons. Stale reads are normal, not exceptional. Concurrent writes conflict, and every resolution strategy has a failure mode: LWW silently loses data, vector clocks push siblings into your application, CRDTs constrain your data model. Global invariants are simply unenforceable — you cannot prevent overselling in an AP store, only detect and compensate. And the complexity does not disappear; it moves into your application, where it is written repeatedly by many teams instead of once by database authors.
Complexity/cost. Sub-millisecond local writes; convergence typically milliseconds to seconds under normal conditions, bounded only by "eventually" under a long partition; extra storage and CPU for versioning metadata, tombstones, and anti-entropy.
When to use it. Where staleness is cheap and unavailability is expensive, and operations naturally commute: shopping carts, social feeds and timelines, likes/views/counters, presence and status, session state, product catalogs, telemetry ingestion, caches. Rule of thumb: if being briefly wrong is invisible or self-correcting, choose AP. Real systems: Cassandra, DynamoDB (eventual reads), Riak, CouchDB, Voldemort, DNS, and essentially every CDN (File 05).
4.3"CA" — The Category Error#
What it claims to be. Consistent and available, "not partition tolerant."
Why it's a myth, precisely. Partition tolerance is not a property you implement — it is a property of the environment. In any system spanning more than one machine, partitions will happen. A system that is not partition-tolerant does not gain consistency or availability when one occurs; it simply has undefined behavior — it corrupts, splits its brain, or hangs. So "CA" describes not a design point but the absence of a plan.
The one honest CA system: a single node, where there is no network between replicas to partition. A standalone PostgreSQL instance is genuinely CA — and genuinely has a single point of failure, which is why you replicate it, which is the moment CAP starts applying to you.
The trap in interviews. Someone says "we use PostgreSQL, so we're CA." The correct response: "A single Postgres is CA because there's nothing to partition. The moment you add a replica, you've made a CAP choice — synchronous replication with automatic failover on quorum makes you CP; asynchronous replication with promote-on-failure makes you AP, and its cost is losing whatever writes hadn't shipped when the primary died." That answer, delivered calmly, is worth more than any amount of theorem recitation.
The nuanced version worth knowing (§8.4): systems like Spanner are sometimes called "effectively CA" — not because they defy the theorem, but because Google engineered the partition probability so low (redundant private fiber, multiple independent paths) that CAP's forced choice occurs rarely enough to be a rounding error in the availability budget. They are still formally CP; they just fall over so seldom that users never notice the C-over-A choice being exercised.
4.4The choice is per-operation, not per-system#
The most valuable practical point in this section. One application legitimately contains both:
Take an e-commerce checkout. Browse the catalog — AP: a stale price for 200 ms is harmless, and refusing to show products costs sales. Add to cart — AP: a cart write must never fail; merge conflicts by union. Check inventory — AP with pre-partitioned allocation (§3.3 step 2): each region holds a slice of stock and sells only from its own. Place the order and charge the card — CP: this is money, it must be exactly-once (File 15's idempotency keys plus a consensus-backed ledger), and refusing the order is far better than double-charging. Order history — AP with read-your-writes (§2.10) so the customer sees their own order immediately.
Say it like this: "CAP applies per operation. I'd run the catalog and cart AP on Dynamo-style storage for availability, and the payment ledger CP on a consensus-backed store for correctness, because the cost of a stale price is nothing and the cost of a double charge is a chargeback plus a customer."
4.5Comparison Table#
| CP | AP | "CA" | |
|---|---|---|---|
| During partition | Minority side errors out | All sides answer | Undefined / corrupts |
| Consistency model | Linearizable (§2.1) | Eventual + session guarantees (§2.2, §2.10) | Linearizable (until a partition) |
| Write latency (same region) | ~1–2 ms (majority RTT + fsync) | ~0.5 ms (local replica) | ~0.5 ms |
| Write latency (cross-region) | 60–180 ms (physics, §0.4) | ~1 ms local, async propagation | N/A |
| Conflicts possible? | No — single history | Yes — must define resolution (§2.9) | No |
| App complexity | Low (DB owns correctness) | High (app owns merge + compensation) | Low |
| Enforces global invariants? | Yes | No — detect + compensate only | Yes |
| Scaling writes | Shard into many groups (File 18 §7.4) | Add nodes, near-linear | Vertical only |
| Recovery after partition | Automatic, lossless log catch-up | Merge + repair + business compensation | Manual, risky |
| Real systems | etcd, ZooKeeper, Spanner, CockroachDB, HBase, MongoDB (majority) | Cassandra, DynamoDB (eventual), Riak, CouchDB, DNS, CDNs | Single-node Postgres/MySQL |
| Best for | Money, inventory, identity, metadata, locks, control planes | Carts, feeds, counters, presence, sessions, catalogs, telemetry | One machine, or a prototype |
4.6The Decision Rule#
Say this, in this order:
- "Is this operation's invariant global?" If correctness requires knowing about all other operations — uniqueness, non-negative balance, at-most-one — you need CP. There is no AP design that enforces a global invariant; you can only detect violations afterwards.
- "Does the operation commute?" If concurrent updates can be merged with a rule that loses nothing (add-to-set, increment, union), AP with CRDTs (§8.2) gives you availability and correctness, because there is nothing to conflict over. This is the best outcome available and you should look for it first.
- "What does being stale cost, in currency?" Quantify it. A stale like count costs zero. A stale price costs a small margin error. A stale balance costs a chargeback and a regulator. Compare that number to the cost of downtime for the same operation, and the answer usually becomes obvious without any theorem.
- "Can I pre-partition the invariant?" (§3.3) Splitting 100 units of stock into 50+50 per region converts a global constraint into two local ones and lets you run AP safely. Always check for this — it is the cleverest move in the space.
- "Do I actually need linearizability, or just session guarantees?" (§2.10) Most complaints about eventual consistency are read-your-writes failures. Sticky routing plus a version token costs almost nothing and eliminates most of the user-visible weirdness.
- Default: CP for the control plane and anything financial or identity-related; AP for the data plane, user-generated content, and telemetry. Then justify each deviation.
5PACELC — The Half of the Trade-off You Feel Every Day#
5.1The formulation#
PACELC (Daniel Abadi, 2010) extends CAP with the case CAP ignores — normal operation:
If there is a Partition (P), a system trades between Availability (A) and Consistency (C); Else (E) — when the system is running normally — it trades between Latency (L) and Consistency (C).
Read as a formula: if (P) then (A or C) else (L or C), giving four classifications: PA/EL, PA/EC, PC/EL, PC/EC.
Why this is the better model, in one sentence: partitions are rare and the ELSE branch is where you live 99.99% of the time, so a framework that only describes the partition case is describing the exception and ignoring the rule. Every synchronous replication decision, every "read from the leader or a follower?" choice, every cross-region write is an EL-vs-EC decision, made thousands of times per second, forever.
5.2The four quadrants, with real systems#
PA/EL — available under partition, latency-optimized normally. The maximum-availability, maximum-speed corner: during a partition all sides serve; normally, writes acknowledge locally without waiting for remote replicas. Systems: Cassandra (with low consistency levels), DynamoDB with eventually consistent reads, Riak, Voldemort, CouchDB. Trade-off: you accept staleness and conflicts always, and you accept them consciously — Dynamo was built for Amazon's shopping cart, where a rejected write costs a sale and a merged cart costs nothing. Choose when: availability and latency are revenue, and the data commutes or tolerates staleness.
PC/EC — consistent always, at both latency and availability cost. The maximum-correctness corner: during a partition the minority refuses service, and normally every operation still pays full coordination. Systems: Google Spanner, etcd, ZooKeeper, HBase, CockroachDB (default), VoltDB, MongoDB with majority write and read concerns. Trade-off: every write costs a quorum round trip forever, and cross-region deployments pay speed-of-light latency on every operation. Choose when: the data is money, identity, inventory, or cluster metadata — and note that this is the correct default for a control plane, which is small, low-throughput, and catastrophic to get wrong.
PA/EC — available under partition, but consistent when healthy. A pragmatic and underrated corner: run strongly consistent while the network is fine, and deliberately degrade to available-and-stale when partitioned. Systems: MongoDB in some configurations (async replication with automatic failover), and many hand-rolled architectures that use synchronous replication plus a "if we lose quorum, serve reads stale" fallback. Trade-off: you get full consistency normally, but your partition behavior is a degradation that the application must recognize and handle, which is easy to leave untested until the day it fires. Choose when: you want strong consistency as the norm but a total write outage is unacceptable — and you have actually built and tested the degraded path.
PC/EL — consistent under partition, latency-optimized when healthy. The rarest and most confusing corner: refuse service rather than diverge during a partition, but relax consistency for speed during normal operation. Systems: PNUTS (Yahoo's system, the canonical example — it gives per-record timeline consistency with a master per record; reads can be local and stale, but the record master is definitive), and single-leader databases with async followers plus quorum-based failover — followers serve fast stale reads normally (EL), while failover requires quorum so you never split-brain (PC). Trade-off: users can read stale data every day even though the system is uncompromising during partitions, which surprises people. Choose when: fast local reads matter, staleness is tolerable, but split-brain absolutely is not — a very common real shape, e.g., MySQL with async read replicas plus an Orchestrator/Raft-based failover.
5.3The quadrant table#
| System | Partition behavior | Normal behavior | Class | Why it chose this |
|---|---|---|---|---|
| Google Spanner | Minority refuses (Paxos) | Every write is a quorum commit + commit-wait | PC/EC | Financial-grade correctness across the globe; Google can afford custom clocks and private fiber |
| etcd / ZooKeeper / Consul | Minority refuses | Quorum writes; linearizable reads via ReadIndex/lease | PC/EC | Control-plane metadata — small, precious, must be singular |
| CockroachDB (default) | Minority range unavailable | Raft quorum per range | PC/EC | SQL semantics with survivability; tunable to follower reads (EL) per query |
| Cassandra (CL=ONE) | All sides serve | Local ack, async propagation, read repair | PA/EL | Availability and write throughput at scale; app owns merge |
| DynamoDB (eventual reads) | All sides serve | Local read, ~1 ms, may be stale | PA/EL | Amazon's rule: the cart never rejects a write |
| DynamoDB (strong reads) | Errors on unavailable partition | Reads route to the leader replica; higher latency and 2× cost | PC/EC | Same database, different per-request choice — proof the classification is per-operation |
| Riak | All sides serve, siblings created | Tunable N/W/R, vector clocks | PA/EL | Faithful Dynamo lineage |
| MongoDB (w:majority, readConcern:majority) | Minority refuses | Majority ack per write | PC/EC | Correctness-first configuration |
| MongoDB (w:1, async secondaries) | Primary side serves; failover may lose writes | Fast local ack | PA/EL | Speed-first configuration |
| PNUTS (Yahoo) | Record master refuses if unreachable | Local stale reads permitted | PC/EL | Geo-replication with per-record mastership |
| MySQL + async replicas + quorum failover | Failover requires quorum; no split-brain | Replicas serve stale reads fast | PC/EL | The most common real-world enterprise shape |
| Azure Cosmos DB | Configurable | Five selectable levels (§7.3) | Tunable | Sells the trade-off as a product SKU with SLAs on each level |
The interview move: when asked "is X CP or AP?", answer with the PACELC class and note that most modern systems are tunable per request, so the honest answer is "it depends on the consistency level you configure — here's what each buys you." That is the difference between a memorized label and understanding.
6The Consistency Model Ladder — Exhaustive Breakdown#
CAP's binary is a lie of simplification: between "linearizable" and "eventual" sits a ladder of models, each with a real cost and a real use case. Full RULE 4 treatment per rung, strongest first. This is the canonical taxonomy — strict serializable, linearizable, sequential, causal(+), the four session guarantees, bounded staleness, consistent prefix, eventual.
6.1Strict Serializability (the ceiling)#
What it is. Serializable (transactions equal some serial order) plus linearizable (that order respects real time). The gold standard for multi-object transactions.
How it works. Two-phase locking or MVCC for serializability (File 11 §7), plus a globally meaningful timestamp order for the real-time part. Spanner does it with TrueTime plus commit-wait: pick a timestamp, wait out the clock uncertainty (~7 ms) before releasing locks, guaranteeing later transactions get strictly larger timestamps.
Why it works. Real-time-respecting timestamps make "happened before" globally comparable, so no observer can see an inversion.
Pros. Your distributed database behaves exactly like a single-threaded single machine — the mental model application developers already have. Zero anomalies to reason about.
Cons. The most expensive point on the ladder: quorum commit + 2PC for multi-shard transactions (File 18 §6.1) + commit-wait, and cross-region deployments pay the full speed-of-light penalty per transaction.
Complexity. ≥1 quorum RTT, plus 2PC across shards, plus clock-uncertainty wait; typically 10–100 ms cross-region.
When to use it. Financial ledgers, double-entry accounting, regulatory systems, anything auditable. Spanner, CockroachDB, FoundationDB.
6.2Linearizability (single-object, real-time)#
What it is. §2.1 — acts like one copy, respects real time, one object at a time.
How it works. All operations for a key funnel through a consensus group's ordered log (File 18); reads confirm current leadership via ReadIndex or a lease (File 18 §5.3).
Why it works. A single total order per object, with reads forced to observe the committed prefix, leaves no room for a stale answer.
Pros. Composable — per-object linearizability yields system-wide linearizability. Enables compare-and-set, locks, and unique-registration primitives.
Cons. No cross-object atomicity (two individually-linearizable objects can still be updated non-atomically). Every operation costs a quorum round trip. Unavailable in the minority during partitions.
Complexity. 1 RTT + fsync per write; 1 heartbeat round or a lease for reads.
When to use it. Locks, leader election, unique-ID or username claims, config values, feature flags that must flip atomically, inventory counters when combined with compare-and-set.
6.3Sequential Consistency#
What it is. All nodes observe operations in the same total order, and each individual client's operations appear in the order that client issued them — but that global order need not match real time. An operation may appear to take effect "late."
How it works. A single agreed order (a totally-ordered log again), but without forcing reads to observe the latest committed position — a replica can lag and still be sequentially consistent as long as it never reorders.
Worked example. Alice writes X=1 at 10:00:00. Bob reads X at 10:00:01 and gets the old value. That is not linearizable (real time violated) but is sequentially consistent, as long as Bob never later sees an order in which X=1 precedes something it shouldn't. The everyday intuition: everyone watches the same movie, some people are a few frames behind.
Pros. Cheaper than linearizability — replicas can serve reads without confirming leadership — while still giving a single agreed history, which is far easier to reason about than eventual consistency.
Cons. The "stale but ordered" behavior violates user expectations in cross-channel scenarios: Alice writes, phones Bob, Bob reads stale. Rarely offered as a product-level setting, which limits its practical use.
When to use it. Mostly a teaching rung and a hardware-memory-model concept (CPU memory models are sequentially consistent or weaker). Know it to explain why linearizability's real-time clause is the expensive part.
6.4Causal Consistency#
What it is. Operations that are causally related are seen in the same order by everyone; concurrent operations may be seen in different orders by different observers. It is widely regarded as the strongest model that remains available during a partition — the sweet spot of the entire ladder.
How it works. Track causality explicitly with logical clocks: Lamport timestamps (a counter incremented on each event and carried on each message, so cause always has a smaller stamp than effect) or vector clocks (§2.9, which additionally detect concurrency). A replica buffers an update until all its causal dependencies have been applied locally, then applies it.
Worked example. Alice posts "I lost my job." Bob reads it and replies "Congrats on the new one!" — no wait, replies "Sorry to hear that." Bob's reply causally depends on Alice's post, so no observer may see the reply without the post. Meanwhile, Carol's unrelated post about lunch is concurrent, and different users may see it before or after Alice's — harmless. Without causal consistency you get the notorious "reply appears before the message" bug that plagues comment systems.
Pros. Eliminates essentially all user-visible consistency weirdness (out-of-order replies, effects before causes) at a fraction of the cost of linearizability. Remains fully available during partitions — the CALM/COPS line of research proves causal is the best you can do while staying available.
Cons. Metadata: vector clocks grow with the number of writers, and dependency tracking costs bandwidth and memory. It does not prevent conflicting concurrent writes — you still need a resolution rule (§2.9). Few mainstream databases offer it as a first-class mode (COPS, Eiger, MongoDB's causally-consistent sessions, and Cosmos DB's session level are the notable ones).
Complexity. No coordination round trip; cost is metadata size and buffering delay.
When to use it. Social feeds, comment threads, collaborative apps, messaging — anywhere the relationships between events are what users notice. This is the model to propose when an interviewer asks how to make an AP system feel correct.
6.5Causal+ (Convergent Causal Consistency)#
What it is. Causal consistency plus the guarantee that replicas converge — i.e., concurrent conflicting writes are resolved by a deterministic, commutative rule so all replicas land on the same value.
How it works. Causal ordering as above, plus LWW or a CRDT merge (§8.2) for concurrent operations.
Why it matters. Plain causal consistency permits replicas to permanently disagree about concurrent writes; causal+ closes that hole. This is what production "causal" systems actually implement (COPS, Eiger), and knowing the distinction is a nice depth signal.
When to use it. Whenever you would use causal — you almost always want the + as well.
6.6The Session Guarantees (read-your-writes, monotonic reads, monotonic writes, writes-follow-reads)#
What they are. Per-client guarantees rather than global ones — the four defined in §2.10.
How they work. Three cheap implementations, in increasing sophistication. (a) Sticky routing: hash the session to one replica, so that client always reads a single, self-consistent view (gives monotonic reads and, if writes go there too, read-your-writes). Cost: uneven load, and the guarantee breaks when that replica fails or the client's IP changes. (b) Version tokens: the write returns a version/timestamp; the client sends it with subsequent reads, and any replica that hasn't caught up to that version either waits or forwards to one that has. Cost: a token on every request and occasional added latency; benefit: correctness survives replica changes — this is the right default and is exactly how MongoDB's causally-consistent sessions and Cosmos DB's session level work. (c) Read-from-leader-after-write: route a user's reads to the leader for N seconds after they write. Cost: leader load spikes; simple to implement.
Pros. They fix ~90% of user-visible consistency complaints for a rounding error of the cost of linearizability, and they compose with any AP store.
Cons. They are per-client only: they say nothing about what other users see, so cross-user interactions (Alice tells Bob to look) can still surprise. Sticky routing has failure-mode gaps; tokens add client-side plumbing.
When to use it. Always, on top of any eventually-consistent store. This is the highest value-per-unit-cost rung on the entire ladder.
6.7Bounded Staleness#
What it is. A quantified promise: reads are never more than K versions or T seconds behind the latest write.
How it works. Replicas track their lag; when a replica exceeds the bound, it either blocks the read until it catches up or redirects the client to a fresher replica. Writers may also be throttled to keep replicas within the bound.
Worked example. Cosmos DB bounded staleness with K=100 operations / T=5 seconds: a read may miss the last 100 writes or the last 5 seconds, whichever comes first — and the system enforces that ceiling by slowing writers if replicas fall behind.
Pros. Turns "eventually" (§2.2's honest weakness) into a number you can put in an SLA and reason about. It gives product teams something concrete: "prices may be up to 5 seconds stale" is a decision a business can make.
Cons. Enforcing the bound costs availability at the margin — if a replica can't keep up, reads block or writes throttle, which is CP-like behavior appearing inside a nominally AP system. Requires reliable lag measurement, which needs decent clocks or version counters.
When to use it. Dashboards, pricing, leaderboards, inventory displays, analytics — anywhere "recent enough" is a real requirement and "perfectly current" is not worth a quorum round trip.
6.8Consistent Prefix (Monotonic Prefix)#
What it is. You may be behind, but you always see a valid prefix of the write history — never a gap, never an inversion. If writes were A, B, C, you may see A, or A B, or A B C, but never A C.
How it works. Ship the replication stream in order and apply it in order (this is exactly what a log-shipping follower does — File 07's ordered log, File 11's WAL).
Pros. Nearly free — it's the natural behavior of ordered log replication. Prevents the most jarring anomaly class: seeing an effect without its cause within a single stream.
Cons. Says nothing about how far behind you are (combine with bounded staleness) and nothing about cross-partition ordering (two shards' streams are independent, so a prefix per shard doesn't give you a global prefix).
When to use it. The right minimum bar for any read-replica setup — and worth naming explicitly, because "our replicas apply the WAL in order" is a guarantee many engineers rely on without knowing it has a name.
6.9Eventual Consistency (the floor)#
What it is. §2.2 — if writes stop, replicas converge. No ordering, no timing, no session guarantees.
How it works. Asynchronous propagation with read repair and anti-entropy (§0.5) plus a resolution rule (§2.9).
Pros. Cheapest possible: no coordination, maximum availability, minimum latency, near-linear scalability.
Cons. Every anomaly is permitted — your own writes can vanish from your view, time can appear to run backwards, effects can precede causes. As a bare contract it is close to unusable, which is why real systems always layer session guarantees on top.
When to use it. Metrics and telemetry, view counts, caches, DNS, CDN content, presence — data where an anomaly is invisible or self-correcting within seconds.
6.10Comparison Table#
| Model | Guarantee in one line | Available during partition? | Coordination cost | Anomaly it still allows | Best for |
|---|---|---|---|---|---|
| Strict serializable | Serial order + real time, multi-object | No | Quorum + 2PC + commit-wait | None | Ledgers, financial systems |
| Linearizable | One copy, real time, per object | No | 1 quorum RTT (+ read confirm) | No cross-object atomicity | Locks, uniqueness, config, leader election |
| Sequential | Same order everywhere, not real time | No (in practice) | 1 agreed order, lagging reads OK | Stale reads across channels | Teaching; hardware memory models |
| Causal+ | Cause before effect + replicas converge | Yes | Metadata only (no RTT) | Concurrent writes still conflict | Social feeds, comments, messaging |
| Session guarantees | Each client sees a sane view of its own actions | Yes | Sticky routing or a version token | Other users may see differently | Layer on top of any AP store |
| Bounded staleness | At most K versions / T seconds behind | Mostly (blocks at the bound) | Lag tracking; throttle at the limit | Bounded staleness by definition | Dashboards, pricing, leaderboards |
| Consistent prefix | Never see a gap or inversion in the stream | Yes | Free with ordered log shipping | Unbounded lag; no cross-shard order | Any read-replica setup (minimum bar) |
| Eventual | Converges if writes stop | Yes | None | Everything | Telemetry, counters, caches, DNS |
6.11The Decision Rule for the ladder#
Start at the bottom and climb only when a specific anomaly hurts. Concretely: begin with eventual + consistent prefix (nearly free), immediately add session guarantees (cheap, fixes most user complaints), add causal+ if users can observe relationships between events (replies, threads, collaborative edits), add bounded staleness when the business needs a number in an SLA, and reserve linearizable / strict serializable for the specific operations that enforce a global invariant. Every rung upward costs coordination, and coordination costs a round trip you can never get back. The mistake is starting at the top "to be safe" — you pay speed-of-light latency on every operation to protect invariants that 95% of your operations don't have.
7Tunable Consistency in Practice — The Knobs Real Databases Give You#
Classifying a whole database as CP or AP is increasingly wrong, because the mainstream systems expose the choice per request. Knowing the actual knob names is a strong practical signal.
7.1Cassandra / ScyllaDB — consistency levels per query#
You set a consistency level (CL) on each read and each write, independently: ONE (one replica responds), QUORUM (a majority of N), LOCAL_QUORUM (a majority within the local datacenter — the single most used setting in multi-region deployments), EACH_QUORUM (a majority in every datacenter, for writes), ALL, and SERIAL/LOCAL_SERIAL (Paxos-backed lightweight transactions for compare-and-set).
The math is §0.5's: with N=3, W=QUORUM(2) + R=QUORUM(2) > 3 gives read-your-write overlap; W=ONE, R=ONE gives 1+1 < 3 and no guarantee at all. **LOCAL_QUORUM deserves special emphasis:** it satisfies the overlap condition within a datacenter — fast, no cross-region latency — while cross-datacenter replication proceeds asynchronously, which means a read in another region can still be stale. That is a deliberate PA/EL choice per region and it is the standard production configuration.
Trade-off: QUORUM costs the majority round trip; ONE is fast and offers nothing; SERIAL runs Paxos and costs roughly 4× a normal write. Mitigation for the latency: use LOCAL_QUORUM and design so cross-region staleness is acceptable, escalating to SERIAL only for the handful of operations that need compare-and-set.
7.2DynamoDB — two read modes, one database#
Eventually consistent reads (default) hit any replica: ~1 ms, may be seconds stale, cost 0.5 RCU (Read Capacity Unit — DynamoDB's billing unit). Strongly consistent reads route to the leader replica: higher latency, unavailable if that replica's partition is unreachable, cost 1 RCU — double. DynamoDB also offers transactions (TransactWriteItems) for multi-item atomicity, at roughly 2× the write cost again.
So the same table is PA/EL for eventual reads and PC/EC for strong reads, and the price list makes the trade-off literal: consistency is billed at 2×. The senior move: default to eventual reads and use strong reads only on the specific read-after-write paths that need them — which is cheaper and faster and more available, and you can name the exact operations where you'd flip it.
7.3Azure Cosmos DB — five named levels with SLAs#
Cosmos DB is the clearest commercial packaging of §6's ladder, offering exactly five levels: Strong (linearizable, but restricted in multi-region write configurations because the latency would be untenable), Bounded Staleness (§6.7 — you specify K versions and T seconds), Session (§6.6 — read-your-writes and monotonic reads for the client's session; the default, and the right default), Consistent Prefix (§6.8), and Eventual (§6.9).
The instructive part is the pricing and SLA: stronger levels cost more request units and carry higher latency SLAs, and Microsoft publishes the numbers. This is the CAP trade-off turned into a menu with prices, and citing it in an interview shows you understand the trade-off is economic, not philosophical.
7.4MongoDB — write concern and read concern, separately#
**Write concern (w)** controls durability/consistency of writes: w:1 (primary only — fast, and a failover can lose it), w:majority (a majority of replica-set members — survives failover, this is the safe setting), plus j:true to require the write be journaled to disk. Read concern controls what reads see: local (whatever this node has, may be rolled back), majority (only data acknowledged by a majority, so it can never be rolled back), linearizable (real-time guarantee, primary only, slowest), and snapshot for transactions. Causally consistent sessions add the §6.6 token mechanism on top.
The critical point candidates miss: **w:majority alone does not prevent stale reads, and readConcern:majority alone does not prevent lost writes.** You need both to get end-to-end safety, and MongoDB's defaults have shifted over the years precisely because of this. Say this: "I'd set w:majority plus readConcern:majority on the ledger collection, and leave the analytics collection at w:1/local."
7.5Kafka — acks, ISR, and the same trade-off in a log#
Kafka (File 07) exposes it as acks: acks=0 (fire and forget — fastest, loses data on any failure), acks=1 (the partition leader only — loses data if the leader dies before followers replicate), acks=all (all in-sync replicas, where the ISR is the set of replicas currently caught up to the leader). Paired with the broker setting min.insync.replicas=2, acks=all means a write is only acknowledged once at least two replicas hold it — and if fewer than two are in sync, the broker refuses the write. That refusal is a CP choice, made explicit in a config file. unclean.leader.election.enable=true flips it the other way: allow an out-of-sync replica to become leader, staying available at the cost of losing acknowledged writes — the AP choice, and one that should almost always stay false.
7.6The practical decision table#
| Workload | Setting to use | Why |
|---|---|---|
| Financial ledger, balances | Cassandra SERIAL / Mongo w:majority+readConcern:majority / DynamoDB transactions / Kafka acks=all+min.insync=2 | Invariant violations require human cleanup and may be regulatory |
| User profile read after edit | Session consistency (version token) or read-from-leader briefly | Only that user notices; RYW is enough and costs nearly nothing |
| Social feed, comments | Causal+ (§6.5) | Users notice reply-before-post; nothing else |
| Product catalog / prices | Bounded staleness (5–30 s) | Business can state the number; a quorum read per page view is waste |
| View counts, likes, telemetry | Eventual, CRDT counters | Anomalies invisible; volume makes coordination prohibitive |
| Multi-region writes, low latency required | LOCAL_QUORUM per region + async cross-region | Avoids paying the ocean crossing on every write |
| Cluster metadata, leader election, locks | Linearizable (etcd/ZooKeeper) | Small, low-throughput, catastrophic if wrong |
8Beyond CAP — Harvest/Yield, CALM, CRDTs, and "Effectively CA"#
CAP's binary framing hides four ideas that are far more useful in real design work.
8.1Harvest and Yield (Fox & Brewer, 1999) — partial answers#
CAP treats a response as correct or absent. Real systems degrade partially, and this pair of terms names how.
Yield is the fraction of requests that get answered — essentially availability, measured. Harvest is the fraction of the data reflected in an answer — completeness. The insight: you can trade harvest for yield. A search engine whose index is spread over 100 shards, with 5 shards down, can either fail the query (100% harvest, 95% yield) or return results from the 95 available shards with a "some results may be missing" note (95% harvest, 100% yield). Google chooses the second, every time, because a user searching for a restaurant does not care that 5% of the index is missing — they care that the page loads.
Applications you should be able to name: a feed that renders with the posts it could fetch and a spinner for the rest; a dashboard that shows metrics from reachable regions with a "3 of 5 regions reporting" banner; an e-commerce page that renders the product but hides personalized recommendations when that service is down (a graceful-degradation ladder). Trade-off: partial answers can mislead if not labeled — a dashboard silently missing a region will make someone conclude traffic dropped. Mitigation: always surface the harvest — "showing 95% of results," "3 of 5 regions" — so the consumer knows the answer is partial.
This is the most practically useful idea in the post-CAP literature and almost nobody brings it up. Doing so is a strong differentiator.
8.2CRDTs — making conflicts impossible instead of resolving them#
A CRDT (Conflict-free Replicated Data Type) is a data structure whose merge operation is commutative, associative, and idempotent — meaning replicas can apply updates in any order, any number of times, and still converge to the identical value. If merging is mathematically total, AP becomes safe: there is no such thing as an unresolvable conflict.
The two families: state-based (CvRDT), where replicas ship their whole state and merge with a join function (for a grow-only counter: take the max of each replica's sub-counter) — simple and network-tolerant since re-delivery is harmless, but expensive to ship; and operation-based (CmRDT), where replicas ship operations that must commute — cheap on the wire but requires exactly-once, causally-ordered delivery from the messaging layer.
The standard catalog: G-Counter (grow-only counter — a vector of per-replica counts merged by max, summed to read), PN-Counter (increment and decrement, built as two G-Counters subtracted), G-Set (grow-only set, merged by union), 2P-Set (add and remove once, using a tombstone set), LWW-Element-Set (timestamped adds and removes), OR-Set (observed-remove set — each add carries a unique tag and a remove only cancels the tags it observed, which fixes the "concurrent add and remove" ambiguity correctly), and RGA/Logoot/Treedoc (sequence CRDTs for collaborative text editing).
Pros: availability and eventual correctness simultaneously, with no coordination at all — the only place in this file where you appear to get both. Cons: only expressible for commutative operations, so a CRDT cannot enforce "balance ≥ 0" (a PN-Counter will happily go negative concurrently); metadata and tombstones (markers for deleted elements, which must be retained so a late-arriving add doesn't resurrect them) accumulate and need garbage collection; and semantics can be counterintuitive (Amazon's resurrected cart items, §2.9).
When to use: collaborative editing (Figma, Google Docs' operational-transform cousin, Automerge/Yjs), presence and status, offline-first mobile sync, distributed counters and sets, and Redis Enterprise's active-active geo-replication, which is CRDT-based.
8.3The CALM Theorem — when you can skip coordination entirely#
CALM — Consistency As Logical Monotonicity (Hellerstein) — states: a program can be computed in a coordination-free, eventually consistent way if and only if it is monotonic. A computation is monotonic if learning more facts never retracts a previous conclusion: adding elements to a set, incrementing a counter, computing a union, or answering "does X exist?" are monotonic. Non-monotonic operations retract conclusions when new facts arrive: counting a set's final size, computing a maximum that must be final, checking "does X not exist?" (uniqueness!), or any threshold test.
Why it matters as a design tool: it tells you precisely when coordination is required, rather than leaving it to intuition. "Add item to cart" is monotonic → no coordination needed → AP is safe. "Is this username taken?" is non-monotonic (a later fact can invalidate your "no") → coordination required → CP. The design move CALM suggests: restructure operations to be monotonic where you can. Instead of "reserve the last seat" (non-monotonic, needs coordination), model it as "append a reservation request" (monotonic, coordination-free) plus a downstream ordered resolution that assigns seats — which is exactly how high-scale ticketing and ad-auction systems avoid contention.
8.4"Effectively CA," and the honest criticisms of CAP#
Spanner's claim. Google describes Spanner as "effectively CA": formally it is CP (a minority partition loses availability), but Google engineered partition probability down with redundant private fiber, multiple independent network paths, and careful failure-domain design, so that network-caused unavailability is a small fraction of total unavailability — the CAP choice is exercised so rarely that users never observe it. **The lesson is not that CAP is wrong; it's that CAP describes what happens when a partition occurs and says nothing about how often that is.** Reducing partition frequency is a legitimate, expensive, and highly effective engineering strategy, and it is invisible to CAP.
The criticisms worth voicing. CAP's definitions are narrow and often not the ones you want: its availability ignores latency (a system that answers after 60 seconds is "available"), its consistency is single-object only, and its model is asynchronous, whereas real networks are partially synchronous. Its binary labels obscure the tunable, per-operation reality of every modern database (§7). Martin Kleppmann's "A Critique of the CAP Theorem" makes the case that the theorem is a narrow formal result routinely over-applied as a design philosophy, and recommends reasoning directly about latency, staleness, and specific failure scenarios instead.
The synthesis to deliver in an interview: "CAP is the right first question — what happens when the network breaks? — but the wrong last answer. I'd use PACELC to cover normal operation, pick a specific consistency model per operation from the ladder, quantify the staleness I can tolerate, design a partition mode with compensation, and reduce partition probability where it's affordable."
9Pros, Cons & Trade-offs#
9.1Benefits of reasoning with this framework#
It forces the failure question early. Most architecture discussions compare features, throughput, and query languages. CAP/PACELC forces the question that actually determines whether your system is correct at 3 a.m.: what does this do when a link degrades? Asking it during design rather than during an incident is worth more than every benchmark.
It gives per-operation precision. Once you accept that the choice is per operation (§4.4), you stop the pointless "is our company a CP company or an AP company" debate and start classifying individual endpoints by the cost of being wrong — which is a tractable engineering exercise with a defensible answer for each row.
It produces a shared, exact vocabulary. "Consistent" is a mush word that means five different things to five engineers. "Read-your-writes but not linearizable, with bounded staleness of 5 seconds" is a specification you can implement, test, monitor, and put in an SLA.
It exposes latency as a design input, not an afterthought. PACELC's ELSE branch makes the everyday cost visible (§0.4). Teams that internalize it stop asking "why is our cross-region write 90 ms?" and start deciding deliberately whether to pay it.
It makes degradation designable. Harvest/yield (§8.1) and partition mode (§3.3) turn "the system broke" into "the system entered a designed, labeled, degraded state and then recovered with compensation" — which is the difference between an outage and a blip.
9.2Costs & Trade-offs of the framework itself#
It is binary where reality is continuous. CAP has no vocabulary for "3 seconds stale" or "95% of shards answered." Mitigation: use the §6 ladder and quantify staleness (§2.4) rather than reasoning in labels.
It invites misapplication as a slogan. "Pick two" has probably caused more bad designs than it has prevented, by convincing teams that partition tolerance is optional or that CP means low availability. Mitigation: state the correct framing (§1.1) and correct the "CA" claim (§4.3) whenever it appears.
Choosing AP moves complexity into your application, where it is more expensive. Conflict resolution, compensation, and staleness handling get written once in a database by specialists, or a hundred times in your services by everyone. Mitigation: confine AP to operations that genuinely commute, and prefer CRDTs (§8.2) so the merge is proven rather than hand-written.
Choosing CP buys correctness with latency and a real outage mode. Every write pays a quorum round trip forever, and quorum loss is a hard outage requiring a risky manual recovery. Mitigation: keep CP data small and in-region (File 18 §8.3), shard into many consensus groups, and rehearse the recovery runbook.
Partition-mode code is the least-tested code you own. It runs only during rare events, which is exactly when you most need it to work. Mitigation: chaos-test it — deliberately inject partitions (iptables rules, cloud security-group changes) in staging, and run or read Jepsen analyses (§2.11) for anything you depend on.
Tunable knobs create accidental configurations. w:1 here and readConcern:majority there produces a system whose real guarantee nobody can state. Mitigation: document the required consistency level per collection/table/endpoint, and enforce it in code review and in client library defaults.
9.3The Senior Rule#
Choose consistency per operation, quantify staleness in seconds, and always design the partition mode and its compensation before you need it. Default to CP for the small set of things that enforce global invariants — money, identity, inventory, cluster metadata — and to AP with session guarantees for everything else, because most data is either commutative or self-correcting. And remember the one asymmetry that decides close calls: an availability failure costs you a bad hour; a consistency failure costs you a corrupted dataset that someone must reconcile by hand, and you may not discover it for weeks.
10Interview Diagnostic Framework (Symptom → Solution)#
| System Symptom (what you're told in the interview) | Why CAP/PACELC is the cure | Specific solution |
|---|---|---|
| "Users post a comment, refresh, and it's gone — then it comes back." | Classic read-your-writes + monotonic reads failure from lagging replicas (§2.10) | Version-token session consistency, or route the user's reads to the leader for a few seconds after a write — not full linearizability |
| "During a network blip both datacenters accepted writes and the data forked." | AP without a defined resolution rule = split-brain (§2.6) | Either move to quorum-based CP for that data, or define a CRDT/vector-clock merge plus business compensation (§3.3) |
| "We oversold 200 units of inventory during an outage." | Global invariant enforced in an AP store — impossible by construction (§4.6 step 1) | CP with compare-and-set for the counter, or pre-partition stock per region so each side's invariant is local (§3.3) |
| "Cross-region writes take 150 ms and users complain." | This is PACELC's ELSE branch — you're paying consistency in latency, and the RTT is physics (§0.4) | Regional leaders with local quorums (LOCAL_QUORUM), async cross-region replication, and an explicit staleness budget |
| "Our vendor says the DB is both highly available and strongly consistent." | The theorem says the claim needs a qualifier; find it (§1.1) | Ask what happens to the minority side during a partition, and ask for Jepsen results (§2.11) |
| "We use Postgres so we're CA." | Category error — CA only exists on one node (§4.3) | Explain that adding a replica forces the choice: sync + quorum failover = CP; async + promote = AP with a lost-write window |
| "A reply shows up before the message it replies to." | Causality violation — the fix is causal consistency, not linearizability (§6.4) | Causal+ with logical clocks, buffering updates until dependencies apply |
| "The dashboard shows a traffic drop that didn't happen." | A partial result presented as complete — a harvest problem (§8.1) | Return partial results labeled with harvest ("3 of 5 regions reporting"), never silently |
| "Analytics queries are slowing down our checkout." | Reads and writes competing; most reads don't need current data | Serve analytics from followers with bounded staleness (§6.7), keep checkout on the leader (CP) |
| "We need strong consistency everywhere for safety." | Over-application: paying quorum latency for operations with no invariant (§6.11) | Classify endpoints; keep CP for money/identity/inventory, drop to session+causal for the rest |
| "Failover promoted a replica and we lost the last few seconds of orders." | Async replication (PA/EL) losing acknowledged writes at failover (§0.5) | w:majority/acks=all+min.insync.replicas=2, and disable unclean leader election (§7.5) |
| "Our multi-region active-active database has mysterious data loss." | Multi-leader with LWW silently discarding concurrent writes, worsened by clock skew (§2.9) | Switch to CRDTs or vector clocks for conflict detection; log every conflict; never let LWW resolve silently |
11Real-World Engineering Scenarios#
11.1Amazon Dynamo / DynamoDB — AP because a rejected write costs a sale#
Amazon's 2007 Dynamo paper is the founding document of the AP camp, and its motivation was a specific business fact: during the 2004 holiday season, the shopping cart service — backed by a strongly consistent relational store — became unavailable, and every rejected "add to cart" was a lost sale. Amazon decided that the cart must accept writes always, and that reconciling a weird cart later was strictly cheaper than refusing a customer.
The end-to-end flow:
- A cart write arrives and is routed by consistent hashing (File 06) to the N=3 replicas that own the key on the ring.
- The coordinator writes to all 3 and returns success as soon as W=1 (or 2) acknowledge — no waiting for the rest, so the write is a local-datacenter operation of ~1 ms.
- If a home replica is unreachable, a sloppy quorum (§2.8) sends the write to the next healthy node on the ring, tagged with a hint naming the intended owner.
- Each version carries a vector clock (§2.9). When the partition heals and the hinted node returns, hinted handoff delivers the stored write.
- A later read with R=1 or 2 may return siblings — multiple concurrent versions. Dynamo hands them to the application, and the cart application merges by union of items.
- Background anti-entropy with Merkle trees (§0.5) reconciles anything reads never touched.
The famous consequence: because merge is a union, a deleted item can reappear — you remove a book, a partition happens, and the book returns. Amazon accepted this deliberately: a resurrected item is an annoyance; a rejected add is lost revenue.
DynamoDB (2012, the managed service) kept the availability but made the trade-off selectable: eventually consistent reads by default (PA/EL, 0.5 RCU), strongly consistent reads on request (PC/EC, 1 RCU), and full transactions when you need them. Takeaway: the CAP choice was made by finance, not by engineering aesthetics — and the modern version of that choice is a per-request parameter with a price tag attached.
11.2Google Spanner — CP with the partition probability engineered down#
Spanner delivers globally distributed, externally consistent (strict serializable, §6.1) transactions — the thing CAP folklore says you can't have — and understanding how is the best possible demonstration that you understand the theorem rather than the slogan.
- Data is split into ranges; each range is replicated across regions by its own Paxos group (File 18 §7.4), so a minority partition loses availability for that range only. This is textbook CP.
- TrueTime gives every server a time interval
[earliest, latest]guaranteed to contain true time — typically ~7 ms wide — backed by GPS receivers and atomic clocks in every datacenter. - A read-write transaction acquires locks, picks a commit timestamp, and then performs commit-wait: it waits out the TrueTime uncertainty before releasing locks, guaranteeing any transaction that starts later gets a strictly larger timestamp. That is what converts serializability into strict serializability across continents.
- Multi-range transactions run 2PC across Paxos groups (File 18 §6.1); because each participant's decision log is itself Paxos-replicated, 2PC's classic coordinator-crash blocking problem is neutralized.
- Read-only transactions at a past timestamp take no locks and need no consensus — they read any replica that has advanced past that timestamp. This is the release valve that makes Spanner's read path scale despite its expensive writes.
- Google reports availability dominated by non-network causes, because redundant private fiber and multiple independent paths make network partitions rare — hence "effectively CA" (§8.4).
Takeaway: Spanner does not beat CAP; it is CP, and it spends enormous money — atomic clocks, private global fiber — to make the moments when CAP's choice binds vanishingly rare, plus commit-wait latency on every write. When someone asks "why doesn't everyone do this?", the answer is: because most companies cannot install atomic clocks or lay their own ocean cable, and commit-wait is a permanent tax on every transaction.
11.3Meta's TAO — eventual consistency at social-graph scale, with the caveats designed in#
Facebook's social graph — friendships, likes, comments, posts — is read-dominated by roughly 500:1 or more, and it is the canonical example of choosing availability and latency because the data's semantics permit it.
TAO (The Associations and Objects) is a geographically distributed, read-optimized graph store layered over sharded MySQL:
- The graph is modeled as objects (a user, a post) and associations (
user LIKES post,user FRIENDS user), sharded across MySQL instances that hold the persistent truth. - In front of MySQL sits a two-level cache tier: a leader cache per shard (the only tier that writes to MySQL) and follower caches in every region that serve reads. Well over 99% of reads are served from cache without touching the database.
- A write goes to the shard's leader cache, which writes through to MySQL and then asynchronously invalidates follower caches in other regions. Local region: strongly-consistent-ish. Remote regions: eventually consistent, typically within milliseconds.
- Read-after-write for the writer is preserved deliberately: the writing region's follower cache is updated synchronously, so the user who posted always sees their own post (§2.10's RYW — the guarantee that actually matters to users).
- Cross-region reads may briefly miss a very recent write — someone in another country may not see a like for a fraction of a second. Product-wise, this is invisible.
- Failure handling is harvest-flavored (§8.1): if a shard is unavailable, TAO can serve stale cached data rather than failing the page render.
Takeaway: TAO gets to be AP because the social graph has almost no global invariants — no balance to protect, no uniqueness to enforce, nothing to oversell — while at Facebook's read volume, a quorum round trip per read would be economically impossible. The design is the CAP decision procedure applied honestly: no global invariant + enormous read volume + staleness measured in milliseconds is invisible → AP, plus read-your-writes so users never see their own action vanish.
12Interview Gotchas & Failure Modes#
12.1Classic Questions (with the crisp answers)#
"Explain CAP." "During a network partition you must choose between consistency and availability — partition tolerance isn't optional because networks partition. And CAP only describes the partition case; PACELC adds the normal case, where the trade-off is latency versus consistency, which is what you actually pay every day." Leading with the correction is the whole point.
"Is MongoDB CP or AP?" *"It depends on configuration — with w:majority and readConcern:majority it's PC/EC; with w:1 and async secondaries it's PA/EL and a failover can lose acknowledged writes. Most modern databases are tunable per operation, so the question is really 'which knob setting for which collection?'"*
"Can you have a CA system?" "Only on a single node, where there's no network to partition. As soon as you replicate, you've chosen: sync replication with quorum failover is CP, async replication with promote-on-failure is AP with a lost-write window."
"Is CAP's C the same as ACID's C?" "No. CAP's C is linearizability — acts like one copy, respects real time. ACID's C is invariant preservation inside a transaction. Different axes entirely; a single-node database has ACID-C and no CAP question at all."
"Does W + R > N give you strong consistency?" "It guarantees the read set overlaps the write set, so the latest acknowledged value is among the responses — but it isn't linearizability: partially-failed writes are neither committed nor rolled back, concurrent writes still need an ordering rule, and sloppy quorums break the overlap entirely."
"How would you design a system that's both available and correct?" "Split it by operation. Anything with a global invariant — payments, inventory, identity — goes CP. Everything else goes AP with session guarantees and causal consistency, and I'd use CRDTs wherever operations commute so there's nothing to conflict over. Then I'd pre-partition invariants where possible so even the constrained operations can run available."
"What's the difference between eventual and strong consistency?" — then immediately go past the binary: "Those are the endpoints of a ladder. Between them sit consistent prefix, session guarantees, causal+, and bounded staleness — and in practice session guarantees plus causal consistency fix nearly every user-visible complaint at a tiny fraction of linearizability's cost."
"Your system is AP and two regions accepted conflicting writes. Now what?" "That depends on a rule I chose in advance: LWW if the value is a whole-object overwrite and losing one update is acceptable; vector clocks if I need to detect true conflicts rather than guess; a CRDT merge if the type allows it. Then compensation for any invariant already violated — refund, restock, notify — and I log every conflict rather than resolving silently, because silent LWW is how data disappears without anyone noticing."
"How does Spanner beat CAP?" "It doesn't. It's CP — a minority partition loses availability. What Google did was engineer partition probability down with private fiber and redundant paths, and add TrueTime with commit-wait so it can be strictly serializable globally. It's 'effectively CA' in outcome, still CP in the theorem."
"What's PACELC's most useful part?" "The ELSE branch, because partitions are rare and normal operation is 99.99% of your life. Every synchronous-versus-asynchronous replication decision and every leader-versus-follower read is an EL-versus-EC choice, made constantly."
12.2Failure Modes & Mitigations#
Silent data loss via last-writer-wins. Symptom: users report edits vanishing, with no errors anywhere. Cause: two concurrent writes resolved by timestamp, worsened by clock skew making the "winner" genuinely older (File 17 §0.3). Mitigation: use version vectors to detect concurrency, keep siblings or apply a CRDT merge, log every conflict as a metric you alert on, and monitor clock offset — never let LWW be both the policy and the audit trail.
Read-your-writes failures after adding read replicas. Symptom: the app worked fine, then "my changes don't show up" reports began the week replicas were added. Cause: reads spread to lagging followers. Mitigation: version-token session consistency, or leader-reads for a short window after each write; monitor replication lag as a first-class SLO and alert when it exceeds the session window.
Split-brain during a partial partition. Symptom: both sides took writes and diverged; conflicting records after the heal. Cause: failure detection based on pairwise heartbeats in a partial or asymmetric partition (§0.1) — each side concluded the other was dead. Mitigation: quorum-based decisions (never pairwise), fencing tokens at the storage layer (File 18 §2.9), and a witness node in a third failure domain so a two-way split always has a tiebreaker.
Availability lost by over-applying CP. Symptom: an entire application is down because one consensus cluster lost quorum, including features that had no consistency requirement at all. Cause: everything routed through one CP store. Mitigation: classify endpoints; keep the CP dataset small; add degraded read paths (cached/stale with a label) for non-critical features so the blast radius of quorum loss is the ledger, not the homepage.
Gray failures never triggering failover. Symptom: 30% packet loss for an hour; latency terrible; no alarms fired because health checks eventually succeeded. Cause: binary health checks against a non-binary failure (§0.1). Mitigation: health checks that assert latency and error-rate thresholds, not just liveness; alert on p99 and on replication lag; consider request hedging (send a duplicate to a second replica after a delay and take the first response) so a degraded node stops dominating tail latency.
Untested partition-mode code failing when it finally runs. Symptom: the first real partition in two years produces behavior nobody predicted. Cause: partition handling is by definition rarely executed. Mitigation: inject partitions in staging (iptables/security-group rules), run game days, and read the Jepsen report (§2.11) for every datastore you depend on — including the one you were told is safe.
Unbounded staleness during a long partition. Symptom: after a multi-hour partition, one region has been serving hours-old prices or entitlements. Cause: eventual consistency with no bound (§2.2). Mitigation: enforce bounded staleness (§6.7) — block or redirect reads once lag exceeds the bound — and surface staleness in the UI ("last updated 4 hours ago") so humans can intervene.
Compensation designed only after the incident. Symptom: an oversell or double-charge occurs and there is no process — engineers write ad-hoc SQL at 3 a.m. Cause: AP was chosen without the compensation half (§3.3 step 3). Mitigation: for every AP operation with a business invariant, write the compensating action at design time — the refund path, the restock path, the apology email — and test it.
Configuration drift across services. Symptom: nobody can state the system's actual guarantee; some services use w:1, others w:majority, against the same collection. Mitigation: define required consistency levels per dataset, encode them in shared client libraries rather than per-service config, and assert them in integration tests.
13Whiteboard Cheat Sheet#
═══════════════════ CAP & PACELC — BOARD DUMP ═══════════════════════════
CAP (Brewer '00 / Gilbert-Lynch '02):
C = LINEARIZABILITY (one copy, real-time order) ← NOT ACID's C!
A = every request to a live node gets a non-error response
(hanging ≠ available; says nothing about latency or correctness)
P = system keeps working despite lost messages
✗ "pick 2" ✓ "P is mandatory → during a partition pick C or A"
PROOF (6 lines): partition N1|N2 → write V1 to N1 (must accept, A) →
msg lost (P) → read N2 (must answer, A) → returns V0 → not
linearizable. ∎ Contradiction is PHYSICS, not implementation.
PACELC (Abadi '10): if (P) then (A or C) else (L or C)
┌──────────┬───────────────────────────────────────────────────┐
│ PA/EL │ Cassandra(CL=ONE), DynamoDB(eventual), Riak, Couch │
│ PC/EC │ Spanner, etcd, ZooKeeper, CockroachDB, Mongo(maj) │
│ PA/EC │ strong-when-healthy, degrade-to-stale designs │
│ PC/EL │ PNUTS; MySQL async replicas + quorum failover │
└──────────┴───────────────────────────────────────────────────┘
★ THE ELSE BRANCH IS 99.99% OF YOUR LIFE — that's why PACELC wins.
PARTITION TIMELINE:
CP: majority side commits │ minority ERRORS OUT │ heal = log catch-up,
zero divergence, no reconciliation
AP: both sides accept │ data FORKS │ heal = merge (LWW /
vector clocks / CRDT) + BUSINESS COMPENSATION
BREWER'S 3 STEPS (the practical recipe):
1 DETECT (timeout, quorum loss, lag threshold — always a guess)
2 PARTITION MODE, per-operation by risk:
continue: commutative/stale-OK ops · restrict: global invariants
pre-partition the invariant: 100 units → 50 + 50 per side ★
3 RECOVER: merge state (LOG every conflict) + compensate (refund,
restock, voucher, apology)
CONSISTENCY LADDER (climb only when an anomaly hurts):
strict serializable serial order + real time, multi-obj $$$$$
linearizable one copy, real time, per object $$$$
sequential same order everywhere, not real-time $$$
causal+ cause before effect + converges ← BEST AVAILABLE
session guarantees RYW · monotonic reads · monotonic writes ·
writes-follow-reads ← CHEAPEST BIG WIN ★
bounded staleness ≤ K versions / T seconds (SLA-able number)
consistent prefix no gaps/inversions (free w/ ordered log)
eventual converges if writes stop $
★ Start at bottom. Session guarantees fix ~90% of complaints.
QUORUM MATH: N replicas, W acks to write, R responses to read
W + R > N → read set ∩ write set ≠ ∅ (sees latest ack'd write)
BUT ≠ linearizable: partial writes, concurrent-write ordering,
and SLOPPY QUORUM (write lands off-home + hinted handoff) breaks it.
CONFLICT RESOLUTION: LWW (simple, SILENTLY LOSES DATA, clock-skew risk)
· vector clocks (detects true concurrency, returns siblings, metadata
grows) · CRDT merge (commutative+associative+idempotent → conflicts
impossible: G/PN-Counter, G/2P/LWW/OR-Set, RGA for text)
BEYOND CAP:
HARVEST/YIELD — trade data completeness for answering at all;
return 95% of shards + LABEL IT ("3 of 5 regions reporting") ★
CALM — coordination-free ⟺ monotonic. add-to-set = monotonic (AP OK);
uniqueness / "is X absent" = non-monotonic (needs CP)
"EFFECTIVELY CA" — Spanner is CP; Google just made P ultra-rare
(private fiber) + TrueTime commit-wait (~7ms) for strict ser.
TUNABLE KNOBS: Cassandra CL (ONE/QUORUM/LOCAL_QUORUM/SERIAL) ·
DynamoDB eventual(0.5 RCU) vs strong(1 RCU = 2× price!) ·
Cosmos 5 levels (Strong/Bounded/Session★/Prefix/Eventual) ·
Mongo w:majority + readConcern:majority (need BOTH) ·
Kafka acks=all + min.insync.replicas=2, unclean.leader=FALSE
NUMBERS: same-AZ RTT 0.5ms · cross-AZ 1–2ms · US coast-coast 60ms ·
trans-Atlantic 90ms · trans-Pacific 150ms ← speed of light, unbuyable
DECIDE: global invariant? → CP. commutes? → CRDT+AP. cost of stale in ₹?
can I pre-partition the invariant? do I need linearizable or just RYW?
DEFAULT: control plane + money = CP · everything else = AP + session
DANGER: LWW silently eats writes · sloppy quorum kills W+R>N ·
partial/one-way partitions defeat pairwise heartbeats · gray failure
never trips failover · "we use Postgres so we're CA" · untested
partition mode · AP chosen with no compensation plan
═══════════════════════════════════════════════════════════════════════14One-Sentence Summary for an Interviewer#
CAP says that because networks inevitably partition and information cannot cross a partition, a replicated system must choose, when partitioned, between linearizable consistency (refuse service on the minority side — CP, the right call for money, inventory, identity, and cluster metadata) and availability (keep answering with possibly-stale or conflicting data — AP, the right call for carts, feeds, counters, and caches, provided you own a conflict-resolution rule and a business compensation path); "pick two" is wrong because P is not optional and "CA" describes only a single node; PACELC completes the model by naming the trade-off you actually pay every day — Else, Latency vs Consistency, because coordination costs a round trip and a round trip costs speed-of-light latency you cannot buy your way out of; and the senior practice is to make the choice per operation rather than per database, climb the consistency ladder only as far as a specific anomaly demands (session guarantees and causal+ fix most user-visible weirdness at a fraction of linearizability's cost), quantify staleness in seconds instead of arguing in labels, exploit CRDTs and monotonic (CALM) designs so commutative operations need no coordination at all, pre-partition invariants so even constrained operations can stay available, and design the detect → degrade → merge-and-compensate partition mode before the partition — remembering the asymmetry that decides every close call: an availability failure costs you an hour, while a consistency failure costs you a corrupted dataset that a human must reconcile and that you may not notice for weeks.
End of File 19. Next → File 20: Distributed Transactions — 2PC, 3PC, Sagas, the outbox pattern, and how to get all-or-nothing across services that don't share a database. Prompt me to continue.