Consistency Models

Once data lives on more than one machine, "read the latest value" stops being a simple idea. This page explains the main consistency models from strongest to weakest, what anomalies each allows, how quorums tune the trade-off, and how to get read-your-writes cheaply.


1. What a Consistency Model Is

Stale Read After a Write Client A Leader Follower write x=2 ok read x x=1 (stale) replication (lagging) Client A reads its own write from a follower before replication arrives: an eventual-consistency anomaly.

When data is copied across several machines (replicas) for availability and speed, different clients can see different versions at the same moment. A consistency model is the contract a storage system offers about which values a read may return, given the writes that have happened. It is the answer to "if I write and then read, what am I allowed to see?"

The diagram shows the classic surprise: a client writes to the leader, gets an acknowledgement, then reads from a follower that has not received the change yet. Whether this is a bug or acceptable behaviour depends entirely on the consistency model the system promises.

2. Why It Matters

Consistency models decide which application bugs are possible. A user who updates a profile and sees the old one assumes the save failed. A stock count read from a stale replica oversells. A comment that appears before the post it answers confuses everyone. Stronger models rule out more anomalies, but cost latency and availability: coordinating replicas requires network round trips, and during a network partition a system must choose between staying available and staying consistent (the CAP theorem). Knowing the precise guarantee lets you pick the cheapest one that keeps your application correct.

3. The Main Models, Strongest to Weakest

The Consistency Hierarchy (Strongest at Top) Linearizable real-time order, single copy Sequential one total order per program Causal cause before effect Read-your-writes session guarantee Monotonic reads session guarantee Eventual replicas converge, no order Each arrow points to a weaker model that the stronger one implies.

Linearizability (strong consistency, atomic consistency). The system behaves as if there were a single copy of the data and every operation took effect at one instant between its start and its end. Once a write completes, every later read, from any client, sees it or something newer. This is what most people intuitively expect. It typically needs consensus (Raft, Paxos) or a single leader with synchronous replication; examples include etcd, ZooKeeper writes, and Google Spanner (which calls it external consistency).

Sequential consistency. All clients see operations in the same total order, and each client's own operations appear in program order, but that order need not match real time. A read may return an older value as long as everyone agrees on the sequence.

Causal consistency. Operations that are causally related (a reply depends on the message it replies to; a write follows a read it was based on) are seen by everyone in that order. Unrelated, concurrent writes may be seen in different orders by different clients. Causal consistency is notable as the strongest model that can stay available during network partitions.

Session guarantees are weaker, per-client promises often layered on eventual systems: read-your-writes (you always see your own updates), monotonic reads (you never see time go backwards), monotonic writes, and writes-follow-reads. Together they are implied by causal consistency.

Eventual consistency. If writes stop, all replicas eventually converge to the same value. Nothing is promised about what reads return in the meantime. DNS and many caches behave this way, as do leaderless stores at low quorum settings.

A practical way to choose is to start from the anomaly you cannot tolerate. If two users must never both book the last seat, you need linearizable operations on that seat, typically a conditional write or a transaction against a single leader. If users must see their own edits, read-your-writes is enough. If a social feed must never show a reply before its parent, you need causal ordering. Everything else, such as view counts, recommendations and analytics, can usually live with eventual consistency. Many systems mix levels per operation, paying for strength only where the business rule demands it.

4. Quorums and Tunable Consistency

Quorums: Overlap Gives Fresh Reads Replica 1 has x=2 Replica 2 has x=2 Replica 3 still x=1 Write W=2 acks from 1 and 2 Read R=2 asks 2 and 3 R + W > N guarantees at least one replica in the read has the latest write (replica 2).

Leaderless systems inspired by Amazon's Dynamo (Cassandra, Riak, DynamoDB's internals) let you tune consistency per request. With N replicas, a write waits for W acknowledgements and a read queries R replicas. If R + W > N, every read set overlaps every write set, so at least one replica returns the latest value. In Cassandra, QUORUM reads and writes with N = 3 give R = W = 2. Note that quorum overlap alone is not full linearizability: concurrent writes, sloppy quorums and clock-based conflict resolution can still produce anomalies.

5. Worked Example: Read-Your-Writes with Version Tokens

# Toy simulation: leader + lagging follower, and two ways to get read-your-writes.
import random

class Store:
    def __init__(self, lag_prob=0.5):
        self.leader, self.follower, self.lag_prob = {}, {}, lag_prob
        self.version = 0
    def write(self, k, v):
        self.version += 1
        self.leader[k] = (v, self.version)
        if random.random() > self.lag_prob:           # replication sometimes delayed
            self.follower[k] = (v, self.version)
        return self.version                            # client keeps this token
    def read_follower(self, k):
        return self.follower.get(k, (None, 0))
    def read_session(self, k, min_version):
        v, ver = self.follower.get(k, (None, 0))
        if ver >= min_version:                         # follower is fresh enough
            return v, ver
        return self.leader[k]                          # otherwise fall back to leader

random.seed(1)
s, stale_plain, stale_session = Store(), 0, 0
for i in range(1000):
    token = s.write("x", i)
    stale_plain   += s.read_follower("x")[0] != i
    stale_session += s.read_session("x", token)[0] != i
print("stale reads, plain follower:", stale_plain)    # roughly half
print("stale reads, session token :", stale_session)  # 0

The simulation writes 1,000 values and reads each one back immediately. Reading blindly from the follower returns a stale value whenever replication lagged, about half the time with this setting. The session version keeps a token (the version number returned by the write) and only accepts a follower's answer if it is at least that new, otherwise falling back to the leader. Real systems use the same trick: MongoDB causal sessions use cluster time, and some databases offer read-after-write via log sequence numbers or by routing a user's reads to the leader for a short time after they write.

6. Comparison Table

ModelGuaranteeAvailable under partition?Typical costExamples
LinearizableSingle-copy, real-time orderNoConsensus round tripsetcd, ZooKeeper (writes), Spanner
SequentialSame total order for allNoCoordination on orderingTheoretical baseline; some memory models
CausalCause before effectYesDependency trackingMongoDB causal sessions, COPS research system
Read-your-writesSee your own writesYes (with stickiness)Session tokens or routingCommon option in managed databases
EventualConverges eventuallyYesLowestDNS, caches, Cassandra at ONE

7. Pitfalls

9. Further Reading