Distributed Systems

Replication and Consistency

From leaders and quorums to the CAP boundary
≈ 37 min read · 8059 words

In the Agreement chapter, replication served a single purpose: fault tolerance. A protocol such as Raft keeps backup copies so that the system survives a crash, and under normal (failure-free) operation the extra replicas are pure overhead, every request still funnels through one leader, which must reach a majority before it can commit. Raft therefore makes a system safer, never faster.

This chapter takes replication seriously as a performance and availability tool, and confronts the price that comes with it. Once clients can read and write more than one copy, the copies can disagree, and the system must define what a read is allowed to return. That definition is a consistency model: a contract between the application and the data store. We will walk the full spectrum of these contracts, from the strongest (linearizability) down to the weakest (eventual consistency), see the protocols that implement each one, and locate the line, the high-availability boundary, below which a replica can keep answering even while cut off from the rest of the world. The chapter closes at the CAP trade-off and the modern distributed databases engineered around it.

1. Why Replicate?#

Fault tolerance is only one of three reasons to keep multiple copies of data.

1.1 The three motivations#

Raft buys fault tolerance only

Under Raft, all requests go through the single leader, so there is no throughput gain; clients cannot read from any replica but the leader, so there is no latency gain; and because the leader must reach consensus with a majority before committing, Raft actually increases latency under normal operation. To gain performance we must relax consistency, which is what the rest of this chapter is about.

1.2 The latency problem#

Latency has become the dominant bottleneck of modern distributed systems. Over four decades CPU speed, memory, disk and bandwidth improved by three to five orders of magnitude; latency barely moved, because it is ultimately bounded by the speed of light.

Metric 1983 2024 Improvement
CPU speed 1 × 10 MHz 8 × 3.2 GHz > 2000×
Memory size ≤ 2 MB 32 GB > 16000×
Disk capacity ≤ 30 MB 4 TB > 100000×
Network bandwidth 3 Mbps > 10 Gbps > 3000×
Latency (RTT) 2.54 ms 0.1 ms < 30×

Inspired by Rumble et al., “It’s time for low latency”, HotOS ’11. As every other metric races ahead, latency becomes the limiting factor, and placing data geographically close to users is the main way to fight it.

The physical floor is visible in inter-region round-trip times. On AWS EC2, mean RTTs range from about 22 ms (California ↔︎ Oregon) to about 363 ms (São Paulo ↔︎ Singapore). A multi-round protocol run across distant regions can take on the order of one second, plainly perceptible to a human. Concretely: Netflix stores copies of each title in data centres close to viewers (a CDN), and cloud storage such as OneDrive replicates documents across regions both for safety and for local, low-latency access.

2. The Core Problem: Consistency#

As soon as clients can read and write several replicas, conflicts arise. The archetype is a Git merge conflict: two collaborators edit a file locally and, on push, the copies have diverged. By contrast, a CDN barely has conflicts, because only Netflix writes new content and everyone else reads; a viewer receiving an episode a few seconds late is not a meaningful conflict.

The interesting case is when writes originate from multiple sources. Collaboration platforms (Google Docs, Microsoft 365, Dropbox) replicate documents onto users’ devices, support disconnected editing, and reconcile on reconnection under the assumption that simultaneous edits are infrequent. The system must then answer three questions:

If we insist on the strongest guarantee, the system behaves exactly as a single machine, i.e. linearizability, there is no escape from a single leader (or something equally expensive). This is the fundamental trade-off, captured by the CAP intuition: under network partitions or delays, one must choose between strong consistency and high availability. Fortunately consistency is not binary; a rich spectrum lies between the extremes.

3. Consistency Models#

A consistency model is a contract between the clients (processes) and the data store: it fixes what guarantees the store gives about the ordering and visibility of operations. The trade-off is one-dimensional and unforgiving:

Stronger guarantees → lower performance, but the developer reasons against a simpler, more intuitive system. Weaker guarantees → higher performance and availability, but more complexity is pushed onto the application.

Models can constrain different dimensions:

The distributed data store

A (distributed) data store may be shared memory, a filesystem, or a database. It holds multiple items (variables, files). Ideally a read returns the result of the last write, but “last” has no meaning in a distributed system without a global clock. Each process interacts with its own local copy; the underlying protocol keeps the copies in sync.

ProcessProcessProcessdistributed data store: replicas kept in sync by the protocolLocal copyLocal copyLocal copy

4. Consistency Protocols#

Before the models themselves, it helps to see the three implementation families, because a model’s availability is tied to which family can implement it.

Single-leaderMulti-leaderLeaderlessLFFCW/RLLLCCRRRCquorum

4.1 Single-leader#

One replica is the leader; the rest are followers. Every write goes to the leader, which stores it locally and then forwards it to the followers in the order it chose. A read may go to the leader (followers as pure backup, as in Raft) or, in the systems we focus on, to any replica.

Propagation to followers has three variants:

Mode A write completes when…
Synchronous all followers have acknowledged
Asynchronous the value is stored on the leader (followers catch up in the background)
Semi-synchronous at least k replicas have acknowledged (k is a tunable, e.g. in Cassandra)

Synchronous (or semi-synchronous) replication is safer: even if k−1 replicas fail, a copy survives, and lagging followers recover by catch-up. If the leader fails, a new one is elected (failover), which is delicate (followers may be stale, the network may be partitioned) and needs a consensus protocol such as Raft. Single-leader replication underlies PostgreSQL, MySQL, Oracle, SQL Server and MongoDB, typically inside one data centre where low latency makes synchronous replication affordable; the write load can be spread by partitioning and giving each partition its own leader.

4.2 Multi-leader#

Several replicas accept writes concurrently and cross-replicate. With no single orderer, write-write conflicts become possible, and how to resolve them depends on the consistency model. Multi-leader is natural in geo-replicated settings where reaching a distant leader would be prohibitively slow, and it works well when conflicts are rare and cheap to reconcile, social networks, where writes are infrequent, one user’s writes tend to hit the same replica, and reordering two comments is harmless. Some databases run single-leader within a data centre and multi-leader across data centres; external tools (Tungsten for MySQL, GoldenGate for Oracle) also provide it.

4.3 Leaderless#

The client contacts several replicas directly for both reads and writes (sometimes via a coordinator acting on its behalf). Conflicts are avoided with quorum-based voting: a write must be accepted by a majority, and a read must contact enough replicas to be sure of seeing the latest version. Amazon Dynamo, Riak and Cassandra are leaderless.

5. High Availability and the CAP Theorem#

This distinction runs through the whole chapter.

High availability

A consistency model is highly available if it can be implemented without blocking / synchronous communication: when a node or link fails, a client can still get an answer from a correct replica. It is not highly available if every correct implementation must communicate synchronously with other nodes, as with single-leader replication, where a client whose local replica is not the leader blocks whenever the leader is unreachable.

High availability is the practical face of the CAP theorem, over Consistency, Availability and network Partition:

In the presence of network failures (P), a system can offer availability (A) or consistency (C), but not both.

CAPConsistencyAvailabilityPartitionP is unavoidable:choose C or A

In practice partitions are always possible and outside the engineer’s control, so the real choice is perpetual: strong consistency or high availability. The rest of the chapter walks from strong-but-unavailable models down to weak-but-available ones, crossing the availability boundary along the way and ending at models served entirely from a local replica. Crucially, some weak models turn out to be achievable with high availability, that is the payoff for giving up global order.

6. Data-Centric Consistency Models#

Data-centric models phrase the contract in terms of the global order of operations across all replicas: which interleavings of reads and writes are permitted. Ideally every operation would take effect instantaneously and be globally ordered by time, impossible without a single clock, and even ignoring time it takes expensive coordination to agree on order.

Reading the space-time diagrams

Each row is a process (P1, P2, …) attached to its own replica; the x-axis is that process’s own time. W(x)a means write value a to x; R(x)b means read value b from x. Because FIFO, causal and sequential consistency do not predicate on time, the horizontal alignment across rows carries no meaning, only the order within each row, and which values reads return, matter.

A useful mental model, stressed in the exercise sessions: distinguish the middleware from the application. The middleware receives updates from other replicas in whatever order the model guarantees (FIFO or causal); the application then freely chooses which variable to read, and in which order. A read returns the value the middleware currently holds for that variable.

6.1 Sequential consistency#

Sequentially consistentP1W(x)aP2W(x)bP3R(x)bR(x)aP4R(x)bR(x)aone order b, a satisfies everyoneNot sequentially consistentP1W(x)aP2W(x)bP3R(x)bR(x)aP4R(x)aR(x)bP3 sees b,a; P4 sees a,b: no single order
Sequential consistency (Lamport, 1979)

The result is the same as if all operations were executed in some sequential order, and the operations of each process appear in that sequence in the order the process issued them.

Two requirements: (1) global agreement, all processes agree on one single history; (2) program order preserved, if a process issues A before B, then A precedes B in the agreed sequence. Operations within a process may not be reordered, all processes see the same interleaving, and, decisively, time does not matter, the agreed sequence need not reflect wall-clock time. Sequential consistency requires global agreement on order, so it is not highly available (it needs blocking coordination). It was born as a memory model for multiprocessors and underpins the Java and C++ memory models.

Sequential consistency is expensive even on one machine#

Consider two Java threads sharing x = 0, y = 0:

int x = 0, y = 0;
// Thread 1          // Thread 2
x = 1;              if (y == 1) {
y = 1;                  // Is x guaranteed to be 1 here?
                    }

Under sequential consistency, y == 1 would force x == 1, because Thread 1 wrote x before y. Java does not guarantee this: reading x == 0 after y == 1 is legal. The reason is that each core has its own cache; shared memory is itself a replicated store seen through those caches, and enforcing sequential consistency would demand a cache-coherency protocol on every access, blocking compiler reordering and adding heavy overhead. Java and C++ therefore make it opt-in: a synchronized block flushes and synchronises caches at its boundaries, and a volatile variable is always read/written past the cache. You pay for consistency only where you need it.

Sequential consistency is not determinism

Sequential consistency guarantees that all processes agree on some order; it does not fix which. Three processes, all variables 0, each writing one variable then printing two others:

P1 P2 P3
x = 1; y = 1; z = 1;
print(y, z); print(x, z); print(x, y);

Depending on the interleaving, the six-digit output may be 001011, 101011, 010111, 111111, and more, every one of them sequentially consistent (a single agreed order, program order preserved), yet all different. The model constrains which interleavings are legal, never which one occurs.

Implementing it: single-leader#

Because sequential consistency needs a single agreed order, it cannot be highly available: two partitioned replicas both accepting writes would produce diverging sequences. The standard implementation is single-leader:

Client AW(x)1Leaderx=1Follower 1R(x)1Follower 2R(x)0fastsloweveryone agrees on the order R(x)0, W(x)1, R(x)1: sequentially consistent

In the worked run above (x starts at 0, A writes 1), C conceptually read before the write took effect and B after; all processes agree on the order R(x)0 → W(x)1 → R(x)1. What matters is the logical sequence everyone observes, not real time. The protocol assumes no failures (failover needs Raft), FIFO channels, sticky processes, and that only the latest state matters (followers apply updates on receipt, no log needed in this simplified model).

Implementing it: leaderless quorum#

Sequential consistency can also be reached without a leader, by having the client (or a proxy) contact several replicas directly. With N replicas, let N_W be the number that must acknowledge a write and N_R the number that must answer a read. Sequential consistency holds if:

NR+NW>N(read-write overlap)NW>N/2(write-write exclusion)N_R + N_W > N \quad\text{(read-write overlap)} \qquad N_W > N/2 \quad\text{(write-write exclusion)}

BalancedNW=10, NR=3validRead-optimisedNW=12, NR=1validInvalidNW=6, NR=7invalidwrite setread setoverlap

With N = 12, N_W = 10, N_R = 3 is valid (10 > 6, 13 > 12); N_W = 12, N_R = 1 is the read-optimised ROWA extreme; but N_W = 6, N_R = 7 is invalid because NW=N/2N_W = N/2 lets two disjoint groups of six accept different writes. Operators tune the split by workload: small NWN_W / large NRN_R favours writes; large NWN_W / small NRN_R favours reads. Because replicas can briefly hold stale values, two background mechanisms repair them, read repair (a read that spots a lagging replica pushes it the fresh value) and anti-entropy (a background process that periodically compares replicas and fills gaps even with no client activity). Like single-leader, the quorum protocol needs blocking synchronisation (a write cannot complete without reaching a quorum), so sequential consistency is not highly available under either implementation.

6.2 Linearizability#

Linearizability is stronger than sequential consistency: it adds a real-time constraint.

Linearizability

A store is linearizable if every operation appears to take effect instantaneously at a single point in real time between its invocation and its completion. If operation A completes (in real time) before B starts, then A precedes B in the global order.

Also called strong, external or atomic consistency, it is the strongest guarantee under replication: fundamentally a recency guarantee, once a client’s write completes, all clients see its effect, giving the illusion of a single copy despite physical replication. It needs no global clock: each operation has a start (issue) and end (response), and the protocol may take as long as it likes within that interval to lock replicas and propagate; once the interval closes, the new state must be visible to everyone.

AW(x)aLeaderF1R(x)aF2R(x)0same real-time instantF1 already holds a while F2 still returns 0: not linearizable

Reusing the single-leader run (x starts at null, A writes a): the order R(x)null → W(x)a → R(x)a is a valid sequential history. It is not linearizable if, at the same real-time instant, B reads null while C reads a, two clients simultaneously disagreeing on the current value, which linearizability forbids.

To make the single-leader protocol linearizable, add two-phase locking:

  1. Lock and propagate. The leader locks all replicas and sends the value; replicas hold it but do not yet serve it. Reads arriving now are deferred.
  2. Unlock. Once all replicas have acknowledged synchronously, the leader unlocks and they begin serving the new value.

This closes the window in which some replicas have the value and others do not, so the write looks instantaneous, at the cost of two communication rounds instead of one and reads blocked during a write.

Composability and strict serializability

Linearizability is composable (local): if every individual variable is linearizable in isolation, the whole schedule over all variables is automatically linearizable. So one can reason about, and implement, linearizability one item at a time. Sequential consistency is not composable, two individually sequentially-consistent objects need not compose into a sequentially-consistent whole. Extended from single operations to multi-operation transactions, linearizability is usually called strict serializability (serializability + real-time order). Raft provides linearizable behaviour: all operations pass through the leader, which serialises them via the consensus log; the “interval” is the round-trip, after which the committed state is visible to all.

Linearizability is strictly stronger than sequential consistency, which is already unavailable, so it is not highly available either.

6.3 Causal consistency#

Causal consistency is the first model below the availability line: it is the strongest model still implementable in a highly available way.

The intuition is a group chat. Each phone is a local replica; messages are writes, reading the chat is a read.

Causal consistency

Writes that are potentially causally related must be seen by all processes in the same order. Concurrent writes may be seen in any order. A write W2 is potentially causally related to W1 if the process issuing W2 had already read W1 (or a value written after W1). Two writes are concurrent if neither writer had seen the other’s write.

This is exactly the happens-before relation from the Synchronization chapter, and it can be read through the same bridge used there: a write and the read that observes it behave like a message sent and received, so “P2 read P1’s write, then wrote” is the same shape as “P2 received P1’s message, then sent one.”

Causally consistentP1P2P3P4W(x)aW(x)cR(x)aW(x)bR(x)aR(x)cR(x)bR(x)aR(x)bR(x)ca before b for all (causal); b, c concurrentNot causally consistentP1P2P3W(x)aR(x)aW(x)bR(x)bR(x)aP3 sees b before a, but b depends on a

Why it is highly available is best seen through airplane mode: switch your phone offline and keep reading and writing your local chat. While disconnected you cannot receive anyone’s messages, so you cannot causally reply to them, all your offline writes are concurrent with everyone else’s. On reconnection the system reconciles everything while preserving causal order; no global agreement is needed. The one assumption is that clients do not migrate between replicas: hopping replicas could carry causal knowledge from one to another that the second is unaware of. (Migration can be supported with extra metadata, see the client-centric models.)

P1P2P3W(x)a[1,0,0]R(x)aW(x)b[1,1,0]a arrivesapply athen bb carries [1,1,0]: P3 holds it until a ([1,0,0]) is applied

Implementation: vector clocks. Every write is stamped with a vector clock recording what its writer knew (the latest write it had seen from every other process). A replica applies an incoming write only once it has applied all writes that causally precede it, as the attached vector indicates; out-of-order writes are buffered. Scalar clocks are insufficient because causal dependencies span multiple processes, you must track what each writer knew about every other writer. Causal consistency fits multi-leader naturally: each node accepts writes locally, stamps them, and propagates, withholding dependent updates until their prerequisites arrive.

6.4 FIFO consistency#

FIFO consistency is the weakest data-centric model here. It guarantees exactly one thing:

FIFO consistency

All writes performed by a single process are seen by all other processes in the order that process issued them. Writes from different processes may be seen in any order.

FIFO consistentP1P2P3P4W(x)aW(x)bW(x)cR(x)bR(x)cR(x)aR(x)bR(x)cb before c everywhere; a (other writer) may go anywhereNot FIFO consistentP1P2P3W(x)aW(x)bW(x)cR(x)cR(x)bP3 sees c before b, but P2 wrote b before c

If channels are already FIFO, the model comes for free; otherwise each process tags its writes with a per-sender sequence number, and a replica buffers out-of-order writes until the gaps fill. FIFO is trivially highly available: a process only orders its own writes, so it can read and write while fully disconnected, and per-sender sequence numbers restore order on reconnection.

Beyond FIFO: synchronization variables

Even FIFO still forces every write to become visible to every process, including those that do not care, wasteful, and unnecessary for writes made inside a transaction or critical section that only matter once the section ends. The weak / entry / release family adds synchronization variables: writes are not propagated automatically but only when a process explicitly calls synchronize(), so the programmer forces consistency exactly where needed, typically to push writes at the end of a critical section and pull the needed writes at the start of a reading session. This is the general principle behind Java’s synchronized / volatile: consistency made opt-in, keeping the common case cheap.

6.5 Eventual consistency#

Eventual consistency is weaker still, it does not predicate on the order of operations at all.

Eventual consistency

If no new updates are made, all replicas eventually converge to the same state.

Two replicas may apply the same two writes in different orders and, for a while, hold different states (if x = 1 and x = 2 both propagate, some replicas end at 1, others at 2, depending on arrival order). Used naively this is dangerous, but combined with the right data types it is safe and useful. It fits read-heavy workloads (few conflicting writes), applications that tolerate temporary divergence (a slightly out-of-order social feed), geo-distributed multi-leader deployments, and cases where simplicity matters, no vector clocks, quorums or locks, just propagate and converge.

counter = 2apply +4apply +2op +4op +2both converge to 8propagate the operation, not the value: increments commute

CRDTs (conflict-free replicated data types) are the key mechanism. A CRDT is engineered so that all concurrent updates merge deterministically, regardless of arrival order, always yielding the same final state. The trick is to propagate the operation rather than its result: if operations are commutative and associative, order does not matter. A counter starting at 2 receiving +4 and +2 reaches 8 either way. An append-only list (social-media comments) tags each element with a unique, roughly time-ordered identifier (e.g. a 64-bit timestamp plus node id) and inserts in identifier order, so all replicas converge to the same list, the Last-Writer-Wins (LWW) strategy when the identifier also decides conflicts on the same key. The costs: temporary inconsistency is visible (a message may reorder when a delayed update lands, fine for social media, unacceptable for a bank balance); a hard-limited counter (ticket sales) may briefly oversell by a few before converging; and anything requiring strict ordering or invariants needs a stronger model.

7. Client-Centric Consistency Models#

Every model so far was data-centric and assumed sticky clients bound to one replica. Client-centric models drop that assumption and ask a narrower question: what is a single, migrating client guaranteed to observe as it moves between replicas, regardless of what other clients see?

The diagrams change accordingly. There is one client; the x-axis is its own clock (left is earlier); each row (L1, L2, …) is a replica location it connects to at different times. WS(x_1) is the write set known at L1; WS(x_1; x_2) means L2 knows everything L1 knew plus writes made at L2.

Monotonic readsL1L2R(x1)R(x2)L2 already knows x1Read your writesL1L2W(x1)R(x2)L2 already knows x1Monotonic writesL1L2W(x1)W(x2)L2 already knows x1Writes follow readsL1L2R(x1)W(x2)L2 already knows x1

The four properties, each a different ordering across a migration:

Property Constraint Direction
Monotonic reads reads never go back in time read → read
Monotonic writes writes applied in issue order write → write
Read your writes client always sees its own writes write → read
Writes follow reads writes applied on top of what was read read → write

7.1 Implementing client-centric consistency#

When a client migrates, the new replica knows nothing of its history, so that context must travel with the client, exactly like web cookies or tokens, and in keeping with stateless (REST) servers that keep no per-client state. The client maintains two lightweight sets, on the client side for scalability:

The protocol is then simple: every write gets a unique id from the store; the client records each id it encounters; on migration it presents both sets to the new replica; the replica serves the request immediately if it has already applied everything in the sets, otherwise it blocks (or signals a retry) until background propagation catches it up. Note these are sets of write identifiers, not vector clocks, kept minimal by dropping ids that later writes overwrite.

Client-centric equals causal, per client

Satisfying all four properties together is equivalent to providing causal consistency for one migrating client: the read and write sets encode exactly what the client causally “knows,” and the protocol makes each new replica reach that causal frontier before serving it. In practice a single session-guarantee mechanism provides all four at once, they come as a package. It extends causal consistency to mobile, non-sticky clients, but requires the client to actively carry and present its state.

8. The Consistency Spectrum#

Linearizabilitysingle order + real timeSequential consistencysingle agreed orderCausal consistencycausally related writes orderedFIFO consistencyper-process write orderEventual consistencyonly convergencehigh-availabilityboundarystronger (less available)weaker (highly available)

From strongest to weakest, with the high-availability boundary falling between sequential and causal consistency:

Model Scope Ordering guarantee Highly available
Linearizability global single sequence + real-time intervals
Sequential consistency global single sequence, no time
Causal consistency global causally related writes only
FIFO consistency global per-process write order only
Eventual consistency global none: only convergence
Monotonic reads per-client reads never go back in time
Monotonic writes per-client client’s writes in issue order
Read your writes per-client client sees its own writes
Writes follow reads per-client writes on top of what was read

The implication chain Linearizable ⟹ Sequential ⟹ Causal ⟹ FIFO is the workhorse for reasoning: proving a schedule sequential proves it causal and FIFO; proving it violates FIFO proves it violates all three. These models govern individual read/write operations; grouping operations into transactions adds a further isolation layer, left to later courses.

9. Replication Design Strategies#

Beyond the consistency model, a replicated system makes three further design choices.

Replica placement. Permanent replicas are configured at deploy time (traditional databases, CDN edge nodes); server-initiated replicas are spawned or removed by load (cloud auto-scaling for a Black-Friday spike); client-initiated replicas appear on demand from access patterns (browser and CDN caches).

What to propagate.

Strategy What is sent Best when
Propagate the value the new data value reads ≫ writes; data is small
Propagate a notification a flag/id that a new version exists writes ≫ reads; bandwidth scarce
Propagate the operation the operation to re-execute (+4, append(item)) CRDTs; data large but operation small

Propagating operations saves bandwidth but the receiver must re-execute them (CPU cost) and they must be deterministic: any call to a non-deterministic function (current time, a random number) yields different results on different replicas, so such side effects must be resolved before propagation (evaluate time() once and ship the result).

PushSCCCserver tracks clients; pushes on changePullSCCCstateless server; clients poll periodically

How to propagate: push vs pull.

Dimension Push Pull
Who initiates server sends proactively client polls periodically
Server state must list all client replicas stateless: no client knowledge
Bandwidth one message per change poll + response per cycle
Client freshness near-immediate bounded by poll interval
Typical use server-to-server replication browser and CDN caches

A common hybrid pushes a lightweight notification on a new version and lets the client pull the data only when it reads, minimal bandwidth when reads are rare, while keeping clients aware of staleness. The push/pull choice combines with the protocol family: leader-based propagation is synchronous, asynchronous or semi-synchronous (as in §4.1); leaderless replicas are reconciled by read repair and anti-entropy (as in §6.1), whose replica-to-replica exchange can itself be push- or pull-based.

10. ACID, CAP and Distributed Databases#

Traditional relational databases offer ACID guarantees, and in a distributed setting each property maps to a coordination protocol:

ACID property Meaning Protocol(s)
Atomicity a transaction fully commits or fully aborts across all nodes two-phase commit (2PC)
Consistency data always satisfies integrity constraints application-level + isolation
Isolation concurrent transactions do not interfere locking, timestamp ordering
Durability committed data survives failures replication (Raft, Paxos)

All four require coordination, hence synchronisation, so they all sit on the non-highly-available side of CAP. Since partitions are always possible, the practical choice is perpetual, consistency (strong guarantees, may block during a partition) or availability (always responds, guarantees relaxed), and it recurs independently across atomicity, isolation and replication.

The pendulum: from ACID to NoSQL to NewSQL

Era 1, centralised ACID (pre-2004). Oracle, DB2, SQL Server ran on one expensive machine, so consistency was “free” (no partitions). Banks still use this for regulated workloads. Limit: vertical scaling has a hard ceiling.

Era 2, NoSQL (~2004-2010). Internet scale forced horizontal scaling on cheap commodity machines, so the community dropped transactions: no isolation (no distributed locks), no multi-table atomicity (no 2PC), weak replication (eventual consistency, no consensus). Key-value and document stores (Cassandra, MongoDB, Redis, DynamoDB) scale easily but push transactional complexity onto the application.

Era 3, NewSQL (~2010+). Many applications genuinely need strong consistency, so instead of abandoning CAP, engineers engineered around its worst-case costs by exploiting the typical workload:

  • Google Spanner deploys atomic clocks + GPS and exposes TrueTime, an API returning a bounded time interval [earliest, latest] (a few microseconds wide). On commit a transaction waits out the uncertainty (commit-wait), so its timestamp is globally in the past once visible: full linearizability at global scale by turning bounded clock error into a protocol guarantee.
  • Calvin separates ordering from execution: a sequencing layer (a Paxos/Raft group) agrees a global order before any transaction touches data; execution replicas then run it deterministically, so a failed replica is trivially replaced and most transactions commit with one message instead of 2PC. Requires deterministic transactions.
  • VoltDB / H-Store ask the developer to declare a partitioning aligned with access patterns (partition a booking system by city). Single-partition transactions then run on one node, single-threaded (the thread is the lock), with no 2PC: as fast as NoSQL; rare multi-partition transactions fall back to 2PC + locking.

The shared philosophy: find the bottleneck in the typical workload and engineer specifically around it, Spanner attacks clock synchronisation, Calvin the cost of 2PC during execution, VoltDB distributed locking, rather than building a perfectly general solution.

11. Summary#

Concept Key point
Why replicate fault tolerance, availability, performance (throughput + latency)
Consistency model contract on the ordering/visibility of operations
Single / multi / leaderless no write conflicts / geo-friendly / client contacts a quorum
High availability implementable without blocking coordination
Data- vs client-centric global order of operations vs one migrating client’s view
Linearizability single order + real time; strongest; needs two-phase locking; not HA
Sequential single agreed order, no time; single-leader or quorum; not HA
Causal causally related writes ordered; vector clocks; highly available
FIFO per-process write order; sequence numbers; highly available
Eventual convergence only; CRDTs, LWW; highly available
Quorum rule NW>N/2N_W > N/2 and NR+NW>NN_R + N_W > N
Client-centric monotonic reads/writes, read-your-writes, writes-follow-reads; = per-client causal
Propagation value / notification / operation; push vs pull; read repair + anti-entropy
CAP partitions always possible → choose strong consistency or availability
NewSQL Spanner (TrueTime), Calvin (pre-ordering), VoltDB (partitioning)

12. Exam questions#

Consistency exercises are a recurring exam item, and they are mechanical: with a disciplined method they are almost impossible to get wrong. The solutions below are unofficial worked solutions, the professor does not publish official ones; where the reasoning was confirmed in class (the Q&A and exercise sessions) that is noted.

12.1 Method#

Read a schedule as writes and reads on a replicated store, one row per process, each row in program order. FIFO, causal and sequential do not predicate on time, so only per-row order and read values matter. Since every value is written by exactly one operation, each read is unambiguously tied to its write.

Happens-before graph of the writesx=2x=5x=4y=1y=3P0P1read then writesolid = per-process write order (FIFO); dashed = read-to-write (causal)x=4 and x=5 are concurrent: no path joins them

A reliable recipe:

  1. Write the constraints. For FIFO, list each process’s writes in order (across variables). For causal, add an edge from a value read by a process to any write that process makes afterward (read → later write), and take the transitive closure with the FIFO chains. The result is a partial order on the writes.
  2. Order of attack. Because sequential ⟹ causal ⟹ FIFO, if a schedule is sequential it satisfies all three; if it fails FIFO it fails all three. Often the quickest route is to test the strongest first (try to build one global sequence) and the weakest as a fallback.
  3. To prove consistency, exhibit a full interleaving that works. To disprove it, show that every legal prefix leads to a contradiction: usually a read that cannot return its value.
  4. The “no going back” rule. Once a reader has observed a value on a variable, it cannot later read a value that was overwritten before it. Across variables the same holds through causal chains: having read y=3 that was written after x=5, a later R(x) can no longer return an older x.
  5. Middleware vs application. The middleware receives updates in FIFO/causal order; the application then reads variables in whatever order it likes. A read returns the value currently held for that variable: so a R(y) between two R(x) does not “reset” x.
  6. The inference trap. A schedule that is consistent with a model proves nothing (like a passing test), but a schedule that violates a model proves the store does not implement it. So the honest conclusion is usually “the store is not X; it may be Y.”
The recurring schedule (2023-2024)

Five 2024 exams reuse the same skeleton, P0: W(x)2, W(x)5, R(x)4, W(y)3 and P1: W(y)1, W(x)4, …, varying only P2. Two structural facts drive every answer:

  • P0 reads x=4 after writing x=5, so in any single global order x=5 comes before x=4.
  • The writes x=4 (P1) and x=5 (P0) are causally concurrent, but P0’s W(y)3 reads x=4 first, so under causal consistency y=3 depends on both x=4 and x=5.

12.2 Is the schedule FIFO / causal / sequential?#

Exam of 18 November 2023, question 5
P0:  W(x)2   W(x)5   R(x)4   W(y)3
P1:  W(y)1   W(x)4   R(y)1   R(y)3   R(x)4
P2:  R(y)1   R(x)4   R(x)5   R(y)3

Is this schedule consistent with FIFO, causal and sequential consistency? What can we infer about the store’s consistency model?

Solution

Sequential, no. P0 reads x=4 after writing x=5, forcing x=5 before x=4. But P2 reads x=4 then x=5, forcing x=4 before x=5. No single global order can satisfy both. ✗

Causal, yes. x=4 and x=5 are concurrent, so P0 and P2 are allowed to observe them in opposite orders. Checking each reader: P1 reads y=3 (which needs x=4 and x=5 applied) then x=4; taking its own x=4 as the last x-write applied is a valid causal order. P2 reads x=4, x=5 (concurrent, either order) then y=3 (after both). No causal edge is violated. ✓

FIFO, yes, since causal implies FIFO.

Inference. The store is not sequential (nor linearizable), because it produced a run a sequential store never could. The run is consistent with causal/FIFO, but one schedule cannot prove the store is causal, so the best answer is “not sequential; possibly causal, FIFO or weaker.

Exams of 18 June 2024 (Q5) and 12 July 2024 (Q5)

Both share P0 and P1 as above (P1 without its final read) and differ only in P2’s last two reads:

            June 18                         July 12
P2:  R(y)1 R(x)2 R(y)3 R(x)5 R(x)4    R(y)1 R(x)2 R(y)3 R(x)4 R(x)5

Is each consistent with FIFO, causal and sequential? How does the answer change if the last read is removed from P2?

Solution

Neither is sequential: P2 reads x=5 (or x=4) after R(y)3, and y=3 sits after x=4 in the global order (P0 reads x=4 before writing y=3), so the older x value can no longer be current.

June 18 (…R(x)5, R(x)4). Under causal, R(y)3 requires both x=4 and x=5 applied, fixing their relative order before P2’s last two reads, but P2 then wants x=5 then x=4 as current, which is impossible. So causal fails. Under FIFO, y=3 only needs P0’s own x=2, x=5 (not P1’s x=4), so P2 can hold x=5 current at R(x)5 and apply x=4 afterward: FIFO holds. Verdict: FIFO ✓, causal ✗, sequential ✗. Removing the last R(x)4 removes the impossible second read, so causal now holds (FIFO ✓, causal ✓, sequential ✗).

July 12 (…R(x)4, R(x)5). Now even FIFO fails: R(y)3 forces P0’s x=5 to be applied, then P2 reads x=4 (applying P1’s write on top) and finally wants x=5 again, which was already overwritten. So FIFO ✗, causal ✗, sequential ✗. Removing the last R(x)5 leaves P2: R(y)1, R(x)2, R(y)3, R(x)4, which admits a full global order (x=2, x=5, x=4, y 1, 3): the schedule becomes fully sequential, all three hold.

Exam of 6 September 2024, question 5
P0:  W(y)1   W(x)2   R(y)3   W(x)6   R(y)5
P1:  R(y)1   W(y)3   R(x)2   R(x)4   R(y)5
P2:  R(x)6   W(y)5   R(x)6   W(x)4   R(x)2

Is it FIFO / causal / sequential consistent? How does the answer change if the last read R(x)2 is removed from P2?

Solution

As given, none of the three. P0 writes x=2 before x=6 (program order), yet P2 reads x=6 (op 1) and later x=2 (op 5), seeing P0’s two writes in reverse order. That already violates FIFO, hence causal and sequential too. ✗ / ✗ / ✗.

Removing the last R(x)2 leaves P2: R(x)6, W(y)5, R(x)6, W(x)4, which reads only x=6 from P0. A full global order now exists, for example W(y)1, R(y)1, W(x)2, R(x)2, W(y)3, R(y)3, R(x)2?… more compactly: x writes ordered x=2 → x=6 → x=4, y writes ordered y=1 → y=3 → y=5, with each read placed where its value is current, so the schedule becomes fully sequential: FIFO ✓, causal ✓, sequential ✓. The single reversed read was the sole obstacle.

12.3 Which values may be read? (the “?” variant)#

Exam of 17 January 2024, question 5
P0:  W(x)2   W(x)5   R(x)4   W(y)3
P1:  W(y)1   W(x)4   R(y)1   R(y)3   R(x)4
P2:  R(y)3   R(x)4   ?

Which values of x and of y is P2 allowed to read at the ?, under FIFO, causal and sequential consistency?

Solution (confirmed in the 31 Oct Q&A)

By the ?, P2 has read y=3 and x=4. Reading y=3 forces P0’s x=2, x=5 to be delivered; reading x=4 (P1) as current forces it to arrive after x=5, so the current x is 4 and x=5 is already overwritten.

Model x at ? y at ?
FIFO {4} {1, 3}
Causal {4} {3}
Sequential {4} {3}
  • x is always {4}. Once x=4 overwrote x=5, the reader cannot go back to x=5 (nor to x=2); there is no newer x-write.
  • y under FIFO is {1, 3}. y=1 (P1) and y=3 (P0) come from different writers, and FIFO does not order writes across writers: depending on the delivery interleaving, either can be current at the ?.
  • y under causal/sequential is {3}. Causally y=1 → x=4 → y=3, so y=1 precedes y=3; having observed y=3, the reader may no longer return y=1.

12.4 Older schedule (five processes)#

Exam of 1 February 2013, question 5

Variables start at 0.

P0:  W(x)2   R(y)2   W(x)3   R(x)3
P1:  W(x)1   R(x)1   W(y)2   W(y)3
P2:  R(x)0   W(y)1   R(y)1   R(x)2
P3:  R(x)2   R(y)2   R(x)2   R(y)3
P4:  R(x)3   R(y)2   R(y)3

Is this schedule consistent with FIFO, causal and sequential? (b) If not, could removing one operation make it consistent, and which?

Solution

This schedule is consistent with all three models. No process reads two different values of the same variable in an order that another process contradicts: for x, P2 reads 0 then 2 and nobody reads x-values in a conflicting order; for y, P3 and P4 both read y=2 then y=3, and only P2 ever reads y=1. A single global order therefore exists, for instance:

R(x)0 · W(x)1 · R(x)1 · W(y)1 · R(y)1 · W(x)2 · R(x)2 · R(x)2 · W(y)2 ·
R(y)2 · R(y)2 · R(x)2 · W(x)3 · R(x)3 · R(x)3 · R(y)2 · W(y)3 · R(y)3 · R(y)3

Every read returns the current value and every process’s program order is respected, so the schedule is sequentially consistent, hence causal and FIFO too. (b) Since it is already consistent with all three, no operation needs to be removed. (Had it failed only sequential consistency because of a single conflicting read, removing that read would be the move, but here there is no conflict.)

12.5 Building on a weaker store#

Exam of 14 February 2024, question 5

An application needs FIFO consistency, but the underlying store only provides eventual consistency. Assuming no failures, does the store meet the requirement? If not, can you build FIFO consistency on top of an eventually consistent store, and how?

Solution

No, the store alone is insufficient. Eventual consistency only guarantees that all updates eventually reach every replica; it says nothing about the order in which they are applied, so a process could observe two writes by the same author out of order, exactly what FIFO forbids.

Yes, FIFO can be layered on top, precisely because eventual consistency guarantees eventual delivery of every update. At the application (or middleware) level, attach a per-sender sequence number to each write. A receiver buffers an update whose sequence number is ahead of what it expects and delivers it only once the gap is filled, so every author’s writes are applied in issue order. The very same construction gives causal consistency if the metadata is upgraded from a scalar sequence number to a vector clock: buffer an update until every write it causally depends on has been delivered. In both cases the eventual-delivery guarantee ensures no update is buffered forever.

13. Glossary#

Term Meaning
Consistency model contract between clients and store on ordering/visibility of operations
Data-centric model over the global order of operations across all replicas
Client-centric model over what a single migrating client observes
Linearizability single order respecting real-time intervals; strongest; recency guarantee
Sequential consistency single agreed order preserving program order; time-independent
Causal consistency causally related writes ordered; concurrent writes free; highly available
FIFO consistency per-process write order preserved; weakest data-centric; highly available
Eventual consistency replicas converge if updates stop; no ordering guarantee
Highly available implementable without blocking/synchronous coordination
Single-leader one replica orders all writes; followers replicate
Quorum leaderless rule NW>N/2N_W > N/2, NR+NW>NN_R + N_W > N
ROWA read-one-write-all: NW=NN_W = N, NR=1N_R = 1
Read repair / anti-entropy mechanisms to refresh stale replicas
Vector clock per-process timestamp tracking causal dependencies
CRDT data type whose concurrent updates merge deterministically
LWW last-writer-wins conflict resolution by unique timestamp
CAP under partitions, choose consistency or availability
2PC two-phase commit, protocol for distributed atomicity
TrueTime Spanner’s bounded-uncertainty clock API enabling global linearizability

Sources: van Steen & Tanenbaum, Distributed Systems* (3rd ed., 2017); Kleppmann, Designing Data-Intensive Applications (2017); Viotti & Vukolić, “Consistency in Non-Transactional Distributed Storage Systems”, ACM CSUR 2016; Bailis et al., “Highly Available Transactions”, VLDB 2014; Lamport (1979) on sequential consistency; Corbett et al. (Spanner, OSDI 2012); Thomson et al. (Calvin, SIGMOD 2012).*

Compiled from G. Cugola's lectures (A.Y. 2025/26), slides + class transcriptions. Not official course material.