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.