Module 8 · Scheduled

Module 8: Replication, Consistency, CAP, and PACELC

Use PostgreSQL, etcd, and CouchDB to observe stale reads, quorum behavior, and write conflicts, then choose explicit consistency and availability guarantees.

Learning goals

Checkout updates stock from 10 to 9, yet a customer still sees 10. Two disconnected devices both edit the same cart. An agent retrieves an older itinerary after a newer one receives approval. This module uses these concrete situations to ask which copy can answer, which writes can succeed, and how the system recovers.

Learning Objectives

  1. Trace a write through primary and replica acknowledgement points.
  2. Reproduce a stale read and explain read-your-writes and eventual convergence.
  3. Compare linearizable, sequential, and causal consistency using small histories.
  4. Explain quorum overlap and its limits.
  5. Apply CAP during a partition and PACELC during normal operation.
  6. Observe concurrent revision conflicts and apply an explicit merge policy.
  7. Select a consistency policy for one project operation using experimental evidence.

Required preparation

Review timeouts in Module 3, duplicate delivery in Module 5, and causal order and fencing in Module 7. Bring Python 3.10 or newer and a working container engine with Docker Compose v2. Download images before class. Run one profile at a time. No cloud account, paid service, Python package installation, or real customer data is needed.

Read the selected sections of PostgreSQL replication, etcd guarantees, and CouchDB conflicts. The lab instructions identify the relevant sections. Formal proofs and the deeper CRDT readings are optional.

The 20 slides allow about 140 minutes for guided teaching and exercises, leaving 10 minutes in the class meeting for a break and questions. Use the Python models if container startup is unavailable, then complete the actual database experiments later. Models demonstrate reasoning rules and do not validate a database implementation.

Download the classroom kit ZIP

with Compose configuration, Python helper, instructions, and a project memo template.

Read the lab instructions Markdown

for commands, expected output, troubleshooting, and recovery.

Core Concepts

Replication

Maintain copies of the same logical data on multiple nodes.

Stale read

A read that omits a relevant completed update.

Linearizability

An operation appears to occur at one point between its invocation and response, preserving real-time order.

Read your writes

A session observes its own completed writes in subsequent reads.

Eventual consistency

Replicas converge once updates stop and propagation and repair succeed.

Quorum

A required subset of replicas participating in an operation.

CAP

Under a partition, linearizability and availability cannot both be guaranteed for a replicated register.

PACELC

During partitions consider availability versus consistency; otherwise consider latency versus consistency.

Guided exercises

Predict the output before each command, compare it with the actual result, and record what the observation does and does not establish. The same teaching sections drive Presentation Mode and the printable handout.

1. Module goal — design reliable replicated systems

Choose replication and consistency guarantees that protect application correctness while balancing latency, availability, and failures.

  • Explain what a successful read or write guarantees across replicas.
  • Predict stale reads, quorum behavior, and outcomes during network partitions.
  • Use PostgreSQL, etcd, and CouchDB experiments to justify a consistency policy for your project.
  • Try: python3 lab.py doctor

Expected observation and discussion: Download the kit before class. Run one container profile at a time. Model exercises work without Docker.

Open-source tools to try (1)
  • Python setup

    Read the linked section, then run the matching exercise in the classroom kit.

2. Replication in distributed systems

Replication maintains the same logical data on multiple networked nodes, which must propagate updates despite delays and failures.

  • Replicas support failover, read scaling, and placing data near users.
  • Propagation takes time; replicas can disagree temporarily or during a partition.
  • Replication copies data; partitioning divides data. Replication does not replace backups.
  • Try: python3 lab.py pg status

A primary and a read replica

flowchart TD
  P[Primary accepts stock update]
  P -->|WAL records| R[Read replica]
  R -->|Possibly stale stock| C[Reader]

Writes go to the primary. The standby replays the log and may serve older state until it catches up.

Expected observation and discussion:

Open-source tools to try (1)

3. Log based replication

The primary records changes in a log. The replica receives that log and replays it into its own data pages.

  • Open source: PostgreSQL streams write-ahead log (WAL) records to standbys.
  • Commercial: Microsoft SQL Server Always On sends transaction log records to secondary replicas.

The log connects the primary and the replica

%%{init: {"flowchart": {"nodeSpacing": 16, "rankSpacing": 32, "padding": 6}, "themeVariables": {"fontSize": "22px"}}}%%
flowchart LR
  C[Writer] -->|Write request| P
  subgraph P[Primary]
    direction TB
    E[Transaction engine] -->|Generate WAL| L[Durable WAL log]
    L -->|Log records| T[WAL sender]
    E -->|Flush pages after WAL| D[Primary data pages]
  end
  P <-->|WAL to replica / progress to primary| S
  subgraph S[Replica]
    direction TB
    R[WAL receiver] -->|Write and flush| W[Local WAL log]
    W -->|Records in order| A[Replay process]
    A -->|Apply changes| B[Replica data pages]
  end
  S -->|Read query result| Q[Reader]

Inside each boundary, arrows show local work. The network carries WAL records to the receiver and progress feedback to the primary. Replica reads see replayed changes. Client acknowledgement depends on the replication mode shown next.

Expected observation and discussion: Follow one stock update. The transaction engine generates ordered WAL records. The primary flushes WAL before the corresponding dirty data pages reach disk. Its sender streams the log to the replica receiver. The receiver writes and flushes local WAL. Replay applies those records to replica data pages, which serve read queries. Feedback reports received, flushed, and replayed positions. Receiving or flushing a record does not mean a query can see it. The configured commit policy decides which feedback, if any, the primary waits for before replying to the writer. PostgreSQL calls these positions LSNs. A replica first needs a base backup, then the subsequent WAL stream. This is physical log replication, not SQL statement replay or a consensus protocol.

Open-source tools to try (3)

4. Asynchronous vs. synchronous replication

Compare when the primary acknowledges a commit and what waiting buys the application.

  • Asynchronous — acknowledge after the primary commits; replica progress is not required.
  • Synchronous — wait for the configured replica acknowledgement before reporting success.
  • Pros — lower write latency; a slow or unreachable replica need not block primary writes.
  • Pros — acknowledged writes reach the required replicas; remote_apply also waits for replay.
  • Cons — stale replica reads; recent acknowledged writes may be lost if failover selects a lagging replica.
  • Cons — extra network and replica latency; writes can block when required replicas are unreachable.

Expected observation and discussion: Read the left column as asynchronous and the right as synchronous. Synchronous acknowledgement may mean received, flushed, or applied, depending on product and setting; it does not imply every replica can serve the latest value. Run python3 lab.py pg sync. This kit has exactly one named synchronous standby. Default synchronous_commit=on without a configured synchronous standby does not wait for remote replication. Restore with pg async.

Open-source tools to try (1)

5. Stale reads

A stale read returns an older version even though a newer write has completed elsewhere; acting on it can break application expectations.

  • A saved profile appears unchanged; the user retries and may trigger duplicate side effects.
  • Old stock can cause checkout to promise unavailable items if it skips an authoritative inventory check.
  • Stale permissions can expose revoked access; authorize against the required current state.
  • Pause replay, update the primary, and compare versions; choose freshness per operation.
  • Try: python3 lab.py pg stale

Expected observation and discussion: Pause replay, not the container. WAL may continue arriving while application of WAL is paused. Keep the pause brief.

Open-source tools to try (1)

6. Eventual consistency with Apache Cassandra

Cassandra can accept a write before all replicas are current, then reconcile their state through delivery and repair.

  • Example: replication factor 3 stores a profile on A, B, and C; C is temporarily unreachable.
  • At write consistency ONE, A can acknowledge name=Sam while another replica still has name=Alex.
  • A read at ONE from a stale replica can return Alex; ONE does not require read/write overlap.
  • Hints, read repair, and scheduled anti-entropy repair help replicas converge after communication resumes.
  • Cassandra offers tunable consistency; eventual consistency alone sets no freshness deadline.

Expected observation and discussion: Cassandra is a conceptual example, not an added container dependency. The coordinator sends writes to all replicas; ONE controls how many acknowledgements it waits for. Cassandra reconciles mutations using timestamps, so clock discipline and repair matter. Hints and read repair are best effort; run anti-entropy repair to cover missed updates. For the existing lab, run python3 lab.py pg catchup to observe PostgreSQL log replay catching up; this is not Cassandra repair.

Open-source tools to try (2)

7. Read your writes — see your own saved changes

A session can require its next read to include its own completed update.

  • Reading the primary is one option in this simple single-primary exercise.
  • Another option waits for a replica to reach a known write position.
  • Try: python3 lab.py pg stale

Expected observation and discussion: After stale, compare the primary and replica output, then run pg catchup. Sticky routing alone is insufficient after failover or a lost session token.

Open-source tools to try (1)

8. Linearizable reads — respect completed writes across clients

A read started after a successful write must include that write or a later one.

  • etcd uses linearizable reads by default.
  • A concurrent write can still complete after the read’s logical point.
  • Try: python3 lab.py etcd seed

Expected observation and discussion: Start the etcd profile first. Write stock=10 through etcd1, then read through etcd3. Equal values alone do not prove linearizability.

Open-source tools to try (1)

9. Sequential and causal consistency

Sequential consistency orders all operations. Causal consistency preserves dependencies and can leave independent edits unordered.

  • Sequential: preserve every client’s program order in one shared order.
  • Causal: observing a reply requires observing the message it depends on.
  • Try: python3 lab.py model histories; then python3 lab.py model causal

Expected observation and discussion: In the counterexample each writer writes before reading. A seeing B’s value and B seeing A’s old value creates an ordering cycle. This is a history exercise, not an etcd mode. The Python exercise illustrates a requirement only. It does not implement a causal database. Read-your-writes is a session guarantee; do not equate it with every consistency model.

Open-source tools to try (2)

10. Quorums — why read and write sets must overlap

A quorum is the required number of replica responses for an operation to succeed; a majority quorum is more than half.

  • N is the replica count; W is required write acknowledgements; R is required read responses.
  • For N=3, W=2 stores the write on A and B; R=2 reading B and C intersects at B.
  • R + W > N forces every read set to meet every completed write set in at least one replica.
  • Without overlap, W=1 on A and R=1 on C can return an older value.
  • The reader must reconcile versions; overlap alone does not ensure linearizability.
  • Try: python3 lab.py model quorum

Expected observation and discussion: Assume a fixed replica set and completed acknowledgements. Sloppy quorums, concurrent writes, and version selection complicate this arithmetic. etcd uses Raft, not this simple Dynamo-style R/W algorithm.

Open-source tools to try (1)
  • Dynamo paper

    Read the linked section, then run the matching exercise in the classroom kit.

11. etcd — coordination when the network partitions

etcd is a distributed key-value store for shared configuration and coordination; Kubernetes uses it to store cluster state.

  • Raft replicates an ordered log so a majority can agree on committed state.
  • The lab uses stock as a small key-value example; production etcd typically holds control-plane metadata.
  • A partition blocks communication between live nodes; disconnect etcd3 while leaving it running.
  • Two connected members can commit stock=9; the isolated member cannot commit writes or serve linearizable reads.
  • Try: python3 lab.py etcd isolate

Two connected members and one isolated member

flowchart TD
  A[etcd1 accepts stock 9] <-->|Peer traffic| B[etcd2 stock 9]
  B -.->|Network disconnected| C[etcd3 local stock 10]

The majority can accept a new value. The isolated live member retains an older value and cannot satisfy a fresh linearizable read.

Expected observation and discussion: Seed first. The helper writes 9 through the majority after isolation. If the isolated member was leader, allow a new election.

Open-source tools to try (2)

12. CAP

During a partition, a replicated register cannot guarantee both linearizability and availability.

  • C means one-copy behavior consistent with real-time order.
  • A means every request to a non-failing node eventually completes successfully.
  • Try: python3 lab.py model cap

Expected observation and discussion: P is the failure condition. CAP availability has no fixed latency bound. An error response does not count as a successful read or write. Avoid the unconditional choose-any-two slogan.

Open-source tools to try (1)

13. Availability choices

One operation can prefer fresh state while another tolerates older state.

  • An isolated etcd member can serve a member-local read that may be stale.
  • The same member cannot complete a fresh linearizable read without coordination.
  • Try: python3 lab.py etcd reads

Expected observation and discussion: reads compares the two modes on connected etcd1 and isolated etcd3. Re-run etcd isolate only after heal and seed if demonstrating again. The isolate exercise already prints isolated local success and linearizable failure. etcd calls member-local mode serializable; this is not SQL transaction serializability.

Open-source tools to try (1)
  • etcd read modes

    Read the linked section, then run the matching exercise in the classroom kit.

14. Recovery

Restoring connectivity allows replicas to catch up.

  • Reconnect etcd3 and verify it now returns stock 9.
  • Recovery needs evidence of convergence, not just a green process indicator.
  • Try: python3 lab.py etcd heal

Expected observation and discussion: Use this immediately after the partition activity. Do not manually promote a PostgreSQL standby in this module: safe failover and fencing need a separate workflow.

Open-source tools to try (1)

15. PACELC

CAP focuses on partitions. PACELC also explains the tradeoff when the network is healthy.

  • If a Partition occurs (P): choose between Availability (A) and Consistency (C).
  • Else (E), during normal operation: choose between lower Latency (L) and stronger Consistency (C).
  • Partition example: return older replica data, or wait/reject requests that require fresh data.
  • Healthy network example: waiting for replica replay gives a stronger guarantee but can increase response time.
  • Try: python3 lab.py pg latency

Expected observation and discussion: P: A/C; Else: L/C. The localhost experiment includes docker and psql overhead and may hide the network cost. It does not assign a permanent PACELC label to an entire product.

Open-source tools to try (1)

16. Conflicting writes

Two disconnected writers can create different children of the same document revision.

  • Edit the cart independently on two CouchDB servers, then replicate.
  • A default winning revision can hide another conflicting branch.
  • Try: python3 lab.py couch conflict

Two edits from a shared cart revision

flowchart TD
  S[Cart contains book] -->|Writer A adds pen| A[Branch A]
  S -->|Writer B adds notebook| B[Branch B]
  A -->|Replicate revision| C[CouchDB retains both branches]
  B -->|Replicate revision| C

Both writers start from the same revision. Replication preserves divergent revisions, so application logic must inspect and resolve the conflict.

Expected observation and discussion: Start the couch profile and run couch seed first. Replication is manual one-shot, so no communication occurs between the independent edits.

Open-source tools to try (1)
  • CouchDB conflicts

    Read the linked section, then run the matching exercise in the classroom kit.

17. Conflict resolution

A merge rule must follow the meaning of the data.

  • For this additions-only cart, union preserves both independent additions.
  • Delete losing leaf revisions and replicate the resolved state.
  • Try: python3 lab.py couch resolve

Expected observation and discussion: Expected items are book, notebook, pen on both servers with no _conflicts. Union fails if removals are meaningful. Concurrent edits during resolution can create more conflicts.

Open-source tools to try (1)

18. Mergeable state

Some data types have merge rules that tolerate repeats and reordering.

  • A grow-only counter keeps one component per writer and merges using maximum.
  • This rule preserves two independent increments without a shared read-modify-write.
  • Try: python3 lab.py model merge

Expected observation and discussion: This standard-library model illustrates a G-counter. It supports increments only and assumes distinct writer identities. A convergent counter does not enforce limited inventory or uniqueness.

Open-source tools to try (1)
  • CRDT introduction

    Read the linked section, then run the matching exercise in the classroom kit.

19. Stale agent context

An AI agent can retrieve an old proposal even after a newer version receives approval.

  • The vector index helps retrieval but does not authorize execution.
  • The tool validates the exact version against authoritative approval state.
  • Try: python3 lab.py model agent

Expected observation and discussion: This is a small workflow model, not a running Milvus deployment. A stronger retrieval setting alone cannot replace approval validation. Connect to the midterm’s exact-version approval question.

Open-source tools to try (1)

20. Apply replication and consistency to your class project

Choose guarantees per operation, then demonstrate how your project behaves when a replica lags or a network link fails.

  • Booking or commerce: protect no-double-booking or no-overselling with an atomic authoritative write; a fresh read alone is insufficient.
  • Feeds or analytics: allow measured staleness where safe; show freshness and provide read-your-writes after edits.
  • Collaborative apps: define how concurrent edits merge; document which changes could be lost.
  • AI agents: recheck permissions and inventory before actions when retrieved context may be stale.
  • Pick one operation; state its invariant, staleness budget, partition response, and recovery policy.
  • Inject lag or a partition, record latency and correctness, and justify your choice in the consistency memo.

Expected observation and discussion: Teams explain one operation in pairs. Use the memo template. Canvas remains the official submission and grading system.

Open-source tools to try (1)

Lab sequence and expected results

Start from the extracted kit directory:

python3 lab.py doctor
docker compose --profile pg up -d --wait
python3 lab.py pg status
python3 lab.py pg positions
python3 lab.py pg sync
python3 lab.py pg stale
python3 lab.py pg catchup

The two database processes initially agree. The positions exercise shows the primary sender’s progress alongside the replica’s received and replayed log positions. An LSN is a position in PostgreSQL’s write-ahead log. A received or flushed record may still await replay, so its presence alone does not establish read visibility. With replay paused, the primary advances while the standby returns the older version. After resuming replay, the helper polls until the versions match. The read-your-writes exercise repeats the stale-read experiment and compares primary and standby reads. A primary read is appropriate only under this lab’s single-primary assumptions. Failover requires additional routing and recovery guarantees.

Before slide 8, switch profiles:

docker compose --profile pg stop
docker compose --profile etcd up -d
python3 lab.py etcd seed
python3 lab.py etcd status

The history and quorum activities on slides 9–10 use standard-library Python. The quorum helper enumerates fixed replica subsets. It is not an implementation of etcd’s Raft protocol, and quorum overlap alone does not establish linearizability.

For slides 11–14:

python3 lab.py etcd isolate
python3 lab.py model cap
python3 lab.py etcd reads
python3 lab.py etcd heal

The helper isolates a live member’s network, writes through the connected majority, and compares the isolated member’s old local read with its unsuccessful linearizable read. A three-second client timeout bounds the classroom wait. CAP availability itself has no fixed response-time bound. An error is a response but does not successfully implement the requested read or write. Heal before stopping the cluster or repeating seed.

For PACELC on slide 15:

docker compose --profile etcd stop
docker compose --profile pg up -d --wait
python3 lab.py pg latency
python3 lab.py pg async

Compare the actual elapsed medians and the acknowledgement conditions. Localhost measurements include process overhead and may not expose a significant latency difference. Acknowledging remote replay still requires a remote condition. Avoid assigning an unconditional CAP or PACELC label to an entire database product.

For the conflicting-write exercises:

docker compose --profile pg stop
docker compose --profile couch up -d
python3 lab.py couch seed
python3 lab.py couch conflict
python3 lab.py couch resolve

Expect two revision branches after independent edits, then one resolved additions-only cart containing a book, a notebook, and a pen. Inspect both branches before resolving. Replication selects a default winning revision but cannot infer your business merge rule. A last-write-wins rule chooses a version using an ordering rule, often a timestamp, and can discard another user’s valid change. Clock skew makes physical-time choices especially risky. A revision precondition can reject a stale edit on one server, while disconnected accepted branches still need a resolution policy. This exercise uses manual one-shot replication between two actual servers, with no continuous sync between edits.

Finish with:

python3 lab.py model merge
python3 lab.py model agent
python3 lab.py model decision

An additions-only cart and a grow-only counter can use simple merge rules. Seat inventory, money, removals, and approval transitions require their own invariants and concurrency controls. A convergent result can still violate a business rule. An AI executor must check authoritative state and exact-version approval even when its retrieval tool offers stronger freshness options.

Recovery and project practice

Follow the kit’s recovery steps after each experiment. Use the

project consistency memo Markdown template

to justify one operation’s policy with observed evidence. This is public learning material. Canvas remains the official system for graded submissions, exams, announcements, and individualized feedback.

Knowledge Check

A write succeeds on the primary, then a standby returns the old value. Did the write necessarily fail?

No. The standby may not have applied the write. Inspect versions and replication progress. The acknowledgement point and read route determine what the caller can infer.

Does a two-of-three read/write overlap prove linearizability?

No. Under a fixed replica set it establishes an intersecting member, but concurrent-write ordering, version selection, and the full protocol still matter.

Why does the isolated etcd member reject or time out a fresh read while a local read succeeds?

The local mode can answer from old state. The fresh linearizable operation requires coordination the isolated member cannot complete. This difference applies to those read modes, not all operations.

What does PACELC add to the partition discussion?

It includes the latency versus consistency tradeoff during normal operation. Remote acknowledgement or coordination can add latency even when all links work.

Why is taking the union of two carts unsuitable when users can remove items?

Union would resurrect removed items. The merge policy must represent removals and concurrency, or operations must coordinate under an appropriate invariant.

Can a fresh vector search authorize an agent to execute a booking?

No. Freshness and authorization are different requirements. The tool must validate the exact immutable proposal and its authoritative human approval before execution.

Key Takeaways

  • An acknowledgement has meaning only relative to its configured commit policy.
  • Replication can expose stale reads and does not replace backups.
  • Session guarantees, causal order, and linearizability answer different questions.
  • Quorum overlap is useful arithmetic, not a complete consistency protocol.
  • Describe CAP and PACELC per operation and configuration, not as universal product labels.
  • Conflict resolution needs domain rules; convergence alone does not preserve every invariant.
  • Revalidate authoritative versions and permissions before an agent executes a consequential action.

Additional Resources