The consistency ladder and the session guarantees
What it is
A set of models describing what a distributed system promises about the order in which operations become visible. They are commonly recited as a list, which hides the two facts that matter: linearizability and serializability are about different things, and the session guarantees are what users actually perceive.
STRONGEST
Strict serializability serializable + linearizable
Linearizability single-object, real-time order
Sequential consistency single-object, some global order
Causal consistency causally related ops ordered
Session guarantees per-client ordering promises
Eventual consistency replicas converge, eventually
WEAKEST
Two orthogonal axes, and conflating them is the single most common error:
LINEARIZABILITY is about SINGLE OBJECTS and REAL TIME.
"Once a write completes, every subsequent read
sees it or something later."
It says nothing about multiple objects.
SERIALIZABILITY is about TRANSACTIONS over MULTIPLE objects.
"The result is equivalent to SOME serial order
of the transactions."
It says nothing about which order, so it permits
a transaction to see stale data as long as the
outcome is equivalent to some serial schedule.
A system can be serializable and not linearizable: a transaction can commit and a later transaction can be ordered before it in the equivalent serial schedule, so a read after a write can miss the write. Strict serializability is both, and it is what Spanner and CockroachDB provide.
Commonly confused with the CAP theorem's "C", which is specifically linearizability, not serializability and not "consistency" in the everyday sense. That imprecision is why CAP discussions go badly.
The problem it solves
Without a named model, a system's guarantee is whatever its implementation happens to do, and every consumer builds a different mental model of it.
"Our database is consistent."
Which of these is true?
- A read after a write always sees it? (linearizable)
- Concurrent transactions can't interleave
into an impossible state? (serializable)
- Everyone sees writes in the same order? (sequential)
- A reply always appears after the message
it replies to? (causal)
- You see your own writes? (session)
- Replicas agree given enough time? (eventual)
These are six different promises with wildly different costs, and a team that has not named which one it provides is providing the weakest one by accident, because that is what falls out of asynchronous replication.
Mechanics
Linearizability, precisely
Every operation appears to take effect atomically at some instant between its invocation and its response, and that instant respects real time.
Client A: |--- write(x=1) ---|
Client B: |--- read(x) ---| MUST return 1 or later
Client C: |--- read(x) ---| MUST return 1
Once B's read has returned 1, no later read may return 0.
That "no going back" property is what makes it linearizable
rather than merely sequentially consistent.
The cost is a round trip to a quorum on every operation, because a node cannot know it has the latest value without asking. That is why linearizable reads in etcd, ZooKeeper and Consul are more expensive than local reads, and why every one of those systems offers a weaker read mode.
etcd: linearizable read = a quorum round trip
serializable read = read from the local replica, may be
stale, and is far faster
ZooKeeper: writes are linearizable; READS ARE NOT by default.
sync() before a read forces linearizability.
ZooKeeper's read behaviour surprises people and is worth knowing: it provides writes in
a total order and reads that may be stale, so a client can write and then read its own write
from a lagging follower and not see it, unless it calls sync().
Sequential consistency, and why it is weaker
Sequential: all operations appear in SOME total order that
respects each client's program order.
Linearizable: that total order must also respect REAL TIME
across clients.
Concretely:
A writes x=1 at t=0, completes at t=1.
B reads x at t=2, returns 0.
Linearizable? NO. B's read started after A's write completed.
Sequentially consistent? YES, if the total order is
[B's read, A's write] and that is consistent with each
client's own program order.
Sequential consistency permits the system to reorder across clients as long as each client's own operations stay in order. It is rarely offered as a headline guarantee precisely because the anomaly above surprises everyone.
Causal consistency: the useful middle
Operations that are causally related are seen in that order by everyone; concurrent operations may be seen in different orders by different observers.
Alice posts: "I lost my job" (event e1)
Bob comments: "So sorry to hear that" (event e2, caused by e1)
Causal consistency GUARANTEES nobody sees e2 before e1.
Eventual consistency does NOT, and the failure is exactly the
"sympathy for an event you haven't seen" bug that social
platforms hit repeatedly.
Meanwhile, if Carol independently posts "Nice weather" (e3),
concurrent with e1, different users may see e1 and e3 in
different orders, and nobody cares.
Causal consistency is the strongest model achievable without sacrificing availability under partition (Mahajan, Alvisi and Dahlin, 2011), which is a genuinely important result: it is the ceiling for an AP system.
The mechanism is tracking causality, typically with vector clocks or explicit dependency metadata, and the cost is metadata that grows with the number of writers.
The four session guarantees, which are what users notice
This is the part that matters most in practice and the part that gets skipped. Users do not perceive linearizability; they perceive these:
READ YOUR WRITES
After you write, your subsequent reads see it.
Violated by: writing to a leader, reading from a lagging
follower. The classic "I updated my profile and it still
shows the old name" bug.
MONOTONIC READS
You never see time go backwards. If you read a value, a later
read returns that value or newer.
Violated by: round-robin across replicas with different lag.
The user refreshes and a comment DISAPPEARS.
MONOTONIC WRITES
Your writes are applied in the order you issued them.
Violated by: routing successive writes to different replicas.
You set a value then delete it, and the delete arrives first,
so the value persists.
WRITES FOLLOW READS (session causality)
If you read a value and then write, your write is ordered
after the value you read.
Violated by: replying to a comment on replica A while replica
B receives your reply before the comment.
These four are cheap to provide and they cover the overwhelming majority of user-perceived anomalies. The standard mechanisms:
# Sticky routing: pin a session to one replica. Simple, and it
# breaks on replica failure and creates load imbalance.
replica = consistent_hash(session_id) % replicas
# Version tokens: the client carries the version it last saw,
# and the serving replica either satisfies it or waits.
# More work, and it survives replica changes, which sticky
# routing does not.
def read(key, session_version):
local = store.get(key)
if local.version < session_version:
if not wait_for_version(key, session_version, timeout_ms=50):
return read_from_leader(key) # fall back
return local
Version tokens are the better mechanism because they survive a replica failing or the load balancer rehashing, which sticky routing does not, and because they degrade gracefully: the fallback is a slower read rather than a wrong one.
The costs, concretely
Model Read cost Write cost Available
under partition?
--------------------------------------------------------------------------
Strict serializability quorum RT + txn quorum RT + 2PC NO
Linearizable quorum RT quorum RT NO
Sequential quorum RT quorum RT NO
Causal local local + metadata YES
Session guarantees local (+ wait) local YES
Eventual local local YES
quorum RT = one round trip to a majority, so intra-region
~1-2 ms, cross-region 60-200 ms.
The step from causal to sequential is where availability is lost, and that is the CAP result stated usefully: everything at or below causal can remain available under partition; everything above it cannot.
The one that is not on the ladder: snapshot isolation
Snapshot isolation is not a point on this ladder and gets placed on it incorrectly all the time. It is a transaction isolation level that is weaker than serializable and permits write skew, which is a specific anomaly where two transactions each read an overlapping set, make disjoint writes, and together violate an invariant neither violated alone. See write skew and snapshot isolation.
PostgreSQL REPEATABLE READ = snapshot isolation, permits write skew
PostgreSQL SERIALIZABLE = SSI, prevents it, at the cost of
serialization failures you must retry
A worked example: choosing per operation
A social platform. The insight to lead with: the consistency model is a per-operation decision, not a system-wide one.
OPERATION MODEL WHY
---------------------------------------------------------------------
Username registration linearizable Uniqueness is a
(consensus) real invariant.
Two people cannot
both get "alice".
Payment / balance strict serial. Multi-object
invariant plus
real-time ordering.
Post a status session guar. The author must see
their own post; nobody
else notices a second.
Comment on a post causal A reply must never
appear before the
thing it replies to.
Like count eventual Nobody can tell 1,247
(CRDT counter) from 1,251, and a
counter converges
without conflict.
Follower list session guar. You must see your own
follow immediately;
others can lag.
Search index eventual Seconds of staleness
is invisible.
Cost consequence: only two of seven operations pay for consensus. If the system had been designed with one model, either everything pays a quorum round trip (and the like button is absurdly expensive) or nothing does (and two users can register the same username).
The specific mechanism for the session-guarantee rows: the client carries a version token, the read path compares it, and on a miss it waits briefly or falls back to the leader. That is roughly fifty lines of code and it eliminates the entire class of "I posted and it's not there" complaints, which is the highest-value consistency work most systems can do.
Production evidence
Herlihy and Wing, "Linearizability: A Correctness Condition for Concurrent Objects" (TOPLAS 1990) is the formal definition, and its scope, single objects with real-time ordering, is what distinguishes it from serializability.
Terry et al., "Session Guarantees for Weakly Consistent Replicated Data" (1994, from the Bayou project) defines the four session guarantees and is the primary source for the argument that they are what users perceive.
Mahajan, Alvisi and Dahlin, "Consistency, Availability, and Convergence" (2011) proves that causal consistency is the strongest model achievable in an always-available system, which is the precise version of the CAP trade-off.
Corbett et al., "Spanner: Google's Globally-Distributed Database" (OSDI 2012) provides strict serializability using TrueTime's bounded clock uncertainty, and its documented commit wait (waiting out the uncertainty interval, historically single-digit milliseconds) is the concrete price of that guarantee.
Amazon DynamoDB offers eventually consistent reads at half the cost of strongly consistent ones, which is direct commercial evidence that the guarantee is a priced choice rather than a property of the system.
ZooKeeper's documented consistency model is linearizable writes with non-linearizable
reads unless sync() is called, and the surprise this causes in practice is why "which
reads are linearizable" is the right question to ask of any coordination system.
Jepsen's analyses (Kyle Kingsbury) have repeatedly found that systems' documented consistency claims do not match their behaviour under partition, which is the strongest available argument for testing the claim rather than trusting the documentation.
The debate
The case for strong consistency everywhere: application code that assumes linearizability and gets it is dramatically simpler, and the bugs from weak consistency are subtle, intermittent and expensive to debug. Spanner's argument is essentially that developer time is worth more than the latency, and for many workloads it is.
The case for eventual consistency: availability under partition, local-latency reads and writes, and horizontal scale without coordination. Dynamo, Cassandra and every CDN are built on it and reach scales that coordination-based systems do not.
The case for causal: it is the strongest model compatible with availability, and it eliminates the anomalies users actually notice while keeping local latency.
My position: choose per operation, default to session guarantees, and reserve consensus for genuine invariants.
The framing I would insist on is that this is not a system-wide decision. In the example above, two of seven operations need consensus and five do not, and a single system-wide model either makes the like button absurdly expensive or lets two users claim the same username. Naming which operations have real invariants, uniqueness, balances, monotonic counters that must not go backwards, is the actual design work.
The default I would pick is session guarantees, because they are cheap, they are implementable with a version token in about fifty lines, and they eliminate the entire class of anomalies users actually perceive: not seeing your own write, seeing a comment disappear on refresh, or a reply appearing before the thing it replies to. Systems that skip them and provide raw eventual consistency generate a steady stream of support tickets that read like bugs and are the documented behaviour.
The precision I would hold to is not calling everything "consistency". Linearizability is
single-object and real-time; serializability is multi-object and says nothing about real
time; snapshot isolation is neither and permits write skew. Those are three different
promises and using one word for all of them is how teams end up believing PostgreSQL
REPEATABLE READ prevents an anomaly that it explicitly permits.
Where I would push back on a requirement: "we need strong consistency" is almost never a requirement, it is an unexamined default. The useful question is which specific invariant would be violated, and the answer is usually about one or two operations rather than the whole system.
Follow-up Q&A
"What is the difference between linearizability and serializability?" They are about different things. Linearizability is a single-object, real-time property: once a write completes, every subsequent read sees it or something later. Serializability is a multi-object transaction property: the result is equivalent to some serial order of the transactions, and it says nothing about which order, so a transaction can be ordered before one that committed earlier in real time. A system can be serializable and not linearizable. Strict serializability is both, which is what Spanner provides.
"Where does CAP fit?" CAP's "C" is specifically linearizability, not serializability and not consistency in the everyday sense, and that imprecision is why CAP arguments go badly. The more useful statement of the trade-off is the Mahajan, Alvisi and Dahlin result: causal consistency is the strongest model achievable in an always-available system. So everything at or below causal can stay available under partition, and everything above it cannot. That is CAP restated as something you can design with.
"What are the session guarantees and why do they matter?" Four: read your writes, monotonic reads, monotonic writes, and writes follow reads. They matter because they are what users actually perceive. Nobody notices a linearizability violation between two strangers; everybody notices updating their profile and seeing the old name, or refreshing and having a comment disappear because the second read hit a replica with more lag. They are cheap, implementable with a version token, and they eliminate the whole class of anomalies that generate support tickets.
"How do you implement read-your-writes?" Either sticky routing, pinning a session to one replica, or version tokens, where the client carries the version it last observed and the serving replica compares. I would use version tokens, because sticky routing breaks when a replica fails or the load balancer rehashes, and it creates load imbalance. With version tokens the replica either satisfies the version, waits briefly for replication, or falls back to the leader, so the degradation is a slower read rather than a wrong one.
"What model do you choose for a system?" Not one, per operation. In a social platform, username registration needs consensus because uniqueness is a real invariant and two people cannot both have "alice". Payments need strict serializability. Posting a status needs session guarantees so the author sees their own post. Comments need causal so a reply never appears before what it replies to. Like counts can be eventual with a CRDT counter, because nobody can tell 1,247 from 1,251. That is two of seven operations paying for consensus, and a single system-wide model gets it wrong in one direction or the other.
"Where does snapshot isolation sit on the ladder?" It does not, and putting it there is a
common error. It is a transaction isolation level, weaker than serializable, and it permits
write skew: two transactions each read an overlapping set, write disjoint rows, and together
violate an invariant neither violated alone. PostgreSQL REPEATABLE READ is snapshot
isolation and permits it; SERIALIZABLE uses SSI and prevents it, at the cost of
serialization failures you have to retry.
"Why is causal consistency interesting?" Because it is the ceiling for an available system, which is a proved result rather than a rule of thumb, and because it eliminates the anomaly users find most jarring: seeing an effect before its cause. "So sorry to hear that" appearing before "I lost my job" is the canonical example, and social platforms have hit it repeatedly. It costs causality metadata that grows with the number of writers, which is the practical limit on it.
"Which reads are linearizable in the system you're using?" That is the question I would
ask of any coordination system, because the answer is often surprising. ZooKeeper's writes
are linearizable and its reads are not, so a client can write and then read a stale value
from a lagging follower unless it calls sync() first. etcd offers both modes explicitly,
with linearizable reads costing a quorum round trip and serializable reads served locally.
Knowing which one your client library defaults to is worth checking rather than assuming.
"How much does strong consistency actually cost?" A quorum round trip per operation, which intra-region is one to two milliseconds and cross-region is 60 to 200. Spanner adds a commit wait to absorb clock uncertainty on top of that. DynamoDB charges twice as much for a strongly consistent read as an eventually consistent one, which is a useful way to see that the guarantee is a priced choice. So the question "do we need strong consistency" is really "which specific invariant would be violated without it", and the answer is usually one or two operations rather than the system.
Common misconceptions
"Linearizable and serializable are the same." One is single-object and real-time, the other is multi-object and order-agnostic. Strict serializability is both.
"CAP's C means consistency generally." It means linearizability specifically, and using the word loosely is why CAP discussions produce heat rather than decisions.
"Eventual consistency means users see stale data." With session guarantees they never see data older than what they already saw, which is the property that actually matters to them.
"Snapshot isolation prevents anomalies." It permits write skew, which is why
SERIALIZABLE exists as a separate level.
"Pick one consistency model for the system." It is a per-operation decision, and a system-wide choice is wrong in one direction or the other.
Interview delivery note
Separate the two axes first, because conflating them is the tell: "Linearizability and serializability get used interchangeably and they're about different things. Linearizability is single-object and real-time: once a write completes, every later read sees it. Serializability is multi-object transactions being equivalent to some serial order, and it says nothing about which order, so it permits a read to miss a committed write. Strict serializability is both, which is what Spanner gives you."
Then move immediately to the practical layer, because that is where the value is: "But the models users actually perceive are the session guarantees: read your writes, monotonic reads, monotonic writes, and writes follow reads. Nobody notices a linearizability violation between two strangers. Everybody notices updating their profile and seeing the old name, or refreshing and having a comment disappear. Those four are cheap and they're what I'd default to."
Refuse the system-wide framing: "And I wouldn't pick one model for the system. In a social platform, username registration needs consensus because uniqueness is a real invariant. Payments need strict serializability. Posting needs session guarantees. Comments need causal so a reply never precedes what it replies to. Like counts can be a CRDT counter, because nobody can tell 1,247 from 1,251. Two of seven operations pay for consensus."
Two lines that show precision: "CAP's C is linearizability specifically, not consistency generally, which is why those arguments go badly. The more useful result is that causal is the strongest model compatible with staying available under partition." And: "snapshot isolation isn't on this ladder at all. It's an isolation level that permits write skew, which is why PostgreSQL has SERIALIZABLE as a separate thing."
Further reading
- Herlihy and Wing, "Linearizability: A Correctness Condition for Concurrent Objects" (TOPLAS 1990).
- Terry et al., "Session Guarantees for Weakly Consistent Replicated Data" (1994).
- Mahajan, Alvisi and Dahlin, "Consistency, Availability, and Convergence" (UT Austin TR, 2011), for causal as the availability ceiling.
- Bailis et al., "Highly Available Transactions: Virtues and Limitations" (VLDB 2014), for what is achievable without coordination.
- Kyle Kingsbury's Jepsen analyses, for the gap between documented and actual behaviour, and his "Consistency Models" reference map.