Time, Clocks & Ordering — Lamport, Vector Clocks, Hybrid Logical Clocks & TrueTime
Time, Clocks & Ordering — Lamport, Vector Clocks, Hybrid Logical Clocks & TrueTime
Almost every hard problem in distributed systems reduces to one question: in what order did these events happen? Which write is newer? Did this read observe that write? Was the lock released before it was re-acquired? On a single machine you answer with a clock. Across machines, the naive move — stamp each event with the local wall-clock time and sort — is wrong, and it fails silently. This page builds ordering from first principles: why physical time betrays you, what logical time captures instead, and the ladder of clocks (Lamport → vector → hybrid logical → TrueTime) that trades metadata, hardware, and latency for stronger ordering guarantees.
Mechanism first: why you cannot order distributed events by wall-clock time
A wall clock (the "time of day", NTP-disciplined) is an estimate of a global reference, and every machine's estimate drifts differently. Three concrete failure modes, all real:
- NTP skew. Even well-synchronized nodes disagree by single-digit to tens of milliseconds; a badly-synced or freshly-booted node can be off by seconds. Two events milliseconds apart on different hosts can carry timestamps in the wrong relative order.
- Non-monotonic jumps. When NTP corrects drift it can step the clock backwards. An event that happened later can get a smaller timestamp than one that happened earlier on the same machine.
- Leap seconds. A UTC leap second repeats or stretches a second; historically this crashed production fleets. Timestamps are not a clean monotonic real line.
The consequence: a timestamp tells you roughly when an event happened, but comparing timestamps from different machines does not reliably tell you which happened first. Yet order is exactly what we need. So we separate two ideas that wall-clock time conflates: physical time (how long ago, for humans and TTLs) and causality (what could have influenced what). Ordering correctness comes from capturing causality — logical time — not from reading a better clock.
Two clocks live in every machine: wall-clock vs monotonic
Before leaving the single machine, internalize a rule that trips up even senior engineers: never measure elapsed time with the wall clock. Operating systems expose two clocks:
- Wall-clock (
System.currentTimeMillis(),time.Now()): calendar time, NTP-disciplined, and therefore can jump backwards or forwards. Use it only for "what time is it" — logging, TTLs, human-facing timestamps. - Monotonic (
System.nanoTime(),time.Now()'s monotonic reading,CLOCK_MONOTONIC): a counter that only ever increases and is immune to NTP steps. Use it for durations, timeouts, rate limiters, and benchmarks.end - starton a wall clock can come out negative.
The LWW footgun, grounded in this. "Last-Write-Wins" conflict resolution keeps the write with the largest wall-clock timestamp. Under clock skew, node B's clock runs 200 ms ahead; a write on A that truly happened later carries a smaller timestamp and is silently discarded — a lost update with no error, no log, no alarm. This is the concrete reason Cassandra's LWW is dangerous under contention, and the reason systems reach for the logical clocks below. LWW is not "resolve by time"; it is "resolve by whichever machine's clock is fastest".
Lamport clocks: a total order consistent with causality
Lamport's 1978 insight: forget synchronizing physical clocks; just count. Give each process an integer counter
C and follow three rules:
- Local event or send:
C = C + 1(then attachCto any message sent). - Receive a message carrying
C_msg:C = max(C, C_msg) + 1.
This guarantees the clock condition: if event a happened-before
b (written a → b — same process in order, or a send and its matching receive, or
transitively), then C(a) < C(b). Break ties by process id and you get a total order
that never contradicts causality — enough to build a replicated state machine or a mutual-exclusion protocol.
The crucial limitation, and the classic interview trap: the implication runs one way only.
C(a) < C(b) does not imply a → b. Two causally-unrelated
(concurrent) events can have any counter relationship. A Lamport clock can order everything, but it cannot
tell you whether the order is real causality or an arbitrary tiebreak — so it cannot detect conflicts. The
diagram below traces this on three processes.
Trace it: on receive of m1, P2 sets C = max(0, 2) + 1 = 3. Event e on P3
gets L=1 — the same Lamport value as a, and less than b's
L=2 — even though e is causally unrelated to both. Lamport happily orders e
before b, but that "order" is fiction. To detect that they are concurrent you need the vector.
Vector clocks: detecting concurrency, not just ordering it
A vector clock replaces the single counter with a vector of length N (one entry per process), so it
records what each process knew, not just a scalar. Rules:
- Local event on process
i:V[i] = V[i] + 1. - Send: increment
V[i], attach the whole vector. - Receive
V_msgon processi:V[k] = max(V[k], V_msg[k])for allk, thenV[i] = V[i] + 1.
Now comparison is component-wise. V(a) ≤ V(b) iff every component of
a is ≤ the corresponding component of b. Then:
V(a) < V(b)(≤ everywhere, < somewhere) ⇒a → b(a truly happened-before b).- Neither
V(a) ≤ V(b)norV(b) ≤ V(a)⇒aandbare concurrent — a genuine conflict.
In the diagram, b = [2,0,0] and e = [0,0,1]: b is bigger in position 1,
e is bigger in position 3 — neither dominates, so they are provably concurrent. That is the
capability Lamport lacks. This is why Amazon Dynamo and Riak attach vector clocks to each object:
on read they can tell a clean overwrite (one version dominates) from a true conflict (concurrent versions), and hand
concurrent siblings to the application to merge — rather than silently losing one, LWW-style.
The cost is real: metadata is O(N) per value, and N = number of writers, which grows
with clients over time. Dynamo prunes the oldest vector entries past a threshold, trading a small chance of a false
"no conflict" for bounded size. Version vectors (per-replica rather than per-client) keep N small when
only replicas mutate. The vector-size problem is the standing objection to vector clocks in the large.
Hybrid Logical Clocks (HLC): logical causality that also tracks the wall
Vector clocks are heavy and their values are meaningless as timestamps. Lamport counters are light but tell you
nothing about when. HLC (Kulkarni et al., 2014) fuses both into a single compact value
(pt, l): a physical-time part pt and a small logical counter l. On each event
it takes max of the local physical clock and the incoming HLC's physical part; if they tie it bumps the
logical counter l; otherwise l resets. The result has three properties at once:
- Captures causality like a Lamport clock —
a → b ⇒ HLC(a) < HLC(b)— even when the underlying physical clocks are skewed. - Tracks physical time — the value stays close to real wall-clock time, so it is meaningful for humans, TTLs, and range scans, and is monotonic by construction (no backward jumps).
- Bounded divergence — an HLC value never drifts from physical time by more than the clock skew bound, and the logical counter stays small (it only grows during ties, i.e. bursts within one clock tick).
It fits in ~64 bits, needs no special hardware, and gives a causal, near-real timestamp. CockroachDB and MongoDB use HLC to order transactions and events across a cluster. HLC is the pragmatic default when you want causal ordering plus meaningful timestamps but cannot deploy atomic clocks.
Spanner TrueTime: external consistency by waiting out the uncertainty
Everything above gives ordering relative to observed messages. Spanner wants something stronger —
external consistency (a.k.a. linearizability across the entire database): if transaction
T1 commits before T2 starts in real time, then T1's commit timestamp
< T2's, globally, for any keys, with no per-key leader coordinating them.
You cannot get that from logical clocks alone, and you cannot get it from ordinary wall clocks (they lie). Spanner's
move is to make the clock honest about its own error.
TrueTime is an API backed by GPS receivers and atomic clocks in every datacenter. Instead of
returning a single instant, TT.now() returns an interval
[earliest, latest] and guarantees the true current time lies somewhere inside it. The width is
2ε (typically a few milliseconds); ε is the clock uncertainty, and TrueTime
knows it because it measures drift against the reference hardware. The clock no longer claims to know the
time — it claims a bound on how wrong it might be.
The commit-wait trick, precisely: to commit a read-write transaction, Spanner picks a commit
timestamp s = TT.now().latest (the newest instant the true time could possibly be), then does not
release locks or acknowledge the client until TT.now().earliest > s — a deliberate wait of
≈ 2ε in expectation, not ε: s = TT.now().latest already sits
~ε ahead of true time at pick, and the release test TT.now().earliest > s demands true
time get ~ε past s — two ε-sized gaps, one wait. The Spanner paper states it plainly:
"the expected wait is at least 2·ε̄" (§4.1.2), with ε̄ ≈ 4 ms in the published
deployment ⇒ ≈ 8 ms of commit latency. Why this works: after the wait, the earliest possible current time is already
past s, so s is guaranteed to be in the real past by the time anyone can observe
the commit. Any transaction that starts after this one's client reply will read a fresh
TT.now() whose value exceeds s — so its timestamp is strictly larger. Timestamp order can
never contradict real-time order. That is external consistency, purchased with a few milliseconds of latency per
commit instead of a global coordinator. Smaller ε (better clocks) directly means shorter waits —
which is why Google invests in the GPS+atomic hardware. The uncertainty didn't disappear; Spanner paid it off in
wall-clock latency.
Selection & trade-offs: which clock, and when a logical clock is enough
The governing question is what kind of order you need and what you'll pay for it (metadata size, special hardware, commit latency). A logical clock is enough whenever ordering only needs to respect observed causality — events connected by messages you actually sent. You need a synchronized physical clock only when you must order events that never communicated, against real (external) time — e.g. "any read after this commit, anywhere, must see it."
| Approach | What it buys | What it costs | Reach for it when |
|---|---|---|---|
| No clock — consensus log order (Raft/Paxos) | A single agreed total order for one group; the strongest, simplest correctness | A leader / quorum round-trip per op; order exists only within the one replicated log | You already funnel writes through one consensus group (config store, single-shard KV). |
| Lamport clock | Cheap (one int) total order consistent with causality | Cannot distinguish causal from concurrent ⇒ cannot detect conflicts | You need a deterministic tiebreak / state-machine order and conflicts are impossible or handled elsewhere. |
| Vector clock | Detects concurrency ⇒ real conflict detection | O(N) metadata per value; grows with writers; needs pruning | Multi-master / AP stores that must surface conflicts to merge (Dynamo, Riak, CRDT causality). |
| Hybrid Logical Clock | Causal order and a monotonic, meaningful, wall-close timestamp; ~64 bits | Divergence bounded by (but dependent on) clock skew; not externally consistent by itself | Cluster-wide event/txn ordering with human-usable timestamps, no special hardware (CockroachDB, MongoDB). |
| TrueTime + commit-wait | External consistency / global linearizability with no per-key leader | GPS+atomic clock hardware; commit latency ≈ 2ε (≈8 ms in the 2012 paper) on every write | You need strict-serializable, real-time-correct ordering across an entire multi-shard database (Spanner). |
Read the table as a ladder of ambition: order within one group (consensus) → order consistent with causality cheaply (Lamport) → detect concurrency (vector) → add meaningful physical time (HLC) → order against real time globally (TrueTime). You climb only as far as the guarantee your use case actually demands, because each rung adds cost.
Pitfalls
- Ordering by wall-clock timestamp. Cross-machine timestamps can invert real order; sorting or LWW-resolving by them loses writes silently under skew. The bug never throws.
- Assuming Lamport order implies causality.
C(a) < C(b)is nota → b. Do not infer "a caused b" or "these conflict" from Lamport values — you need a vector for that. - Vector clocks that grow unbounded. One entry per writer; over a service's lifetime the vector bloats. Prune old entries (accepting rare missed conflicts) or use per-replica version vectors.
- "NTP is good enough for correctness." NTP bounds drift on average, not worst case, and can step backwards. It is fine for logs and metrics, never for ordering that must be correct. If you need real-time ordering, you need measured uncertainty (TrueTime) or you avoid the requirement.
- Measuring durations with the wall clock. Timeouts, rate limiters, and benchmarks on
currentTimeMillis()can go negative or fire wrongly across an NTP step. Use the monotonic clock. - Not alarming on the clock itself. Skew and backward steps are silent failures, so make them loud: export measured NTP offset per host and alert when it exceeds your budget, and have any timestamp-ordered ID generator (Snowflake-style, or an HLC) refuse to issue rather than emit a smaller value when it detects the wall clock jumped backwards — better a brief write stall than a silently mis-ordered or duplicated ID.
Takeaways
- Order comes from causality, not from reading a better clock. Wall-clock time across machines is an estimate that can skew, jump backwards, and stretch — never sort or resolve conflicts by it.
- Lamport orders, vector clocks detect. Lamport gives a cheap causality-respecting total order but can't tell causal from concurrent; vector clocks compare component-wise to prove concurrency, at O(N) metadata.
- HLC is the pragmatic middle — causal order plus a monotonic, wall-close, ~64-bit timestamp with no special hardware; TrueTime buys full external consistency by exposing uncertainty as an interval and commit-waiting out the uncertainty (≈ 2ε expected), trading milliseconds of latency for a global real-time order with no per-key leader.
- Climb the ladder only as far as needed: consensus-log order → Lamport → vector → HLC → TrueTime, paying metadata, hardware, and latency as you go.
Re-authored for this guide; space-time and TrueTime diagrams hand-authored as SVG. Sources: Lamport, "Time, Clocks, and the Ordering of Events in a Distributed System" (CACM 1978); Fidge & Mattern (vector clocks, 1988); Kulkarni et al., "Logical Physical Clocks" / HLC (2014); Corbett et al., "Spanner: Google's Globally-Distributed Database" (OSDI 2012) for TrueTime and commit-wait; Kleppmann, DDIA ch. 8–9. See also: The Consistency Spectrum, CAP/PACELC, Quorum, Replication Lag & Failover. On-ramp: Time, Clocks & Ordering — Why You Can't Trust the Wall Clock (the LWW failure trace and the which-clock-for-which-problem decision table).
🤖 Don't fully get this? Learn it with Claude
Stuck on Time, Clocks & Ordering — Lamport, Vector Clocks, Hybrid Logical Clocks & TrueTime? Open Claude, copy a block below, and it'll teach you this exact concept — visually and interactively.
Build the mental picture, not memorization.
I just read a lesson on **Time, Clocks & Ordering — Lamport, Vector Clocks, Hybrid Logical Clocks & TrueTime** (System Design) and want to truly understand it. Explain Time, Clocks & Ordering — Lamport, Vector Clocks, Hybrid Logical Clocks & TrueTime from first principles using ONE vivid real-world analogy and a visual mental model — draw it as ASCII art or a clear step-by-step diagram — with a concrete example using real numbers. Then ask me one question to check I got the mental picture, and wait for my reply. If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.
Socratic — adapts to where you're stuck.
Teach me **Time, Clocks & Ordering — Lamport, Vector Clocks, Hybrid Logical Clocks & TrueTime** interactively. Ask me ONE guiding question at a time, wait for my answer, and adapt to my confusion — build the idea with me step by step instead of explaining it all at once. If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.
Active recall exposes what you missed.
Quiz me on **Time, Clocks & Ordering — Lamport, Vector Clocks, Hybrid Logical Clocks & TrueTime** with 5 questions, easy to tricky, ONE at a time. Tell me if each answer is right; at the end, explain clearly what I got wrong and why. If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.
Intuition + hook + flashcards for long-term memory.
Help me remember **Time, Clocks & Ordering — Lamport, Vector Clocks, Hybrid Logical Clocks & TrueTime** for the long term: give the one-sentence intuition, a memorable hook/mnemonic, a tiny worked example, and 3 active-recall flashcards (Q -> A). If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.