studyDDIAChapter 6 · Replication

Last-Write-Wins Discards the Write That Actually Happened Last

Two leaders write the same key; a clock 30 seconds fast makes an earlier write carry a larger timestamp. LWW keeps the larger timestamp — so it silently drops "C", the write that truly happened last, and keeps an older "B". Sync the clocks and the very same writes resolve correctly. "Last write wins" is a misnomer.


Concept

Multi-leader and leaderless replication let two nodes accept a write to the same key at the same time, so conflicts are inevitable and something has to resolve them. Last-Write-Wins (LWW) is the simplest rule: stamp every write with a timestamp and keep the one with the greatest timestamp. No coordination, no merge — and silent data loss. The "timestamp" is a wall-clock reading, and wall clocks drift. If one node's clock runs fast, a write it made earlier in real time carries a larger timestamp than a write another node made later; LWW keeps the larger timestamp and discards the write that truly happened last — with no error. You will predict the winner, then watch "C" (the real last write) get silently dropped while an older "B" survives, purely because a clock was 30 seconds fast.

Provenance

Designing Data-Intensive Applications, 2nd ed. (Kleppmann & Riccomini, 2025), Chapter 6 — Replication, §"Last write wins (discarding concurrent writes)":

the name "last write wins" is misleading

The book stresses that LWW's surviving write was not necessarily the last one: because timestamps come from clocks that disagree, "last" is whatever clock said the biggest number. This exercise makes that discarded write visible.

Prerequisites

The surprise is that a conflict resolver can be deterministic and wrong at the same time. These help; each line notes where it shows up.

  1. Conflict resolution in multi-leader/leaderless replication — two replicas accept writes to one key independently, so on sync one write must be chosen; both scenarios below are exactly that two-write conflict.
  2. Last-Write-Wins (LWW) — the rule "keep the write with the greatest timestamp"; it is what lww_resolve computes and what picks the winner in each scenario.
  3. Clock skew / unreliable wall clocks — different nodes' time-of-day clocks disagree by seconds; modeled here as a per-node offset added to real time to produce the write's timestamp.
  4. Real order vs. timestamp order — the write that happened last in wall-clock time need not have the largest timestamp; the transcript prints both orders side by side so you can see them diverge.
Where to learn the prerequisites LWW and its data loss (#1, #2): DDIA §"Last write wins (discarding concurrent writes)" and §"Handling write conflicts". Why clocks can't be trusted for ordering (#3, #4): DDIA Ch. 9, §"Unreliable clocks" and §"Timestamps for ordering events" — LWW's failure is a special case of "don't order events by wall-clock time." If only one is new, make it #3 — the whole failure rides on clocks disagreeing.
Environment these numbers came from Deterministic — no real clocks and no randomness, so the winner reproduces exactly. Captured on macOS 26.5.2 (Darwin 25.5.0), arm64, Python 3.15.0a8, standard library only — no Docker, no third-party packages. Two leaders write title to the same key; real times and per-node clock offsets are passed in explicitly, so timestamp = real_time + offset is fixed.

Mental model

Two leaders, one key title, and one conflict resolved by LWW:

When the two orders agree, LWW is correct. When a clock skew flips them, LWW keeps an older write and the real-last write is lost silently. The whole exercise is one script, code/lww_clock_skew.py.

Setup

cd study/ddia/ch06/code
python3 lww_clock_skew.py

Do not read the output yet — make each prediction first.


Step 1 · predict the winner under a skewed clock

Predict. At real time t=100, node A (clock +30s fast) writes title="B". Then later, at t=110, node B (clock accurate) writes title="C". The real-last write is "C". Which value does LWW keep — "B" or "C"? Write down the winner and the two timestamps.

Which write survives when A's clock is 30s fast?
Scenario 1 -- node A's clock is skewed +30s (fast) -------------------------------------------------- real (wall-clock) order of events -- what actually happened: t=100s real node A (clock offset +30s) writes title='B' -> stamps ts=130 t=110s real node B (clock offset +0s) writes title='C' -> stamps ts=110 => the write that ACTUALLY happened last: title='C' (node B, t=110s real) timestamp order -- what LWW compares (greatest ts wins): ts=110 title='C' (node B) ts=130 title='B' (node A) => LWW WINNER: title='B' (node A, ts=130) - title='C' (node B, ts=110): LOST (silently discarded) <-- this was the REAL last write! verdict: WRONG: LWW kept an OLDER write; the real-last write was lost with no error.

A's write happened first in real time but carries the bigger timestamp (130 > 110) because A's clock was 30s ahead. LWW keeps ts=130 → "B". The write that actually happened last, "C", is discarded — no conflict raised, no error logged. The data is just gone.

Step 2 · the control: sync the clocks, keep the same two writes

Predict. Same two writes, same real times, but now both clocks are accurate (offset 0). Does LWW still lose "C"?

With clocks synced, does "C" survive?
Scenario 2 (control) -- clocks synced (both offset 0) ----------------------------------------------------- real (wall-clock) order of events -- what actually happened: t=100s real node A (clock offset +0s) writes title='B' -> stamps ts=100 t=110s real node B (clock offset +0s) writes title='C' -> stamps ts=110 => the write that ACTUALLY happened last: title='C' (node B, t=110s real) timestamp order -- what LWW compares (greatest ts wins): ts=100 title='B' (node A) ts=110 title='C' (node B) => LWW WINNER: title='C' (node B, ts=110) - title='B' (node A, ts=100): LOST (silently discarded) verdict: CORRECT: LWW kept the real-last write.

With clocks agreeing, timestamp order equals real order (100 < 110), so LWW keeps "C" — the actual last write. The algorithm did not change; only the clocks did. LWW is correct exactly when clocks agree.

Step 3 · the bottom line

Predict. Same algorithm, two runs. In how many did LWW keep the real-last write — and what was the only thing that differed between them?

What actually decided whether LWW was correct?
bottom line: skewed clock -> LWW correct? False (the real-last write 'C' was silently lost) synced clock -> LWW correct? True (same two writes, but now 'C' survives) LWW is only as trustworthy as the clocks agree. "Last write wins" is a misnomer: the survivor is the greatest-timestamp write, not the last one.

The writes were identical across both runs; only the clock offset changed. LWW's correctness rode entirely on whether the clocks agreed — not on anything about the writes themselves.


What you should see

Why

LWW's only input is the timestamp, and the timestamp is real_time + offset — the node's own clock reading. LWW picks max(ts). When both offsets are 0, ts is a monotonic function of real_time, so max(ts) is the last write: the control scenario keeps "C" correctly. The algorithm is not broken; it is exactly right whenever the clocks form a shared, consistent order of time.

The skew breaks that assumption. A's +30s offset adds a constant to A's timestamps only, so a write A made 10 seconds before B's still reads 100 + 30 = 130 > 110. LWW compares 130 vs 110 and keeps A's older "B". It has no way to know 130 came from a fast clock rather than from a later moment — a timestamp carries no evidence of the clock that produced it. So the resolution is deterministic and repeatable (re-run it, "C" is always lost) yet causally wrong. And because a conflict "resolved" is a conflict silenced, nothing surfaces: no exception, no divergence alarm, no second version to reconcile. "C" simply never existed as far as the surviving replica is concerned.

The boundary — LWW trades durability for convergence, and clocks set the price LWW's real guarantee is only that every replica eventually agrees on some value, not that it keeps the right one; the moment two nodes' clocks disagree by more than the gap between two writes, "the right one" and "the one LWW keeps" can differ, and a write vanishes. The fix is to stop ordering events by wall-clock time: use version vectors / causal tracking to detect genuine concurrency and keep both conflicting writes (siblings) for the application to merge, or logical clocks (Lamport timestamps) that at least give a consistent total order without pretending to be real time. LWW is acceptable only when losing concurrent writes is genuinely fine — a cache, a last-seen presence timestamp — and never when each write is data you can't afford to drop.

Go deeper

Sources: DDIA 2e, Ch. 6, §"Last write wins (discarding concurrent writes)", §"Handling write conflicts" · Ch. 9, §"Unreliable clocks".