Concrete scenario
Node A sends a message to Node B, but B has an earlier wall-clock reading when it acts on the message.
Mechanics
Wall clocks represent calendar time and may be stepped or slewed by synchronization; monotonic clocks are local tools for elapsed durations but are not comparable across machines. Lamport clocks guarantee that causally prior events receive lower timestamps, but the reverse implication is false. Vector clocks can distinguish causal order from concurrency at metadata cost.
Failure mode
Last-write-wins using unsynchronized wall time can discard a causally later update. A timeout measured on a wall clock can be distorted by a clock adjustment, and vector metadata can grow with participants.
Trade-off
Physical time is useful for user timestamps and retention; logical time is useful for ordering. Hybrid logical clocks combine both but still require defined uncertainty, tie-breaking, and application conflict semantics.
Worked example: Lamport versus vector clocks
Three nodes, A, B and C, each start with a counter of 0.
- A performs a local event: Lamport A = 1. Vector A = [1,0,0].
- A sends m1 to B: A = 2, and m1 carries 2 (vector [2,0,0]).
- C performs a local event independently: C = 1, vector [0,0,1].
- B receives m1: B = max(0,2)+1 = 3. Vector B = [2,1,0].
- C sends m2 to B carrying 2 (vector [0,0,2]). B receives: B = max(3,2)+1 = 4. Vector B = [2,2,2].
Compare A's send event (Lamport 2, vector [2,0,0]) with C's first event (Lamport 1, vector [0,0,1]). Lamport suggests C's event came first, but the vectors are incomparable (neither is less than or equal to the other in every component), so the events are really concurrent. Only vector clocks reveal that.
Clock types
| Clock | Answers | Size | Detects concurrency | Used by |
|---|---|---|---|---|
| Wall clock (NTP) | Calendar time, plus or minus a few ms (worse under load or skew) | 8 bytes | No | Logs, TTLs, user-facing timestamps |
| Monotonic | Elapsed time on one host | 8 bytes | Not applicable | Timeouts, latency metrics |
| Lamport | A total order consistent with causality | One integer | No | Log ordering, Raft terms (similar idea) |
| Vector / version vector | Exact causality | O(nodes) | Yes | Riak, Dynamo, CRDT libraries |
| Hybrid logical clock (HLC) | Causality plus closeness to physical time | Physical time plus a counter | No | CockroachDB, YugabyteDB, MongoDB cluster time |
| TrueTime | A bounded interval [earliest, latest] | Two timestamps | No, but supports external consistency | Google Spanner, which waits out the uncertainty, typically under 7 ms |
How real systems order events
- Spanner uses GPS and atomic clocks to keep uncertainty small, and "commit-waits" until the timestamp is guaranteed to be in the past everywhere.
- CockroachDB uses HLC with a maximum offset (default 500 ms). A read that finds a value inside its uncertainty window restarts at a higher timestamp.
- Snowflake IDs (Twitter) combine milliseconds, a machine ID and a sequence number. They are roughly time-sortable, but they do not capture causality across machines.