Every earlier article in this module has depended, implicitly, on nodes agreeing about the order events happened in — which write came first, whether a heartbeat is recent or stale. This article covers why the obvious tool for that, each machine's own clock, does not actually work.
Wall-clock time drifts, and syncing it is never instant#
Every machine's clock runs at a very slightly different rate, and Network Time Protocol synchronization corrects for this only periodically, not continuously — clock synchronization across a fleet of machines is an approximation, not a guarantee, and the error is rarely zero:
Node A's clock: 14:00:00.000 -> writes event XNode B's clock: 13:59:59.998 -> writes event Y, 50ms after X actually happened
Comparing timestamps: X (14:00:00.000) < Y (13:59:59.998) is FALSE —Y's timestamp is smaller, even though Y happened after X.Sorting these two events by their recorded timestamp gives the wrong answer — not because of a bug, but because the two clocks that produced those timestamps were never perfectly aligned to begin with, and no realistic amount of NTP correction guarantees they are close enough for sub-second ordering to be trustworthy.
Lamport timestamps: a logical clock instead of a physical one#
Lamport timestamps sidestep the physical-clock problem by not trying to measure time at all — just track a counter, and use message-passing itself to keep it consistent:
Each node keeps a counter, starting at 0.
On any local event: increment the counter.
On sending a message: attach the current counter value.
On receiving a message with counter value C: set local counter = max(local counter, C) + 1This produces a value where, if event A actually caused event B (B happened after receiving a message sent because of A), A's timestamp is guaranteed smaller than B's. That guarantee — called happened-before — is exactly what wall-clock time could not reliably provide.
A Lamport timestamp orders causally related events — not all events
Two events with no causal relationship — neither one sent a message the other's clock ever incorporated — can end up with either timestamp order, arbitrarily. Lamport timestamps guarantee correct order for events that are actually related; they do not claim to reveal a "true" order for events that are genuinely independent, because no such order exists.
Vector clocks: telling concurrent apart from ordered#
A Lamport timestamp is a single number, which cannot distinguish "these two events are unrelated" from "one genuinely came first." A vector clock — one counter per node, incremented locally and merged (via element-wise maximum) on every message — fixes this: comparing two vector clocks reveals whether one happened strictly before the other, or whether they are genuinely concurrent — causally unrelated, not just close in wall-clock time. This is exactly the mechanism Amazon's Dynamo uses, referenced in Replication and Consistency Models, to detect when two clients genuinely wrote conflicting versions of the same value concurrently, rather than one simply overwriting the other.
The simpler alternative: don't solve global ordering at all#
Topics, Partitions, and Offsets covers Kafka's own answer to this whole problem: sidestep it. Kafka guarantees order only within a single partition, using a simple monotonically increasing offset assigned by the partition's leader — no wall-clock comparison, no vector clock, no cross-partition ordering promise at all. This is a deliberate, narrower guarantee: cheap to implement and reason about, in exchange for never claiming to order events that happened on different partitions relative to each other.
Common mistakes#
Sorting events from different machines by wall-clock timestamp#
This looks correct in testing, where clock drift is usually small and network delay is usually short, and fails exactly when it matters most — under load, across data centers, or after any prolonged clock drift a missed NTP sync can cause.
Treating "concurrent" as meaning "simultaneous"#
Concurrent, in the vector-clock sense, means no causal relationship could be established — it says nothing about whether the two events happened at the same wall-clock instant. Two events an hour apart in real time can still be concurrent if neither one's effects ever reached the other.
Reaching for vector clocks when a simpler ordering guarantee would do#
Kafka's per-partition offset solves the ordering problem for a huge share of real pipelines without any of a vector clock's bookkeeping overhead. Vector clocks earn their complexity specifically when genuinely concurrent writes to the same logical value need to be detected and reconciled — not as a default choice.
Further reading#
- Lamport timestamp and Vector clock — the two logical-clock mechanisms covered above, in full.
- Clock synchronization and Network Time Protocol — why physical clocks drift in the first place, and the limits of correcting for it.
- Topics, Partitions, and Offsets — the narrower, offset-based ordering guarantee real systems often use instead.