Fundamentals

There Is No Global Clock

Two machines cannot agree on what time it is closely enough to order events by timestamp. Which means a comparison of two timestamps from two hosts is not an ordering — it is a guess, and it is wrong in exactly the cases you built it to handle.

▶ Run the lab

The question this answers

The question

Why can I not just compare timestamps to work out which event happened first?

The guarantee — the property claimed, and its scope

None from wall-clock timestamps across hosts. Two events on different machines can be ordered reliably only if a causal chain of messages connects them; otherwise they are concurrent and no timestamp comparison makes them otherwise.

Everything below is bought to hold this sentence. "Strongly consistent" with no scope attached is a slogan, not a guarantee — read what it actually covers, and what it explicitly does not.

What a node knows — observation versus inference

A node knows its own clock reading and the order of events it processed locally. It does not know the offset between its clock and any other node’s, and it cannot measure that offset exactly — only bound it, and only under assumptions about network symmetry it has no way to verify.

A node knows its own state and the messages that arrived. Everything else is inference from evidence that was already stale. "B has not replied in five seconds" is knowledge; "B is down" is a decision — and usually the bug.

What guarantee?What does a node know?How does it work?What can fail?How does it fail?Where is coordination?What holds under failure?How does it recover?How would you know?What is the simpler thing?
clocksorderingskewcausality

Why the offset is unmeasurable

The natural fix is to synchronise clocks, and NTP does exactly that: a node measures the round trip to a time server and splits the difference. The split assumes the path is symmetric — that the request took as long as the response. Under load, or across an asymmetric route, it does not, and the error is half the asymmetry. That is why practical synchronisation gives you *bounds* of a few milliseconds on a well-run network and occasional excursions of far more, rather than an exact offset.

Then there is drift: a quartz oscillator runs fast or slow by tens of parts per million, so an unsynchronised clock accumulates error continuously. And there is correction: when the daemon notices the error, it either steps the clock — which can move it backwards — or slews it, which makes the clock temporarily run at the wrong rate. Both of those break code that assumed time is monotonic, which is why Never Measure a Duration With the Wall Clock is a separate lesson and why measuring a duration with a wall clock is a bug.

The tightest available answer is a clock that reports an *interval* rather than an instant — "now is somewhere in [t₁, t₂]" — using disciplined hardware. That does not eliminate uncertainty; it makes it explicit, so a system can wait out the interval when it needs certainty and skip the wait when it does not. Paying real latency to buy an ordering guarantee is the honest shape of the trade.

host-a  ntp offset  +0.4ms   drift  -12ppm    last step: none
host-b  ntp offset  -3.1ms   drift  +38ppm    last step: -1.2s, 4 min ago
host-c  ntp offset  ?        drift  ?         daemon dead since Tuesday

event written at host-a  2026-08-25T10:14:02.481Z
event written at host-b  2026-08-25T10:14:02.478Z   <- earlier timestamp
                                                       later in reality
Two hosts, same instant, three different opinions

What a timestamp comparison actually asserts

When you write if (a.ts > b.ts) for events from different hosts, you are asserting that the clock difference between those hosts is smaller than the time between the events. For events seconds apart on a healthy network, that assertion usually holds. For events milliseconds apart — which is precisely when the ordering question arises — it frequently does not.

This is the flaw at the heart of last-write-wins conflict resolution, and it is worth stating in its most uncomfortable form: last-write-wins does not keep the last write. It keeps the write from the host with the furthest-ahead clock. A node whose clock is 200ms fast wins every conflict for 200ms of real time, including against writes that genuinely came later and even against writes that causally *depend* on data it has not seen. Last Write Wins Is Data Loss You Chose by Default does this properly; the point here is that the flaw is in the clock, not in the merge rule.

Worse, the failure is silent. Nothing errors. The data converges, the metrics are clean, and one user’s change disappeared. This class of bug is found by customers, not by monitoring, which is why the reasoning has to happen at design time.

The clock says B was first. The messages say otherwise.assumption
Host A (clock +0ms)Host B (clock +250ms fast)replicate X=1: deliveredreplicate X=1replicate X=2 (ts is earlier!): deliveredreplicate X=2 (ts is earlier!)write X=1 (ts 10:00:00.100) (write) at t=1write X=1 (ts 10:00:00.100)read X=1, then write X=2 (ts 10:00:00.050) (write) at t=5read X=1, then write X=2 (ts 10:00:00.050)LWW compares timestamps, keeps X=1 (decide) at t=9LWW compares timestamps, keeps X=1t=1time →t=9
delivereddelayed (dashed, long)duplicated (×2)dropped — stops short, never arriveswritedecide
B read X=1 before writing X=2, so X=2 causally follows X=1 and must win. Its timestamp is earlier because B’s clock is fast — sorry, ahead — and last-write-wins therefore discards the newer value. No error is raised anywhere.

Causality is the ordering you can actually trust

If timestamps cannot order events, something must. The answer is the only thing nodes genuinely share: messages. If A sent a message and B received it, then everything A did before sending precedes everything B does after receiving. Chain those edges together and you get a partial order that is true by construction rather than by assumption — no clock required.

It is a *partial* order, and that is not a defect. Two events with no chain of messages between them are genuinely concurrent: no observer could have distinguished their order, and the system is free to choose. The mistake is inventing a total order where only a partial one exists, and then relying on it. Lamport clocks and version vectors are the mechanisms; Happens-Before: The Only Ordering You Actually Have and Vector Clocks: Buying Concurrency Detection at O(N) do them properly, and Concurrency’s happens-before is the same idea inside one machine, where the edges come from memory barriers instead of messages.

When you genuinely need a total order — a ledger, a replicated log, a sequence of commands every replica must apply identically — you do not get it from clocks. You get it from an agreement protocol that assigns positions, which is what Total Order Broadcast Is Consensus Wearing a Different Hat and the whole consensus module are for, and it costs a round trip to a majority for every entry.

Where clocks are still fine

None of this means timestamps are useless. They are the right tool whenever the question is human-facing rather than correctness-critical: displaying when something happened, expiring a cache, choosing a retention window, bucketing metrics. The rule of thumb is that a clock is fine for anything where being a few hundred milliseconds wrong is a cosmetic problem.

They are also fine, and necessary, for bounding things: a lease that expires, a token that becomes invalid, a deadline. Note the shape of those uses — they rely on elapsed time on one machine, not on comparing instants across machines, and they should be measured with a monotonic clock. The dangerous case is narrow and specific: using a wall-clock reading from one host to order or compare against a reading from another.

One caveat that catches people out: a lease is safe only if the granter and the holder agree about elapsed duration, which they do not exactly. Real lease protocols therefore have the holder treat its lease as expiring *earlier* than the granter does, so the gap absorbs the drift. That asymmetry is the practical form of "do not trust clocks across hosts" — see Leases: Authority With an Expiry Date.

  • Fine: displaying time, expiring caches, bucketing metrics, log retention.
  • Fine with care: leases and deadlines — measure elapsed time monotonically, and build in a margin.
  • Not fine: ordering writes across hosts, resolving conflicts, deciding which of two events came first.
  • Not fine: measuring a duration with a wall clock, which can move backwards.

Key points

  • Clock offset between hosts cannot be measured exactly, only bounded — and the bound assumes network symmetry.
  • Comparing timestamps from two hosts asserts the skew is smaller than the interval between events; near-simultaneous events are exactly where that fails.
  • Last-write-wins keeps the write from the fastest clock, not the latest write, and fails silently.
  • Causality — established by messages — is the ordering that holds without assumptions, and it is partial.
  • A total order requires agreement, and agreement costs a round trip to a majority.

The chain, answered

Every field here is required, which is why no lesson in this domain can recommend a design without naming what an operator sees when it fails, what survives the partition, what repairs it afterwards, and the simpler thing to consider first.

How it works
  • Each host runs an oscillator that drifts, and a daemon that periodically estimates the offset to a reference.
  • The estimate is computed from a round trip and assumes the two directions took equal time.
  • Correction is applied either as a step, which can move the clock backwards, or as a slew, which changes its rate.
  • Between corrections the error grows with drift; after a correction the clock may disagree with its own recent readings.
  • Any cross-host comparison inherits the sum of both hosts’ errors, which is unbounded when a daemon has failed.
What can fail at the boundary
  • The time daemon dies and no alert fires, because the host is otherwise healthy.
  • A leap second or a step correction moves the clock backwards, so a duration computes as negative.
  • A virtualised host is paused and resumed, waking with a clock far behind real time.
  • Asymmetric routing biases the offset estimate consistently in one direction.
  • A container inherits the host clock but not the correction, drifting independently of what monitoring reports.
How it fails — what an operator sees
  • Silent lost update under last-write-wins: a user’s edit disappears. The operator sees no error, correct-looking convergence, and a support ticket — the only symptom is the customer.
  • Negative durations in telemetry: latency histograms acquire impossible values after a clock step. The operator sees p99 drop to zero or a metric rejected as out of range.
  • Premature lease expiry: a holder with a fast clock releases early while the granter still considers the lease held, or a slow clock keeps acting past expiry. The operator sees two workers performing the same exclusive job.
  • Out-of-order log correlation: events across services sort incorrectly in the aggregated view, so the timeline shows an effect before its cause. The operator sees an incident narrative that appears to violate causality and wastes an hour on it.
Where coordination is required
  • No coordination is required to read a clock, and none is available to make two clocks agree exactly.
  • Establishing a causal order requires only the messages you were already sending, plus a counter — this is why causality is cheap.
  • Establishing a total order requires consensus, and paying for it is a deliberate choice made per-decision rather than per-system.
  • An interval clock lets you buy certainty by waiting out the uncertainty window — a direct exchange of latency for ordering.
What still holds under failure
  • Local monotonic time keeps working correctly regardless of wall-clock chaos.
  • Anything ordered by causal edges remains correctly ordered even when clocks are badly wrong.
  • Anything ordered by wall clock silently produces a plausible but incorrect order, with no failure signal.
How it recovers
  • Detect: monitor clock offset and time-daemon health per host as a first-class signal, not as an afterthought.
  • Contain: never let a single host’s timestamp decide a conflict; prefer version predicates or causal metadata.
  • Recover: after a clock incident, identify writes made during the bad window rather than assuming convergence fixed it.
  • Reconcile: where LWW discarded data, recovery means retrieving the lost version — which is only possible if you kept it. Systems that store siblings can; systems that overwrite cannot.
  • Verify: replay the causal graph rather than the timestamp order when reconstructing what happened.
How you would know
  • Per-host clock offset and drift, alerted on absolute value, with the time daemon’s liveness as a separate signal.
  • Count of computed durations that are negative or implausibly large — the cheapest detector of a clock step.
  • Conflict-resolution outcomes attributed by host: a host that wins disproportionately has a fast clock, not better data.
  • Skew between the timestamp a log line carries and the ingestion time at the collector, which surfaces drifting hosts without extra instrumentation.
When it helps
  • Whenever two events from different machines are compared, merged, sorted or deduplicated — which includes most log pipelines and every conflict resolution rule.
  • Especially in multi-leader or multi-region designs, where concurrent writes to the same key are normal rather than exceptional.
When it hurts
  • Adding causal metadata to data that only ever has one writer buys nothing and costs storage on every record.
  • Reaching for consensus-backed ordering when a per-key single owner would have provided the order for free.
Simpler alternatives
  • Route all writes for a key through one owner: the owner’s local order is a real order, and no clocks are involved.
  • Use a version counter per record and reject writes based on stale versions, so ordering is enforced rather than inferred.
  • Keep concurrent versions as siblings and let the application merge them, trading storage and complexity for never losing a write.
  • Use a data type that converges regardless of order, so the ordering question stops being asked.

Timestamp order versus the order that actually happened

Timestamp order versus the order that actually happened
e1 → e2 by a message, so that ordering is a fact. e3 is concurrent with both. Move the per-host offsets and watch the timestamps disagree with all of it.
by timestampe2 (90)e1 (100)e3 (200)
by causalitye1 → e2e3 ∥ e1e3 ∥ e2
The timestamps say e2 (90) is not later than e1 (100) — yet e2 is the receipt of a message e1 sent. This ordering is not merely unreliable, it is provably wrong, and no amount of NTP removes it: it only requires B’s clock to be behind A’s by more than the transit time.
Last-write-wins on key x
e1 wrote
"alice" @ 100
e3 wrote
"carol" @ 200
LWW keeps carol, because C wrote the higher number. It agrees with real time at these offsets — but nothing in the system checked that, and the two writes are concurrent, so there is no fact of the matter about which "should" win. Discarding one of them is a decision LWW makes for you, silently.
Three hosts, three oscillators. The label on each event is what that host wrote down.assumption
Host A (+0 ms)Host B (-90 ms)Host C (+60 ms)x = "alice": deliveredx = "alice"A: write x = "alice" — t=100 (write) at t=100A: write x = "alice" — t=100B: receives A’s message, writes y — t=90 (read) at t=180B: receives A’s message, writes y — t=90C: write x = "carol" — t=200 (write) at t=140C: write x = "carol" — t=200t=100time →t=180
delivereddelayed (dashed, long)duplicated (×2)dropped — stops short, never arriveswriteread
Position on this diagram is real time, which no node can observe. Each node only ever sees the numbers in the labels, and those come from three unrelated clocks.
A node knows its own clock reading. It cannot measure its offset against a peer — only against a time server, over a round trip whose two directions it assumes took equal time. Comparing two hosts’ timestamps inherits the sum of both errors. Causality is the ordering that needs no assumption: it is established by messages, it is partial, and the events it refuses to order are genuinely unordered.
assumptionOffsets here are held fixed; in reality they drift and are stepped, so the same pair of events can compare differently an hour apart. That a cross-host timestamp comparison asserts skew is smaller than the interval between the events is exact, and is the assumption nobody writes down.

What people believe, and what is true

Claim

NTP keeps our clocks in sync, so timestamps are fine.

Reality

NTP bounds the error under good conditions. The bound is milliseconds, the events you are ordering are milliseconds apart, and the bound does not hold when a daemon dies.

Claim

Last-write-wins keeps the most recent write.

Reality

It keeps the highest timestamp. On a host whose clock is ahead, that is not the most recent write, and the loser is deleted without a trace.

Claim

A monotonic clock fixes ordering across machines.

Reality

It fixes duration measurement on one machine. Two monotonic clocks have unrelated origins and cannot be compared at all.

Claim

Causality gives me a total order.

Reality

It gives a partial order. Concurrent events are genuinely unordered, and pretending otherwise is how you get an ordering that only holds when nothing interesting happens.

Go deeper

Only the levels this lesson can honestly fill — a missing level is a claim nobody had.

Overview

Clocks on different machines disagree by more than the gap between the events you want to order. Use causality — who told whom — not timestamps.

Practical

Audit for three patterns: comparing timestamps from different hosts to decide an order, measuring durations with a wall clock, and last-write-wins on any data a user would notice losing. Alert on clock offset per host. When you need order, get it from a single writer or a version predicate.

Advanced

The frontier is trading latency for ordering explicitly. Spanner-style designs use a clock that returns an uncertainty interval and commit-wait out that interval before acknowledging a write, which makes wall-clock timestamps genuinely orderable at the cost of a few milliseconds per commit and a dependency on the error bound being honest. Hybrid logical clocks take the cheaper route: a timestamp that is causally correct by construction and close to physical time when clocks behave, which gives you human-readable ordering without staking correctness on the hardware.

Apply it

Reason about this
  • A multi-region key-value store uses LWW. One region’s clock drifts 400ms ahead for an hour. Describe exactly what a user in the other region experiences, and what monitoring would have shown.
Interview questions
  • 💬 Why is comparing timestamps from two hosts unsafe, and when specifically does it fail?
  • 💬 Explain why last-write-wins can discard a write that causally depends on the one it keeps.
  • 💬 You need a total order over events from five services. What are your options and what does each cost?