What “the data is correct” means when the data lives on five machines — and why the right answer is different for a bank balance and a like counter.
Prerequisites: Scalability, Availability Time to read: ~20 minutes
One database, one copy of the data: “consistency” is trivial. You wrote 5, you read 5.
Now you have three replicas, because you need availability and read throughput. A write lands on replica A. Half a millisecond later, someone reads from replica C — which hasn’t received it yet.
Time Replica A Replica B Replica C
t=0 balance=100 balance=100 balance=100
t=1 balance=50 ←write
t=2 balance=50 balance=50
t=3 balance=50 ← finally
↑
A read here returns 100. Is that a bug?
It depends entirely on what the data is. If it’s a bank balance and you’re about to authorize a withdrawal, that’s a serious bug. If it’s a like count on a photo, nobody will ever notice or care.
A consistency model is a contract: it defines exactly what a reader is guaranteed to see. And because stronger guarantees require more coordination, they cost latency and availability. Choosing the model is one of the highest-leverage decisions in a design.
Five friends in a group chat with bad reception.
Every one of these is a real, named model, and each is the correct answer for some system.
The guarantee: the system behaves as if there is exactly one copy of the data, and every operation takes effect at a single instant between its start and completion. Once a write completes, every subsequent read — from any client, on any replica — returns that value or a later one.
How it’s achieved: coordination. Either all reads and writes go through one leader, or a quorum of replicas must agree before a write is acknowledged. → Consensus, Quorums
Cost: every write waits for a majority to acknowledge — so latency is bounded by your slowest necessary replica, and cross-region writes cost a full round trip (~150 ms). During a network partition, the minority side must refuse to serve rather than risk divergence.
Use for: account balances, inventory in a flash sale, unique constraints (usernames, seat booking), distributed locks, leader election, anything where a stale read causes money to move incorrectly.
Systems: etcd, ZooKeeper, Google Spanner, single-node relational databases, CockroachDB, FaunaDB.
All operations appear in some single global order that every node agrees on, and each individual client’s operations appear in the order they issued them. Weaker than linearizability because that global order need not match real time — an operation that finished before another started can appear after it.
Mostly of theoretical interest; you’ll rarely choose it explicitly, but it’s the standard answer to “what’s weaker than linearizable but still totally ordered?”
The guarantee: operations that are causally related are seen in the same order by everyone. Concurrent (unrelated) operations may be seen in different orders.
If Bilal posts “I lost my keys” and Ayesha replies “check the car,” nobody will ever see the reply without the original. But two unrelated posts might appear in different orders for different users — and nobody cares.
Why it’s attractive: it’s the strongest model that remains available during partitions. It gives you the guarantee humans actually notice (cause before effect) without global coordination.
How it’s tracked: vector clocks or explicit dependency metadata.
Use for: comment threads, social feeds, collaborative tools, messaging.
The guarantee: you always see your own writes. Other users may lag.
This is the one users complain about when it’s missing, and it’s the cheapest fix in this entire chapter. The classic bug:
User posts a comment → write goes to the primary
Page refreshes → read goes to a replica (lagging 200 ms)
Comment isn't there → "the site ate my comment!"
User posts it again → now there are two
Fixes, cheapest first:
🎙️ Bringing this up unprompted — “we should handle read-your-writes here, or users will think their post vanished” — is a small, specific, very effective way to demonstrate real experience.
The guarantee: you never see time go backwards. If you read a value, a later read won’t show you an older one.
Without it: you refresh a page and a comment appears, refresh again and it’s gone, refresh again and it’s back. This happens when successive requests hit replicas with different lag. Usually fixed by routing a given user consistently to the same replica.
The guarantee: if writes stop, all replicas eventually converge to the same value. That’s it. No promise about when, and no promise about what you see in the meantime — including reads that appear to go backwards.
Why anyone accepts this: it’s the cheapest and most available model. Writes are acknowledged immediately by any replica; no coordination, no waiting, and the system keeps serving during partitions.
📐 In practice “eventually” is usually milliseconds to seconds — but under load or partition it can be minutes, and your design must not break when it is.
Use for: view counts, like counts, follower counts, DNS, product recommendations, search indexes, analytics, caches.
Systems: Cassandra and DynamoDB (in their default modes), Riak, DNS, most CDNs.
| Model | Guarantee | Coordination cost | Available during partition? |
|---|---|---|---|
| Linearizable | Reads always see the latest write | High — quorum or leader per operation | ❌ Minority side must refuse |
| Sequential | One global order, per-client order preserved | High | ❌ |
| Causal | Cause always precedes effect | Medium — track dependencies | ✅ |
| Read-your-writes | You see your own writes | Low — routing trick | ✅ |
| Monotonic reads | Time never goes backwards for you | Low — sticky replica | ✅ |
| Eventual | Converges, someday | None | ✅ |
For each piece of data in your design, ask: “What is the actual consequence of a reader seeing a value that is 5 seconds old?”
| Consequence | Model |
|---|---|
| Money moves incorrectly; two people get the same seat; a duplicate username is created | Linearizable |
| A conversation reads out of order and looks broken | Causal |
| The user thinks their own action failed | Read-your-writes |
| A number is slightly off | Eventual |
| Nobody could possibly notice | Eventual |
🚨 The critical insight: this is a per-field decision, not a per-system one.
The same e-commerce system, in one request:
| Data | Model | Why |
|---|---|---|
| Inventory count at checkout | Linearizable | Overselling costs real money and angry customers |
| Payment record | Linearizable | Obviously |
| Order status for the buyer | Read-your-writes | They just placed it and must see it |
| Product description | Eventual | Nobody notices a 10-second-old description |
| “1,247 people viewed this” | Eventual | It’s a marketing number |
| Reviews | Eventual (causal for replies) | Replies must follow their parent |
| Recommendations | Eventual | It’s a guess anyway |
🎙️ A very strong interview move: “I’d use different consistency models for different parts of this. Inventory and payments need strong consistency, so those go through a linearizable store. The product catalogue and view counts are eventually consistent and can be served from replicas and caches. That gets us strong guarantees only where we pay for them.”
That single answer demonstrates you understand consistency as an engineering trade rather than a setting.
Confusingly, the C in ACID means something different from the C in CAP or from this chapter.
They are unrelated concepts that share a word. Knowing this distinction is a common interview follow-up, and getting it right is a nice signal. → Transactions
| Choice | Gain | Cost |
|---|---|---|
| Strong consistency | Simple reasoning; correctness by default | Higher latency (quorum/leader round trips); unavailable during partitions; harder to scale writes |
| Eventual consistency | Low latency, high availability, easy scaling | Application must tolerate stale and out-of-order reads; conflict resolution becomes your problem |
| Causal consistency | Preserves what users notice | Metadata overhead (vector clocks) and more complex implementation |
| Read-your-writes only | Fixes the complaint users actually have, cheaply | Doesn’t help cross-user staleness |
| Mixed per-field | Pay only where it matters | Two code paths; developers must know which is which |
1. See replication lag. Run Postgres with a streaming replica (Docker makes this a 10-minute job). Write to the primary and immediately read from the replica in a tight loop. Measure how often you get stale data, and how stale. Then put the replica under load and measure again — lag grows exactly when you least want it to.
2. Break read-your-writes on purpose. Build a two-endpoint app: POST /comment writes to the
primary, GET /comments reads from the replica. Add 200 ms of artificial replication delay. Use it.
You’ll immediately feel why users report “the site ate my comment.”
3. Classify a real system. Open any app you use. List ten pieces of data on one screen. For each, write down which consistency model it needs and what the worst case is if it’s 5 seconds stale. You’ll find that almost everything is fine with eventual, and one or two things absolutely aren’t.