Consistency models
Every consistency model is just a rule about which stale reads you are willing to let a user see.
Consistency is a contract about what a read may return
Once data exists in more than one place — a replica, a cache, a second region — "what is the current value?" stops having a single answer. A consistency model is the contract you offer the application about which of the possible answers a read is allowed to produce. Stronger models forbid more answers, which costs coordination, which costs latency and availability.
Interviewers push on this because it is the one topic where candidates reliably say something confidently wrong ("we'll use eventual consistency for speed") without noticing they just permitted a user to see their own comment disappear. The senior move is to pick a model per operation and justify it in product terms.
Replication lag is the concrete thing underneath all of it
"Eventual consistency" sounds abstract until you attach numbers to it. Lag is the wall-clock delay between a write committing on the primary and being visible on a given replica, and its distribution is wildly skewed — the median is boring and the tail is where your bugs live.
| Setup | Typical lag | Tail behaviour |
|---|---|---|
| Same-AZ async replica | 0.5-2 ms | Tens of ms under write bursts |
| Cross-AZ, same region | 2-10 ms | 100 ms+ during compaction or vacuum |
| Cross-region (us-east → eu-west) | 70-120 ms, floor set by RTT | Seconds if the link saturates |
| Any replica during a bulk write, index build, or long transaction | seconds to minutes | Single-threaded replay (classic MySQL) can fall hours behind and never catch up until write traffic drops |
| Read-through cache with 60 s TTL | up to 60 s | This is replication lag too. People forget caches are replicas |
The operationally important property: lag is not bounded. Any design that says "the replica is only a few milliseconds behind, so it's fine" is a design that breaks during the exact incident where correctness matters most. If you need a bound, you must enforce one — by reading the primary, by waiting for a version token, or by refusing to serve from a replica whose lag exceeds a threshold.
The two poles, and the useful middle
Strong consistency (informally: every read sees the latest committed write) requires the read to be coordinated with the write — route to the leader, or read a quorum, or hold a lease. Cost: at least one extra round trip, and unavailability whenever the leader is unreachable. Eventual consistency promises only that if writes stop, replicas converge. Cost: nothing; guarantee: nearly nothing. Any stale value is legal, in any order, on any read.
Between them sit the session guarantees — the models that actually ship in real products, because they fix the anomalies users can perceive without paying for global coordination.
| Model | Forbids | How it's implemented |
|---|---|---|
| Read-your-writes | You post a comment and it isn't there on refresh | Route a user's reads to the primary for N seconds after their write; or return a version token (Postgres LSN, MySQL GTID) with the write and have the read wait for a replica that has caught up to it |
| Monotonic reads | Refreshing shows a comment, then it vanishes, then it's back | Sticky routing: hash the user to one replica so they never move backwards in the replication stream |
| Consistent prefix | Seeing the reply "because he's late" before the question "why?" | Preserve write order per partition; don't shard causally-related rows across independently-replicated partitions |
| Causal consistency | Any effect visible before its cause, across users | Track happens-before with vector clocks or dependency metadata and delay applying an update until its dependencies have landed |
Quorums: R + W > N, with the numbers worked out
In a leaderless or quorum-replicated store (Dynamo, Cassandra, Riak) you
choose three numbers: N replicas hold each key, a write must be
acknowledged by W of them, a read must gather R of them. If
R + W > N, the read set and the write set must overlap in
at least one node by pigeonhole — so at least one responding replica has
the newest write, and version stamps let the reader pick it.
| N, W, R | Overlap? | Write tolerance | Read tolerance | Character |
|---|---|---|---|---|
| 3, 2, 2 | Yes (4 > 3) | 1 node down | 1 node down | The default. Balanced, survives one failure on both paths |
| 3, 3, 1 | Yes (4 > 3) | 0 — any node down blocks writes | 2 nodes down | Read-optimised: 1-replica reads are fast and local, writes are brittle |
| 3, 1, 3 | Yes (4 > 3) | 2 nodes down | 0 | Write-optimised: fast durable-ish writes, fragile reads |
| 3, 1, 1 | No (2 < 3) | 2 down | 2 down | Pure eventual consistency. Lowest latency, highest availability, a read can miss a write entirely |
| 5, 3, 3 | Yes (6 > 5) | 2 down | 2 down | Survives two failures on both paths; ~1.5x the write cost. Standard for critical data |
Latency follows directly: a W=2 write waits for the second-fastest of three replicas, so it inherits that node's p95, not the fastest node's. Raising W or R moves you further into the tail of the slowest responder — which is why quorum systems tune these per-query rather than globally.
Linearizability vs serializability — telling them apart for good
They sound like synonyms, they come from different fields, and mixing them up is the fastest way to lose credibility on this topic.
- Linearizability is about single objects and real time. Every operation appears to take effect instantaneously at some point between its invocation and its response, and that point respects wall-clock order: if write W completes before read R begins, R must see W. It says nothing about multi-key transactions. It is a recency guarantee — the C in CAP.
- Serializability is about multi-object transactions and says nothing about real time. Concurrent transactions produce a result equivalent to some serial order — and that order need not match the order they actually happened in. A transaction that committed an hour ago may legally be ordered after one committing now. It is an isolation guarantee — the I in ACID.
- Strict serializability is both: a serial order that also respects real time. This is what Spanner (via TrueTime), FaunaDB and CockroachDB market as "external consistency", and it is the strongest practical model.
The one-liner worth memorising: linearizability is about recency of a single key; serializability is about the illusion of one-at-a-time transactions. A database can be serializable and still let you read stale data (Postgres SERIALIZABLE on a replica); a store can be linearizable per key and have no transactions at all (etcd, ZooKeeper).
Choosing per operation: model, cost, and what it looks like to a user
| Model | Guarantee | Cost | Real product example |
|---|---|---|---|
| Eventual | Replicas converge if writes stop | None — local read, local write, survives partitions | Like count, view count, follower count. Off by 3 for 200 ms; nobody can tell, and nobody is harmed. Rendering it as "1.2k" makes the staleness literally invisible |
| Monotonic reads | Never move backwards in time | Sticky routing — mild load imbalance, awkward on replica failover | An infinite feed. Items reappearing or vanishing while scrolling reads as a bug even when values are individually fine |
| Read-your-writes | You always see your own effects | Primary reads for a short window, or LSN-waiting on read | Posting a comment, editing a profile, uploading an avatar. The cheapest model that makes the product feel correct — and the default you should reach for on any write-then-read flow |
| Causal | Effects never precede their causes | Dependency metadata on every write; real complexity | Threaded comments and chat. A reply must not be visible before the message it replies to |
| Linearizable (single key) | Reads see the latest committed write, in real-time order | Leader round trip; unavailable during leader loss (CP) | Bank balance display, seat inventory, feature-flag kill switch. Being 200 ms stale is a wrong number on a screen someone will act on |
| Strict serializable / transactional | Multi-key transactions in a real-time-respecting serial order | Consensus per commit; cross-region commits cost an inter-region RTT | Username reservation, seat booking, money transfer. Two users claiming @ada concurrently must produce exactly one winner — this needs a uniqueness constraint or a compare-and-set, not a read-then-write |
- If a user sees a value 5 seconds old, what happens? Nothing → eventual. Confusing → session guarantees. Wrong decision or lost money → strong.
- Can two concurrent writes both "win"? If the answer must be no (username, seat, inventory), no amount of replication tuning helps — you need a single serialisation point: a uniqueness constraint, a conditional write, or consensus.
- Who notices the staleness — the writer or a third party? Only the writer → read-your-writes is enough and it's cheap. Everyone → you're in strong-consistency territory.
Recognizing it in an unseen problem
- Signals: the moment your diagram grows a read replica, a cache, or a second region, you have chosen a consistency model — the only question is whether you chose it deliberately. "Multi-region", "read replicas", "global users" are all consistency prompts in disguise.
- The naive design says "we'll use eventual consistency, it's more available" and then puts a write-then-read flow on top of it. Almost every user-visible consistency bug is a read-your-writes violation, not a deep causality problem.
- Distinguishing it from CAP hand-waving: CAP is a statement about behaviour during a network partition, which is rare. Replication lag is present every single second of normal operation. Talk about lag; mention CAP only if partitions are actually in scope.
- Uniqueness and inventory are not consistency-tunable. If two concurrent operations must not both succeed, no choice of R and W saves you — you need a single point of serialisation. Say that explicitly; it's a common trap.
- Quantify. "Cross-region lag is ~100 ms, so a European read of a US write can be stale for a tenth of a second — fine for a like count, not for a payment confirmation" is worth more than three paragraphs of theory.
- The pitfall to avoid: using "strongly consistent" and "serializable" interchangeably. If you only remember one thing: linearizability = recency of one key; serializability = transactions appear one-at-a-time.