The question this answers
Why can I not just compare timestamps to work out which event happened first?
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.
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.
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 realityWhat 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.
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.
- • 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.
- • 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.
- • 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.
- • 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.
- • 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.
- • 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.
- • 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.
- • 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.
- • 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.
- • 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
What people believe, and what is true
NTP keeps our clocks in sync, so timestamps are fine.
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.
Last-write-wins keeps the most recent write.
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.
A monotonic clock fixes ordering across machines.
It fixes duration measurement on one machine. Two monotonic clocks have unrelated origins and cannot be compared at all.
Causality gives me a total order.
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
- ⚡ 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.
- 💬 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?