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.
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.
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.
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.
lww_resolve computes and what picks the winner in each scenario.offset added to real time to produce the write's timestamp.title to the same key; real times and per-node clock offsets are passed in explicitly, so timestamp = real_time + offset is fixed.
Two leaders, one key title, and one conflict resolved by LWW:
(node, value, real_time, offset). Its timestamp is the node's reading of the clock: ts = real_time + offset. A skewed clock has a nonzero offset.real_time — the truth of what happened when.ts — what LWW actually compares.ts (ties broken by value so every replica picks the same winner). It never looks at real_time; it can't.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.
cd study/ddia/ch06/code
python3 lww_clock_skew.py
Do not read the output yet — make each prediction first.
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.
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.
Predict. Same two writes, same real times, but now both clocks are accurate (offset 0). Does LWW still lose "C"?
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.
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?
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.
verdict: WRONG and no error.verdict: CORRECT) — LWW's correctness depends entirely on the clocks, not the writes.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.
+5 instead of +30 and predict the winner; then +11. Where exactly does "C" start surviving?lww_resolve with a resolver that returns both values as siblings whenever their real times differ by less than the clock uncertainty. Predict what the skewed scenario now reports — and what new burden that puts on the application.ts for a Lamport logical clock (counter, node-id tiebreak) that increments on each event a node sees. Predict whether it recovers the causal order here, and what it still can't tell you about two truly concurrent writes.Sources: DDIA 2e, Ch. 6, §"Last write wins (discarding concurrent writes)", §"Handling write conflicts" · Ch. 9, §"Unreliable clocks".