Start here: the same stale read, two very different features#
TL;DRthe 30-second version
- A consistency model is the promise a system makes about what a read can return once there are multiple copies of the data. The models form a spectrum from strong to weak.
- Strongest is linearizable: it behaves as if there's a single copy, and once a write finishes, every later read sees it — real-time freshness. Then sequential (one agreed order, but not real-time), causal (only cause-before-effect is preserved), and eventual (copies just converge over time).
- Alongside that global spectrum are the client-centric guarantees — read-your-writes, monotonic reads, monotonic writes, consistent prefix — which promise a single user a sane view of their own session without making the whole system strong.
- Stronger consistency costs latency (a read/write has to coordinate across machines) and availability (a machine cut off from the others must refuse rather than serve stale data). CAP and PACELC name that trade.
- So the design move is per feature, not per system: a username claim needs linearizable; a like count is fine with eventual. Match the guarantee to the need and pay for nothing more.
You're running a social app on a replicated database — several copies of the data, kept in sync by shipping the write log from one to the others (that's the /replication-modes topic). Two features live in that same database. One is the username claim: when someone signs up as @alice, no one else can also be @alice. The other is the like count on a post: a number that goes up.
Now the familiar bug. A user writes to one replica and, a moment later, reads from a different replica that hasn't caught up yet. They get the old value. This is a stale read, and it comes straight from replication lag — the gap between a write committing on one copy and reaching the others. Here's the thing the lag topic doesn't answer: for the like count, a stale read is completely fine. The number is off by one for half a second, nobody notices, nobody is harmed. For the username claim, a stale read is a disaster — two people both read "@alice is free," both claim it, and now the account is corrupted.
So the real question was never "how do I stop stale reads?" It's "which of my features can tolerate one, and which can't?" The naive fix is to ban stale reads everywhere — make every read return the latest write, globally, always. That works, and it's the wrong default, because of what it costs.
The spectrum: four models, from strong to weak#
Consistency models line up on a spectrum. At each step down you give up one ordering guarantee, and in return the system gets faster and stays up in more failure situations. Start at the top.
Linearizable consistency is the strongest, and it's what people usually mean by "strong consistency." The promise is simple to state: the system behaves as if there is exactly one copy of the data, and every operation happens instantly at one moment. Concretely, the instant a write finishes, every read that starts after it — by the actual wall clock — sees that write or something newer. That "by the wall clock" part is called real-time order, and it's the expensive part: it's what forces the coordination round-trip, because a machine has to confirm no newer write exists before it answers. A username claim needs exactly this: the check "is @alice taken?" and the write "claim @alice" must act as if run one-at-a-time on a single copy.
Sequential consistency relaxes one thing: the real-time part. It still requires that all machines agree on a single order for the operations, and that each client's own operations appear in that order in the order it issued them (its program order). But that agreed order doesn't have to match wall-clock time. So a read can return a value that's a little behind the true latest — as long as everyone is behind in the same, consistent way. It's cheaper because nobody has to check the clock against every other machine; they just have to agree on an order.
Causal consistency relaxes more: it only preserves cause and effect. If one write happened because of another — a reply written after reading a question, a comment written after seeing the post — then every machine must show them in that order. This is the happens-before relationship (the /vector-clocks topic is how you track it). But writes that are independent, with no cause-effect link between them, may show up in different orders on different machines, and that's allowed. Two people liking two different posts at the same time have no causal link, so no machine has to agree on which came first. Causal is the sweet spot for things like comment threads: a reply never appears before the message it answers, but the system doesn't pay to order unrelated events.
Eventual consistency is the weakest and the cheapest. The only promise is: if the writes stop, the copies will eventually converge to the same value. In between, a read can return an old value, and two reads can even disagree. There's no ordering guarantee at all while updates are in flight. A like count is the perfect fit — every replica takes increments locally at full speed, they reconcile in the background, and the total is right once things settle. This is the default for Dynamo-style stores like Cassandra, and it's the topic behind the /quorum/sim knob, where you tune how many replicas a read and a write touch.
Go deeperUnder the hood: linearizable is not the same as serializable
These two words get swapped constantly, and they are genuinely different guarantees about different things. Linearizability is about a single object and real time: one key behaves as if every read and write hit a single copy, in wall-clock order. It says nothing about grouping operations together.
Serializability is about transactions — groups of reads and writes over many objects — and it's the strongest isolation level (the /isolation-levels topic). It promises the result is the same as if the transactions had run one after another, in some serial order. But that order need not match real time: a serializable system may run your transaction "in the past," as long as the outcome matches some sequential schedule. So serializability orders multi-object transactions but drops the real-time clock; linearizability adds the real-time clock but only for single objects.
The guarantee that has both — transactions, in a serial order that also respects real time — is called strict serializability, and it's what Google Spanner and CockroachDB advertise. When someone says a database is "strongly consistent," ask which one they mean: a linearizable single-key store (etcd) and a strict-serializable transactional database (Spanner) are making different promises.
The other axis: guarantees for one client's own session#
The spectrum above is about what every machine agrees on globally. There's a second, cheaper axis: guarantees about what a single client sees across its own session, without making the whole system strong. These are the client-centric (or session) guarantees, and they're often all a user actually needs. There are four worth knowing.
You've met the first two already in /replication-modes as fixes for lag anomalies. Here they get their proper names, and the point is that each is a promise you can offer one user cheaply — usually by routing that user's requests to a copy that's caught up, not by slowing everyone down.
| Guarantee | The promise to one client | Broken when… |
|---|---|---|
| Read-your-writes | You always see your own latest write. | You post a comment, reload, and it's missing. |
| Monotonic reads | Your reads never go backward in time. | You refresh, see a reply, refresh again and it's gone. |
| Monotonic writes | Your own writes apply in the order you made them. | You rename a file then delete it, and the delete lands first. |
| Consistent prefix | You see writes in an order that could really have happened — no gaps that scramble cause and effect. | You see an answer appear before its question. |
The reason these matter: they're much weaker, and much cheaper, than global strong consistency, yet they cover most of what makes an app feel broken to a person. A user mostly cares that their own actions stick and that their own view doesn't jump around. You can give them that by pinning their session to one caught-up replica — a sticky route — while the rest of the system stays fast and eventually consistent for everyone else. The /replication-modes topic has the concrete routing; the naming here is what lets you ask, per feature, "which of these four does this actually need?"
The decision tool: from CAP to the honest version, PACELC#
So stronger consistency costs something. CAP is the famous statement of what. It says: when the network partitions — splits the machines into groups that can't talk to each other — a system can keep serving (stay available) or stay linearizable (consistent), but not both. During a partition, a machine that can't reach the others must either answer from its own possibly-stale copy (available, not consistent) or refuse to answer (consistent, not available). That's a real, proven trade, formalized by Gilbert and Lynch in 2002.
But CAP is often misread, and it's incomplete in a way that matters. It only describes the partition case — and partitions are rare. Read literally, CAP says nothing about how your system should behave the other 99.9% of the time, when the network is fine. And in that normal case there's still a trade, one CAP is silent about: even with no partition, a linearizable read has to coordinate across machines, and that coordination costs latency. You are trading consistency against latency constantly, not just during a partition.
PACELC gives you a two-letter label for each system: how it leans under a partition, and how it leans normally. Here are the four combinations with a real system for each, which is the fastest way to see that this is a genuine design choice, not a ranking.
| PACELC class | Under a partition | Normally | Example |
|---|---|---|---|
| PA/EL | Stay available, allow stale | Favor latency, allow stale | Cassandra, DynamoDB (default) |
| PC/EC | Refuse rather than diverge | Pay latency for consistency | Spanner, VoltDB (strict/strong) |
| PA/EC | Stay available under partition | But consistent when healthy | MongoDB (default) |
| PC/EL | Consistent under partition | But fast (stale) when healthy | PNUTS (Yahoo) |
The label isn't a grade. PA/EL isn't "worse" than PC/EC — it's a system that decided its workload can live with staleness and wants speed and uptime, which is exactly right for a like count and exactly wrong for a bank ledger. The tool is: know what your feature needs, then pick (or configure) the system whose letters match.
What it costs: latency and availability, with the numbers#
Both costs of strong consistency trace to one fact: a machine can't be sure it holds the latest value using only its local copy, so it must talk to others before it answers. That single round-trip is where the money goes.
- Latency: a linearizable read or write has to confirm with a quorum — a majority of the replicas (the /quorum/sim rule, R + W > N) — or route through a single leader elected by consensus (/raft/sim). Either way it's at least one network round-trip before the operation completes. Within one datacenter that adds maybe a millisecond. Across regions it's the speed of light: ~100–150 ms per operation. An eventual read from the nearest replica skips all of it and answers in well under a millisecond.
- Availability: when a partition cuts a replica off from the majority, a linearizable system makes that replica stop serving — it can't prove its data is current, so it must refuse rather than answer stale. The minority side goes down for the duration. An eventually-consistent system keeps both sides answering from their local copies; nobody goes down, but the two sides can diverge until the partition heals and they reconcile.
PredictYour app runs replicas in three regions. A product manager asks to make the like count linearizable "so it's always exactly right." Each increment currently is a ~1 ms local write that's always available. After the change, each increment must confirm with a cross-region quorum. Roughly what does one increment now cost in latency, and what happens to the counter during a regional network partition — and was any of it worth it for a like count?
Hint: A quorum increment pays a cross-region round-trip; a linearizable operation on the minority side of a partition must refuse. Then ask what the like count actually needed.
One increment jumps from about 1 ms to roughly 100–150 ms, because it now waits for a cross-region round-trip to a majority of replicas before it can acknowledge — a 100× latency hit on the hot path. And during a regional partition, the minority side can't reach a majority, so a linearizable counter there must refuse increments and often refuse reads too: the feature goes down for those users until the network heals. In exchange you got a count that's never off by one. But a like count never needed that — being briefly off by one, then converging, harms nobody. So the change bought a 100× slowdown and new downtime to solve a problem the feature didn't have. The correct answer to the PM is: keep the like count eventually consistent, and spend linearizability on the username claim and the account balance, where an off-by-one really is a bug. That is the whole discipline — match the guarantee to the need, and refuse to pay for strength a feature can't use.
The decision tool in practice: which model for which feature
The payoff of the whole spectrum is that you can look at a feature and name the weakest model that still makes it correct — because the weakest correct model is also the cheapest and most available one. A few worked calls:
- Account balance, username uniqueness, inventory-count-at-checkout, a distributed lock: linearizable (or strict-serializable if it's a multi-row transaction). These break if two operations see a stale value — double-spend, duplicate username, oversold stock. Pay the coordination cost; it's the point.
- A shopping cart, a comment thread, collaborative document edits: causal is usually enough. You need a reply after its message and your removals to stick, but you don't need a global real-time order across unrelated users. This is also where CRDTs (/crdts) let concurrent edits merge without a leader.
- Like counts, view counts, activity feeds, most analytics: eventual. Off-by-a-little-for-a-moment is invisible, and you want the speed and the always-up behavior. Tune it with the quorum knob if you want a little more freshness for a little more latency.
- Anything user-facing where the person must see their own action: add the session guarantees (read-your-writes, monotonic reads) on top of an otherwise-weak model, via sticky routing. Cheap, and it's what removes the "where did my comment go?" feeling.
The spectrum in one table
The four global models side by side, with what each guarantees and what it costs:
| Model | A read returns… | Cost | Reach for it when… |
|---|---|---|---|
| Linearizable | The latest write, in real time | A coordination round-trip; the minority refuses under a partition | Correctness breaks on any stale read (locks, balances, uniqueness) |
| Sequential | A value consistent with one agreed order (maybe behind) | Agreement on an order, but no real-time clock | You need one global order but can tolerate slight staleness |
| Causal | Cause always before effect; unrelated writes may reorder | Tracking happens-before (version vectors); stays available under a partition | Threads, carts, collaborative edits |
| Eventual | Possibly old; converges once writes stop | Almost nothing — local reads, background merge | Counts, feeds, anything off-by-a-little is fine |
Reading it top to bottom, each row trades a guarantee for latency and availability. The skill isn't picking the strongest row; it's picking the weakest row that still keeps the feature correct.
In the wild
- Cassandra / DynamoDB — PA/EL, leaderless and eventually consistent by default, with a per-query knob (the quorum R and W) to buy more freshness when a specific read needs it. Chosen for uptime and low latency on workloads (feeds, counts, sessions) that tolerate staleness.
- Google Spanner / CockroachDB — PC/EC, strict-serializable: transactions in a real-time order. Spanner pays for it with tightly-synchronized clocks (TrueTime) and a commit-wait, accepting latency to make even multi-row transactions behave as if run one at a time. Chosen where money and correctness are on the line.
- MongoDB — PA/EC by default: a replica set gives up consistency for availability under a partition, but reads from the primary are consistent when healthy. Write concern and read concern let you dial toward linearizable per operation when you need it.
- etcd / ZooKeeper / Consul — linearizable single-key stores built on consensus (Raft, or ZooKeeper's ZAB). They're small and slow relative to a cache, on purpose: they hold the data that must be exactly right — leader election, locks, config — where a stale read would corrupt the system.
- PNUTS (Yahoo) — the classic PC/EL system, and the reason PACELC needed a fourth box: consistent under a partition, but favoring low latency (per-record timeline, not global) in normal operation.
Common questions & gotchas
Isn't "strong consistency" a single, well-defined thing?
No, and that ambiguity causes real bugs. It usually means linearizable (single-object, real-time), but people also use it for strict-serializable (multi-object transactions in real-time order) and sometimes just for "more consistent than eventual." A linearizable key-value store and a strict-serializable database make different promises. When you hear "strongly consistent," pin down which guarantee — single-key or transactional — before you rely on it.
Does CAP mean I have to pick two of consistency, availability, and partition tolerance?
That's the popular misreading. You don't get to opt out of partition tolerance — networks partition whether you like it or not, so P is a fact, not a choice. CAP's real content is narrower: during a partition you must choose between C and A. PACELC is the fuller picture, because it also names the consistency-versus-latency choice you make in normal operation, which is the choice you're actually making almost all the time.
If each key is linearizable, is my whole system linearizable?
No — linearizability is per-object and doesn't compose across objects. Two keys can each be individually linearizable while a pair of operations spanning both keys sees an inconsistent combination, because there's no guarantee they take effect in a single shared order. Guaranteeing a consistent order across multiple objects is a transaction property (serializability), which is a stronger and more expensive thing than per-key linearizability.
Is eventual consistency just "consistency, but slower to arrive"?
It's weaker than that sounds. Between writes, eventual consistency makes no promise about order or recency at all: two reads can disagree, and a read can move backward relative to an earlier one. The only promise is convergence once writes stop. That's why you usually layer session guarantees (read-your-writes, monotonic reads) on top — to give a single user a sane view even though the global model is eventual.
Isn't causal consistency basically as good as linearizable, but cheaper?
For many apps, yes — and it has a special property: causal is the strongest model that stays available under a partition (shown by Bailis and others). But it genuinely reorders independent writes across replicas, so anything needing a single global real-time order (a lock, a uniqueness check, a balance) is not safe on causal. It's the right ceiling for collaborative and social data, not for money.
QuizA team makes every feature in their app strictly serializable "to be safe," including the view counter on each video. In a healthy, un-partitioned network they see p99 latency spike and cross-region reads crawl. What's the diagnosis and the fix?
- Their network is partitioned; add more replicas.
- They're paying consistency-for-latency on every operation (PACELC's EC), including features that never needed it; downgrade the view counter and other tolerant features to eventual and reserve strong consistency for correctness-critical ones.
- Serializability has no latency cost when healthy; the problem must be disk I/O.
- Switch everything to eventual consistency to fix the latency.
Show answer
They're paying consistency-for-latency on every operation (PACELC's EC), including features that never needed it; downgrade the view counter and other tolerant features to eventual and reserve strong consistency for correctness-critical ones. — This is the PACELC 'else' cost, not a partition problem. A strict-serializable operation coordinates across machines on every request — a cross-region round-trip of ~100–150 ms — so making the view counter (and everything else) strong means paying that latency universally, even with a perfectly healthy network. The fix isn't to abandon strong consistency everywhere (option 4 would corrupt the balance and uniqueness features that genuinely need it) and it isn't more hardware. It's to match the model to the need: keep strict-serializable for money and correctness-critical writes, and drop view counts, feeds, and similar to eventual, which reads locally in under a millisecond and stays available. Strong-everywhere is the expensive default this whole topic exists to talk you out of.
In an interview
Consistency is a favorite because it separates people who memorized "CAP: pick two" from people who can actually make the call. The move that signals the second kind: don't reach for the strongest model. Ask what the feature needs, name the weakest model that keeps it correct, and justify the trade out loud.
- Lead with the spectrum, not a definition: linearizable → sequential → causal → eventual, each step dropping an ordering guarantee to buy latency and availability. That framing shows you see a menu, not a binary.
- Don't conflate linearizable and serializable — this is the classic trap. Linearizable is single-object and real-time; serializable is multi-object transactions in some serial order; strict-serializable is both. Naming the difference unprompted is a strong signal.
- Correct the CAP misreading: you can't drop partition tolerance, so CAP is really a C-vs-A choice during a partition. Then upgrade to PACELC to name the latency-vs-consistency choice you make in normal operation — the case you're in almost always.
- Make the cost a number: a linearizable operation is a coordination round-trip, ~100–150 ms cross-region versus ~1 ms local, plus the minority going unavailable under a partition. That's the argument for not defaulting to strong.
- Close on the decision tool: match the guarantee to the feature (balance/username → linearizable; thread/cart → causal; likes/views → eventual), and add session guarantees via sticky routing so users see their own writes. That's the answer that sounds like you've shipped this.
PredictThe interviewer says: 'We're designing a checkout. Adding an item to the cart feels laggy across regions, but we can't ever oversell the last unit of stock. How would you set consistency?' What's the strong answer?
Hint: Two parts of one flow with opposite needs — is the cart the same consistency as the stock decrement?
Split the feature by what each part actually needs, rather than picking one model for the whole checkout. Adding to the cart doesn't need a global real-time order — it needs the user to see their own additions and not have them reorder, so run the cart on a weak, low-latency model (eventual, or causal) with the session guarantees (read-your-writes, monotonic writes) via a sticky route to a nearby replica; that kills the cross-region lag they're complaining about. The stock decrement at checkout is the opposite: overselling the last unit is exactly the double-spend that a stale read causes, so that single operation must be linearizable — a quorum or leader-coordinated compare-and-decrement so two checkouts can't both grab the last unit. The strong answer is naming that these two parts sit at opposite ends of the spectrum on purpose: pay the coordination cost only on the decrement, and keep the cart fast and always-available. If they push, mention that the decrement is really a small transaction (check stock ≥ 1, then subtract) so it wants strict-serializability, not just per-key linearizability.
References & further reading
- Jepsen — Consistency Models (the interactive map) — Aphyr's map of how the models relate and which stay available under a partition — the canonical picture.
- Strong consistency models (Kyle Kingsbury / aphyr) — Plain-English walk from linearizable down through causal and eventual, with the real-time distinction.
- Consistency Tradeoffs in Modern Distributed Database System Design (Abadi, 2012) — The PACELC paper: why the latency-consistency trade in normal operation matters as much as CAP's partition trade.
- Highly Available Transactions: Virtues and Limitations (Bailis et al., VLDB 2014) — Which guarantees can and cannot stay available under a partition — the ceiling on causal and the session guarantees.
- Designing Data-Intensive Applications, Ch. 5 & 9 (Kleppmann) — Replication lag and the session guarantees (ch5); linearizability, ordering, and consensus (ch9).