Module 07 · Lesson 03

Clocks lie, so order events by sequence

Two machines with the same wall clock will disagree by more than you think.

Advanced 7 min read Updated 2026-11-02

What you will be able to do

  • Explain clock skew and why NTP does not solve it
  • Choose a conflict resolution rule for a replicated field
  • Use logical versions instead of timestamps for ordering
  • Design an event ordering scheme that survives a failover

The skew problem

NTP keeps clocks within a few milliseconds of each other on a healthy network, which sounds acceptable until you notice that requests arrive in microseconds. Worse, a clock can step backwards when NTP corrects a drift, so a later write can carry an earlier timestamp. Two replicas can then apply writes in opposite orders and remain permanently different.

Typical NTP skew
0.1-10 ms
Worse under load or in containers
Worst-case observed
seconds
VM migration, suspended instances
Monotonic clock
per machine only
Good for timeouts, useless across nodes
Timestamp tie-break
node id
Deterministic, not meaningful
Never trust a timestamp for causality
If a client's clock is five minutes fast, its writes look like they came from the future and win every conflict until the skew is corrected. That is a correctness bug with a plausible-sounding rationale.

What to use instead

MechanismGuaranteesCost
Per-key version numberStrict order per key, compare-and-setRequires single writer or consensus per key
Logical clock (Lamport)If A caused B, A sorts before BOnly a partial order; concurrent writes still tie
Hybrid logical clockCausality plus near-wall-clock readingMore state, still needs tie-break
Sequence from a log offsetTotal order per partitionOrder only inside the partition
Vector clockDetects concurrencyGrows with the number of writers

For most designs the answer is embarrassingly simple: partition by entity and keep a version counter incremented by the single owner of that partition. Order within the entity is then a number, not a guess about time.

Conflict resolution

Avoid the conflict
Route all writes for one entity to one partition or one leader. Most conflicts disappear before resolution is needed.
Make writes commutative
Counters, sets and append-only lists merge without a winner. Design the data so order does not matter.
Compare versions
Reject a write whose base version is stale, and return the current value so the client can retry with intent.
Pick a deterministic rule
If truly concurrent writes must resolve, choose by version then node id, and accept that one update is discarded.

Last-write-wins is fine for settings where the newest intent should win — a profile's theme colour. It is wrong for a balance or an inventory count, where discarding an update loses money. The rule is about the data, not the database.

Ordering across a failover

A single-writer stream is ordered until the writer dies. On failover, the new writer must continue from the committed offset, not from its own counter, or the sequence repeats. This is why log offsets beat application-assigned ids inside a stream, and why idempotent consumers matter even when ordering is guaranteed.

One precise sentence
"I don't rely on timestamps for ordering. Each entity has a version, and a write must carry the version it read; a stale version is rejected. Cross-entity order comes from the partition offset."

Worth remembering

  • Wall clocks jump backwards; they are not monotonic and not comparable across machines.
  • Last-write-wins is a data-loss policy unless the write is already idempotent and full-state.
  • Per-entity sequence numbers give ordering without any clock agreement.
  • When two writes are truly concurrent, you must pick a rule — timestamp, version, or merge.

Checkpoint

Answer all 3 questions to finish the lesson.
1. Why is last-write-wins by wall clock unsafe?
Timestamps from different machines are not comparable at the precision writes arrive. The loser is decided by skew, not by intent.
2. You need strict ordering of events for one order id. What is the most direct mechanism?
One writer per key incrementing a sequence gives a total order for that key with no clock agreement.
3. For which data is last-write-wins acceptable?
A preference is full-state and idempotent: the newest intent is the correct value. The others lose information when an update is discarded.