Skip to content

    System Design

    Consistency Models: Linearizable to Eventual

    Learn what linearizable, sequential, causal and eventual consistency each promise, where session guarantees fit between them, and how to pick one per feature.

    By Published 20 min read

    Share

    Flagship lessonadvancedBackend System Design

    Before this: CAP Theorem and PACELC

    What you'll learn: By the end of this lesson you will be able to define a consistency model as the set of histories a system may produce, state the exact promise of linearizable, sequential, causal and eventual consistency using the original papers' wording, name the four session guarantees and the anomaly each removes, read Jepsen's availability classes, check a small history with a short Java program, and assign a model to each feature of a product.

    A company keeps its price list on one whiteboard in the middle of the office. Anyone who walks past reads the current prices, and when someone rubs out a price and writes a new one, every later reader sees the new one. Now the company opens two more offices and pins a photocopy of the whiteboard in each. Updating the original no longer updates the copies at once. A clerk in the second office may read yesterday's price, and a customer who is told two different prices by two clerks will not be amused.

    The whiteboard is one promise, and every photocopy scheme is a weaker promise with its own list of surprises. Which surprises you can live with, for which prices, is what a consistency model describes. The overview, CAP, PACELC and consistency models, gives a paragraph per model and CAP Theorem and PACELC explains why a partition forces a choice. This lesson is the dedicated treatment of the models: what each forbids, where the definitions come from, what it costs, and how to choose.

    Read from the primary sources and checked on 2026-11-08: Herlihy and Wing (1990), Lamport (1979), Terry et al. (1994), Vogels (2008), Kleppmann (2015) and the Jepsen consistency pages. No product versions are involved.

    The core idea

    A consistency model is a contract about histories. A history is the list of operations clients performed, with when each one started, when it finished and what it returned. The model says which histories are allowed. A stronger model allows fewer histories, so it has fewer surprises and costs more coordination. Jepsen puts it this way: a consistency model is a safety property that declares what a system can do.

    Three kinds of example run through the lesson, each labelled:

    • Minimal example: a 100-line Java program that checks small histories against four models and prints which ones they satisfy.
    • Realistic example: ShopStream's order confirmation page, read from a replica that is a moment behind the primary.
    • Failure example: a shopper who refreshes the order page and watches the status go backwards.

    Histories: the language of the models

    An operation has an invocation (the client sends it) and a response (the client hears back). Two operations are concurrent when their intervals overlap. If operation A responds before operation B is invoked, A precedes B in real time.

    A single-copy register with no concurrency is easy: a read returns the latest write. Concurrency and replication are what make it hard, because there is more than one defensible answer to "what should this read return?". Each consistency model picks which answers are acceptable. The diagram shows the situation that separates the strong models from the rest.

    sequenceDiagram accTitle: A stale read after a completed write accDescr: Client A writes x equals 1 to the primary, which acknowledges. After that acknowledgement, client B reads x from a replica that has not yet received the write and gets 0. The write finished before the read began, so the history is not linearizable. participant A as Client A participant P as Primary participant R as Lagging replica participant B as Client B A->>P: write x = 1 P-->>A: ok Note over A,B: the write has finished, the read starts later B->>R: read x R-->>B: 0 P-)R: replicate x = 1 (arrives later)

    Figure 1. The stale read that separates strong from weak models. How to read this: time runs downward. The write completes before the read starts, yet the read returns the old value because the replication message is still in flight. Linearizability forbids this history. Sequential, causal and eventual consistency all allow it.

    Linearizability: the single-copy illusion

    Maurice Herlihy and Jeannette Wing defined it in 1990. Their abstract says linearizability "provides the illusion that each operation applied by concurrent processes takes effect instantaneously at some point between its invocation and its response". A history is linearizable when some legal sequential order of its operations also respects real-time precedence: if A finished before B began, A comes first. Jepsen states the same rule.

    The consequences are concrete:

    • A read that starts after a write has completed must return that write or a later one. Figure 1 breaks this.
    • Concurrent operations may be ordered either way, so a read that overlaps a write may return the old or the new value.
    • Once any reader has seen the new value, every read that begins afterwards must see it too. There is no going back.

    Two properties from the paper matter in design. Locality: a system is linearizable if each individual object is, so objects can be built and checked independently; sequential consistency and serializability are not local. Nonblocking: the model itself never forces a process with a pending call to a total operation to wait; blocking comes from implementations. Jepsen adds the scope warning: linearizability is a single-object model, and "an object" may be a key, a table or a database. For guarantees across objects it points to strict serializability, which Herlihy and Wing describe as the transaction version, with linearizability as the case where each transaction is one operation on one object.

    The price is availability. Jepsen classes linearizability as neither totally available nor sticky available: during a partition, some or all nodes cannot make progress. That is the CAP result seen from the model's side (CAP lesson). Systems that offer it route operations on a key through a single leader or a consensus group (consensus and leader election). The common failure is assuming a database is linearizable when it is not. Kleppmann's examples: reads from asynchronous followers in single-leader replication are not linearizable, ZooKeeper reads are not linearizable by default, and a Dynamo-style store should not be assumed linearizable just because it uses quorum reads and writes (R + W > N).

    Use it where a stale answer costs money or breaks a uniqueness rule: balances, stock that must not oversell, unique usernames, leader election.

    Sequential consistency: one order, no clock

    Leslie Lamport defined it in a two-page note in 1979, for multiprocessor memory. A multiprocessor is sequentially consistent when "the result of any execution is the same as if the operations of all the processors were executed in some sequential order, and the operations of each individual processor appear in this sequence in the order specified by its program."

    Both models want one total order that every process agrees on. Sequential consistency only requires that order to respect each process's own program order, not real time. So Figure 1 is allowed: "B reads 0, then A writes 1" is a legal total order that keeps each client's own order. Herlihy and Wing make the same point: it is weaker because it does not require the original history's precedence order to be preserved.

    Lamport's note also explains why the order matters. In a two-process mutual-exclusion protocol, each process sets its own flag and then reads the other's. If a processor may read before it writes, both can enter the critical section. That gives his requirement R1: each processor issues memory requests in the order specified by its program. He then shows R1 alone is not enough, because with several memory modules a read to one can overtake a write to another, so he adds R2: requests to one memory module are served from a single FIFO queue. Together they make the whole machine sequentially consistent if each processor is sequential.

    Two limits. It is not local: Herlihy and Wing give a two-queue history where each queue's sub-history is sequentially consistent but the combined history is not. And Jepsen classes it, like linearizability, as neither totally available nor sticky available. Jepsen's intuition: a process may read stale state, but once it has observed some operation it cannot observe anything earlier. In product design the difference from linearizability rarely decides anything, but vendor pages often confuse the two.

    Causal consistency: causes before effects

    Causal consistency drops the demand for one global order and keeps only the part that users can notice. If one operation could have influenced another, every process must see them in that order. Jepsen: causally related operations should appear in the same order on all processes; processes may disagree on the order of causally independent operations. The "could have influenced" relation is Lamport's happens-before: operations by the same process are ordered, and an operation that reads a value is after the write it read (the IDs, clocks and ordering lesson covers the clocks that track it).

    sequenceDiagram accTitle: A reply visible before the post it answers accDescr: Alice posts a question that reaches replica 1. Bob reads it there and posts a reply that reaches replica 2 first. Carol reads replica 2 and sees the reply with no question. Causal consistency forbids this because the reply was caused by the question. participant A as Alice participant R1 as Replica 1 participant B as Bob participant R2 as Replica 2 participant C as Carol A->>R1: post "Is the milk fresh?" B->>R1: read thread R1-->>B: "Is the milk fresh?" B->>R2: post reply "Yes, dated Friday" Note over R1,R2: the question has not reached replica 2 yet C->>R2: read thread R2-->>C: reply only, no question

    Figure 2. The anomaly causal consistency exists to prevent. How to read this: Bob's reply was caused by what he read, so it must not become visible anywhere before the question. A causal system holds the reply back until its cause has arrived, or delivers both together. Two unrelated posts may still appear in either order to different readers.

    This is the first model on the way down that survives a partition, with a condition. Jepsen classes causal consistency as sticky available: clients connected to a non-faulty node can keep making progress, as long as each client keeps talking to the same server. The cost is bookkeeping. The system tracks dependencies (a version vector or a dependency list per operation) and delays making an operation visible until what it depends on is visible.

    Session guarantees: the practical middle

    Terry, Demers, Petersen, Spreitzer, Theimer and Welch worked on a replicated store for mobile users at Xerox PARC (the Bayou project). A mobile client could write to one server and read from another that had not yet heard of the write. Their 1994 paper proposes four guarantees, each scoped to one client's session, designed to sit on top of a weakly consistent system. Its definition of the first: if a read follows a write in a session and is performed at server S at time t, the write is included in S's database at t.

    Guarantee Plain meaning Anomaly it removes The paper's example
    Read your writes A read reflects the session's earlier writes You save a change and then do not see it A user changes a password and is rejected at login because the new one has not reached the server contacted
    Monotonic reads A read never sees less than an earlier read in the session Data appears, then vanishes Today's calendar appointments appear and disappear as the display reads from different servers
    Writes follow reads A write is ordered after the writes whose effects the session has read A reply lands before the post it answers Replies on a bulletin board appear only after the original post
    Monotonic writes A session's writes are applied in the order issued Version 2 is overwritten by version 1 Saving version N+1 must replace version N everywhere, not be replaced by it

    The mechanism is simple. Each write gets a unique identifier, and the client keeps a record of the writes it has read and written (compacted to version vectors). Before using a server, the client checks that the server's version vector covers what the session needs; if not, it picks another server or waits. The paper is open about the price: a guarantee can reduce availability, because the client may have to find a server that is current enough.

    Jepsen's availability classes for these differ. Read your writes is sticky available. Monotonic reads, monotonic writes and writes follow reads are classed as totally available. PRAM, which Jepsen states is exactly equivalent to read your writes, monotonic writes and monotonic reads together, is sticky available. Jepsen describes causal consistency as stricter than PRAM, also enforcing writes follow reads, so it sits above all four in strength.

    sequenceDiagram accTitle: Read your writes with a version token accDescr: A client writes to the primary and receives version 7 as a token. On a later read, a replica at version 5 is too far behind, so the router sends the read to the primary or to a replica at version 7 or later. The client sees its own write. participant C as Client participant G as Router participant P as Primary participant R as Replica C->>G: write G->>P: write P-->>G: ok, version 7 G-->>C: ok, token v7 C->>G: read with token v7 Note over G,R: replica is at version 5, behind the token G->>P: read (fall back to the primary) P-->>G: includes version 7 G-->>C: the client's own write

    Figure 3. Read your writes without making every read strong. How to read this: only this client's reads are constrained by its token. Other clients keep reading from replicas, so the extra load falls on the primary only for readers who have just written.

    Eventual consistency: the weakest useful promise

    Werner Vogels' definition: if no new updates are made, eventually all accesses return the last updated value. The period before that is the inconsistency window, which depends on communication delays, load and the number of replicas. Terry et al. say the goal relies on two properties: total propagation (every write reaches every replica) and consistent ordering (replicas apply writes in an order that leads to the same state).

    Two things are easy to miss. First, it is a statement about where the system ends up, not about the path. Nothing in it forbids a stale read, a read that goes backwards, or a reply before its post. That is why it is combined with the session guarantees in practice. Second, "converge" needs a rule when two replicas accepted conflicting writes: last-write-wins, merging, or keeping both versions. The CAP lesson shows a cart union merge and its cost. Vogels' everyday example is DNS, the most popular system he names that implements eventual consistency.

    The ladder, and what each rung costs

    flowchart TD accTitle: The ladder of consistency models accDescr: From strongest to weakest. Linearizable implies sequential, which implies causal, which implies PRAM and writes follow reads. PRAM is read your writes plus monotonic reads plus monotonic writes. Linearizable and sequential cannot be totally or sticky available. Causal, PRAM and read your writes are sticky available. Monotonic reads, monotonic writes and writes follow reads are totally available. Eventual consistency is a separate convergence promise. subgraph NOAV["Not available during a partition (Jepsen)"] LIN["Linearizable"] --> SEQ["Sequential"] end subgraph STICKY["Sticky available"] CAU["Causal"] PRAM["PRAM"] RYW["Read your writes"] end subgraph TOTAL["Totally available"] MR["Monotonic reads"] WFR["Writes follow reads"] MW["Monotonic writes"] end SEQ --> CAU CAU --> PRAM CAU --> WFR PRAM --> RYW PRAM --> MR PRAM --> MW EVT["Eventual consistency: replicas converge when writes stop"]

    Figure 4. The models as a ladder. How to read this: an arrow means "implies", so everything below a model is also guaranteed by it. The boxes are Jepsen's availability classes: stronger models cost availability during a partition. Eventual consistency stands apart because it constrains the end state, not what a read may return on the way.

    Model Forbids Availability in a partition (Jepsen) Typical cost Fits
    Linearizable Any read that ignores a completed write Not available on the minority side Coordination on the path; latency; refusals Balances, stock, unique names, locks
    Sequential Disagreement on the order, or reordering one client's operations Not available on the minority side Still needs a global order Rare as a product choice
    Causal An effect visible before its cause Sticky available Dependency tracking; clients stay on one server Comments, chat threads, collaborative edits
    Session guarantees The anomalies one client notices Sticky or totally available, by guarantee A token or sticky routing; a fallback path Your own profile, order, cart
    Eventual Nothing about reads, only that replicas converge Available A conflict rule; users may see stale or odd data Counters, caches, catalogues, feeds

    The ladder is a partial order, not a line, and many databases let you choose per request, so ask what a particular operation guarantees, not which label a product wears. Kleppmann's complaint about "CP" and "AP" labels is the same point: one system can have operations with different consistency characteristics, and many systems fit neither bucket.

    Minimal example: checking histories by brute force

    The way to make these definitions concrete is to check histories mechanically. The program below takes small histories of reads and writes on registers and tries to find a total order that explains them. With the real-time rule on it checks linearizability. With it off, only each process's own order must hold, which is sequential consistency. It also checks two session guarantees directly, treating values as versions (a bigger number is newer). It is a teaching tool: the search is exponential in the worst case, and it is not how a production checker such as Jepsen's works.

    import java.util.*;
    
    public class HistoryCheck {
        // One finished operation: who, read or write, which register, value, start and end time.
        record Op(String proc, boolean write, String key, int value, int start, int end) {
            static Op w(String p, String k, int v, int s, int e) { return new Op(p, true, k, v, s, e); }
            static Op r(String p, String k, int v, int s, int e) { return new Op(p, false, k, v, s, e); }
        }
    
        // Is there a total order of all operations that respects the register semantics,
        // each process's own order, and (if realTime) "A ended before B began => A before B"?
        static boolean exists(List<Op> ops, boolean realTime) {
            return place(ops, new boolean[ops.size()], new HashMap<>(), realTime, 0);
        }
    
        static boolean place(List<Op> ops, boolean[] done, Map<String, Integer> state, boolean rt, int placed) {
            if (placed == ops.size()) return true;
            for (int i = 0; i < ops.size(); i++) {
                if (done[i] || !mustComeFirstAreDone(ops, done, i, rt)) continue;
                Op o = ops.get(i);
                Integer old = state.get(o.key());
                if (!o.write() && o.value() != (old == null ? 0 : old)) continue; // a read must return the current value
                if (o.write()) state.put(o.key(), o.value());
                done[i] = true;
                if (place(ops, done, state, rt, placed + 1)) return true;
                done[i] = false;
                if (o.write()) { if (old == null) state.remove(o.key()); else state.put(o.key(), old); }
            }
            return false;
        }
    
        static boolean mustComeFirstAreDone(List<Op> ops, boolean[] done, int i, boolean rt) {
            Op o = ops.get(i);
            for (int j = 0; j < ops.size(); j++) {
                if (j == i || done[j]) continue;
                Op p = ops.get(j);
                boolean before = (p.proc().equals(o.proc()) && p.start() < o.start()) || (rt && p.end() < o.start());
                if (before) return false;
            }
            return true;
        }
    
        // Session checks (values are versions, so a bigger value is newer).
        static boolean readYourWrites(List<Op> ops) {
            Map<String, Integer> written = new HashMap<>();
            for (Op o : sorted(ops)) {
                String k = o.proc() + "/" + o.key();
                if (o.write()) written.merge(k, o.value(), Math::max);
                else if (o.value() < written.getOrDefault(k, 0)) return false;
            }
            return true;
        }
    
        static boolean monotonicReads(List<Op> ops) {
            Map<String, Integer> seen = new HashMap<>();
            for (Op o : sorted(ops)) {
                if (o.write()) continue;
                String k = o.proc() + "/" + o.key();
                if (o.value() < seen.getOrDefault(k, 0)) return false;
                seen.put(k, o.value());
            }
            return true;
        }
    
        static List<Op> sorted(List<Op> ops) {
            return ops.stream().sorted(Comparator.comparingInt(Op::start)).toList();
        }
    
        static void show(String name, List<Op> ops) {
            System.out.printf("%-34s %-13s %-11s %-4s %s%n", name,
                    yes(exists(ops, true)), yes(exists(ops, false)), yes(readYourWrites(ops)), yes(monotonicReads(ops)));
        }
    
        static String yes(boolean b) { return b ? "yes" : "NO"; }
    
        public static void main(String[] args) {
            System.out.printf("%-34s %-13s %-11s %-4s %s%n", "history", "linearizable", "sequential", "RYW", "MR");
            System.out.println("-".repeat(70));
            show("H1 read overlaps the write, old", List.of(
                    Op.w("A", "x", 1, 0, 30), Op.r("B", "x", 0, 10, 20)));
            show("H2 read after the write, old", List.of(
                    Op.w("A", "x", 1, 0, 10), Op.r("B", "x", 0, 20, 30)));
            show("H3 two readers disagree in order", List.of(
                    Op.w("A", "x", 1, 0, 100), Op.r("B", "x", 1, 10, 20), Op.r("C", "x", 0, 30, 40)));
            show("H4 one reader goes backwards", List.of(
                    Op.w("A", "x", 1, 0, 100), Op.r("B", "x", 1, 10, 20), Op.r("B", "x", 0, 30, 40)));
            show("H5 writer misses its own write", List.of(
                    Op.w("A", "x", 1, 0, 10), Op.r("A", "x", 0, 20, 30)));
            show("H6 Lamport's flags, both read 0", List.of(
                    Op.w("P1", "a", 1, 0, 10), Op.r("P1", "b", 0, 20, 30),
                    Op.w("P2", "b", 1, 5, 15), Op.r("P2", "a", 0, 25, 35)));
        }
    }

    Run it with java HistoryCheck.java (Java 25 or later; the single-file launcher compiles it in memory). Expected output (identical when re-run under JDK 25 on 2026-11-08):

    history                            linearizable  sequential  RYW  MR
    ----------------------------------------------------------------------
    H1 read overlaps the write, old    yes           yes         yes  yes
    H2 read after the write, old       NO            yes         yes  yes
    H3 two readers disagree in order   NO            yes         yes  yes
    H4 one reader goes backwards       NO            NO          yes  NO
    H5 writer misses its own write     NO            NO          NO   yes
    H6 Lamport's flags, both read 0    NO            NO          yes  yes

    How to read the table, row by row:

    Row What happened What the output teaches
    H1 A's write overlaps B's read; B gets the old value Linearizable: the read can be placed before the write. Overlap permits either answer
    H2 B reads 0 after A's write finished (Figure 1) Not linearizable, but sequential: put B's read first. No clock, no violation
    H3 B reads 1, then later C reads 0 Not linearizable (a reader went backwards in real time). Sequential, because B and C are different processes
    H4 The same B reads 1 and then 0 Fails sequential consistency and monotonic reads: one client's own order breaks
    H5 A writes 1, then A reads 0 Fails read your writes, and also sequential and linearizable
    H6 Lamport's two flags, both processes read 0 Fails sequential consistency: each read needs to be before the other process's write, which is a cycle

    The checker cannot show causal consistency, which needs the read-from relation; the point of the table is how the models separate. Break it: change H2's read to return 1 and every column becomes "yes". Then change the true in exists(ops, true) to false in show: the linearizable column now equals the sequential one without touching a single history, which is the whole difference between the first two models. Stretch: add a history with two writers and two readers in which the readers see two independent writes in opposite orders, and find out which of the four columns reject it.

    Misconceptions, corrected

    Claim Correction
    "Strong or eventual: those are the two choices." Sequential, causal and the session guarantees sit between them and fix what users notice at modest cost.
    "Eventual consistency means anything can happen." It promises convergence once writes stop. Add session guarantees to remove the visible anomalies.
    "A replicated database is linearizable by default." Reads from asynchronous replicas are not; many systems are linearizable only for some operations or settings.
    "R + W > N gives linearizability." It gives overlap; see the CAP lesson.
    "This is ACID consistency." These models describe what reads may return across copies; ACID consistency is about database invariants.

    The other side of this topic

    The browser meets these models as optimistic updates: the interface shows an action as done before the server has confirmed it, and must be able to roll back or reconcile if the server answers differently. The Frontend System Design track plans a lesson on optimistic updates and rollback (not published yet, so there is no link). The back-end rule that makes the front end's job possible is to say per operation which guarantee it has, and to return an error or a version the interface can act on.

    In practice: the order confirmation that says "not found"

    This is the realistic example. It is an educational scenario on the course's running example, ShopStream, and nothing in it describes a real company. Every figure is illustrative.

    This is a realistic scenario, not a case history.

    The situation

    ShopStream's Stage 2 added read replicas to take browsing load off the primary database (scale reads). At the course's illustrative peak of about 58 checkouts per second, a shopper pays and the browser is sent to the confirmation page. The confirmation page reads the order from a replica. The replica is a few hundred milliseconds behind, an illustrative figure, so a small share of shoppers see "order not found" for a moment, press the button again and place a duplicate order. The team asks whether to send every read to the primary.

    Starting architecture

    ShopStream read paths: one must be fresh, one may lagShoppers to Order API (requests); Order API to Primary (write, token reads); Order API to Replica A (browse); Primary to Replica A (replication); Primary to Replica BFRESH: AFTER THE SHOPPER'S OWN WRITEMAY LAG: PRODUCT PAGES, HISTORYShoppersShoppersOrder API: routes by guaranteeOrder APIroutes byguaranteePrimary: orders, stockPrimaryorders, stockReplica A: browse readsReplica Abrowse readsReplica B: browse readsReplica Bbrowse readsrequestswrite, token readsbrowsereplication

    Figure 5. The design under discussion. How to read this: everything starts at the order API. The top path goes to the primary and is always fresh. The bottom path goes to replicas that trail the primary by a replication delay, so reads there may be stale.

    Options considered

    Option What the shopper experiences Cost Risk
    A. All reads from the primary Always fresh Primary carries the whole read load again, undoing Stage 2 The primary becomes the bottleneck at peak
    B. Reads from replicas, plus a version token: a shopper's reads after a write must come from a copy at least as new Sees own order at once; others' pages may lag A token in the cookie; a fallback path Fallback load when replicas lag
    C. Read from the primary for a fixed window after a write Fresh for the window A timer to tune Wrong if lag exceeds the window
    D. Pin each shopper to one replica Never sees time go backwards Uneven load; an outage moves the shopper Own write may not have reached that replica

    The decision

    Option B, scoped by feature. The point is to give each read the weakest guarantee that its user cannot notice, not to pick one model for the product:

    Feature Guarantee Mechanism
    Stock reservation, payment Linearizable on one authority Conditional update on the primary; refuse if it cannot be reached
    Order confirmation, order history after a write Read your writes Version token; replica must be at least as new, else the primary
    Order status while tracking Monotonic reads The same token carries the last version seen; never read an older copy
    Shopper and courier messages Causal, per conversation Each message names the one it answers; hold a reply until its parent is visible
    Product page, view counts Eventual Any replica or cache

    The confirmation page stops failing because the token says "version 7 or later", and the read goes to the primary when the replica is at version 5. Everyone else keeps reading replicas.

    Rollout and verification

    A rollout would first log, for a sample of reads, the token held and the version the replica returned, to count how often a replica was behind. Verification is a test that writes an order, reads it through the router with its token while a replica is held back, and asserts the order is returned, plus a test that two consecutive reads with a rising token never return a lower version. Metrics: the fraction of token reads that fell back to the primary, replica lag, and duplicate-order attempts.

    The 2 a.m. failure

    The page reads: primary CPU high, fallback rate near 100 percent. A nightly batch job loaded a replica, replication fell behind, and every token read went to the primary: Option A arriving by the back door. The checks: lag per replica, the fallback ratio, the primary's connection count. The mitigation is to let a read wait a bounded time for a replica before falling back, to cap the fallback rate and show an honest "your order is being confirmed" page, and to move the batch job to its own replica. The lasting fix is to alert on replica lag and size the primary for the fallback rate you accept.

    Failure example: the status that goes backwards

    The failure example is the monotonic reads anomaly. A shopper tracks an order. The load balancer sends each refresh to a different replica, and the replicas are at different points.

    sequenceDiagram accTitle: Order status going backwards accDescr: A shopper refreshes the tracking page twice. The first refresh reaches replica A, which has the status out for delivery. The second reaches replica B, which is behind and shows packed. The shopper sees time go backwards. participant S as Shopper participant LB as Load balancer participant A as Replica A, up to date participant B as Replica B, behind S->>LB: refresh LB->>A: read order status A-->>S: out for delivery S->>LB: refresh again LB->>B: read order status B-->>S: packed Note over S: the status went backwards, a monotonic reads violation

    Figure 6. A client that sees time go backwards. How to read this: each read is individually fine, because both values were true at some moment. The violation is in the sequence one client sees. This is row H4 of the checker: no real-time reasoning is needed to call it wrong.

    What breaks: support tickets ("the app says my order is packed again") and clients that act on the first value. Detection: log the status version each session sees and alert on a decrease. Prevention: carry the last version seen in the session and have the router refuse an older replica, or pin the session to one replica. Both are the session guarantees from Terry et al.

    Architect's notes

    • Name the guarantee per operation. A table with one row per read and write beats "we use strong consistency".
    • Ask what a stale answer costs. Money and uniqueness get the strongest model, noticeable oddities a session guarantee, the rest eventual.
    • Scope it to one object. If two keys must change together, you need a transaction or a single owner.
    • Read the docs for the operation, not the brand. A store is rarely "strong" or "eventual" in general.
    • Treat the session token as a first-class thing. It gives one client linearizable-looking behaviour on weak copies.

    Common mistakes

    • Confusing sequential with linearizable. The first has no real-time rule.
    • Calling a replicated database "strongly consistent" without checking where reads go.
    • Using eventual consistency for data a user just wrote and will read straight back.
    • Ordering causal events by wall-clock time. Clocks drift; use version or dependency information.
    • Promising monotonic reads while round-robining across replicas.

    Key takeaways

    • A consistency model is the set of histories a system may produce; stronger models allow fewer.
    • Linearizable: each operation appears instantaneous between invocation and response, respecting real time; per object; costs availability in a partition.
    • Sequential: one total order plus each process's program order, ignoring real time.
    • Causal: causes before effects; sticky available.
    • The four session guarantees remove the anomalies one client notices, typically with a token.
    • Eventual promises only convergence once writes stop; pair it with session guarantees and a conflict rule.
    • Choose per feature: strongest for money and uniqueness, session guarantees for what users notice, eventual elsewhere.

    Practice exercises

    Exercise 1 (30 min). Without running the program, predict the four columns for a history where A writes x=1 and then x=2, and B reads x=2 and then x=1. Add it and compare.

    Exercise 2 (45 min). For ten features of a food-delivery app, write the cost of a stale read and assign a model; mark each one that needs a token.

    Exercise 3 (1 hour). Extend HistoryCheck with a writes-follow-reads check and write one passing and one failing history.

    Exercise 4 (1 hour). Design the version token for the order confirmation page: its contents, where it lives, how a replica proves it is current enough, and what happens when it is lost.

    Exercise 5 (2 hours). For a Dynamo-style store with N = 3, W = 2, R = 2 (see the CAP lesson), construct a history that is stale despite R + W > N and say which model it violates.

    Official documentation

    Ask about this article

    Get answers grounded in this post. AI-generated — based on this article, and may be imperfect.

    Scaled AI Weekly

    Enjoyed this? Get more like it every Monday.

    Real architecture decisions, LLMOps patterns that survive production, and engineering leadership advice — from 12+ years of building at enterprise scale. Free. No spam. Unsubscribe anytime.

    Get new articles by email

    Free. We email a confirmation link first, and you can unsubscribe from any email.

    Join engineers building production AI systems

    Comments