Prompt and scope
Design cross-node version stamps for a three-region key-value store. Each node has only a local wall clock, with an assumed maximum skew of 50 milliseconds. The network can delay, retry, and reorder messages, and a node clock can move backward. Writes need a comparable version for MVCC, audit ordering, and conflict diagnosis.
This fits distributed-storage, database, infrastructure, and system-design interviews. An HLC is a pair (physical, logical): the physical part stays near wall time, while the logical part advances when physical time does not move or when a newer remote stamp is observed. The problem does not ask you to infer the real-world order of concurrent events and does not give you a TrueTime-style hardware time bound.
What the interviewer is testing
The interviewer wants guarantees before components:
- A strong answer says HLC preserves causal order, local monotonicity, and proximity to physical time; it does not claim global real-time order or a conflict-free total order.
- A strong answer gives update invariants for local and receive events instead of repeating “physical time plus a counter.”
- A strong answer carries the maximum clock skew
εinto reads and explains why MVCC can retry, rather than generating stamps only on writes. - A strong answer compares vector clocks and TrueTime and says HLC does not replace consensus, uniqueness constraints, or application conflict resolution.
A weak answer only takes the maximum of two machine clocks. That misses message causality, clock rollback, logical-counter overflow, and the uncertainty interval.
Clarifications before answering
- What must the stamp guarantee? HLC is enough to order MVCC versions per key; external-consistency commit order across regions needs consensus or a bounded-time service.
- Is
50milliseconds a hard bound or an observed metric? Only a hard bound can safely defineε; an estimate is useful for alerts and conservative retries. - Can reads cross replicas, and may they retry? Cross-replica reads should carry a read timestamp and uncertainty bound; if retries are forbidden, the guarantee or coordination round must change.
- How are concurrent writes merged? HLC makes timestamps comparable, but the application still needs conditional writes, vector context, or an explicit merge rule.
30-second answer framework
“I would keep (p,l) on every node. p is the largest physical time observed, and l breaks ties within that physical time. For a local event, use max(now,p), reset the logical part when physical time advances, otherwise increment it. On a remote stamp, take the maximum of local, remote, and current physical components, then increment the logical part whenever multiple sources share that maximum. Causal messages therefore move HLC forward while the value remains close to wall time. For MVCC, turn the skew bound ε into an uncertainty window; a version inside that window requires a retry or a higher read timestamp. HLC does not prove the real order of concurrent events and does not replace consensus or conflict merging.”
Step-by-step deep answer
1. State the invariants first
Each node maintains T=(p,l), compared by p first and l second. The design needs three invariants:
pis at least the wall time and remote physical component the node has observed.- Consecutive events emitted by one node have strictly increasing stamps.
- If event A's stamp is carried to event B, B's stamp is strictly greater.
The HLC paper describes this as retaining causal information while staying close to physical time. Martin Fowler's pattern also models a hybrid timestamp as physical time plus a logical counter.
2. Update a local event
Let now be the current physical time and (p,l) the old stamp:
if now > p:
p = now
l = 0
else:
l = l + 1If the wall clock moves backward, p does not move backward and the logical part keeps growing. The implementation must detect a counter near its limit; silent wraparound would reverse comparisons. The paper shows that HLC can use fixed-width storage, but the width still needs validation against clock resolution, allowed drift, and event rate.
3. Update after receiving a remote stamp
For remote R=(rp,rl), compute q=max(now,p,rp), then choose the logical component based on which source reaches that maximum:
if q == now and q > p and q > rp:
(p, l) = (q, 0)
else if q == p and q == rp:
(p, l) = (q, max(l, rl) + 1)
else if q == p:
(p, l) = (q, l + 1)
else:
(p, l) = (q, rl + 1)The important invariant is not the syntax: the maximum physical component never retreats, and when local and remote values tie for the maximum, the logical component exceeds both. Attach the current HLC to an outgoing message or transaction context; the receiver updates its clock before stamping its own event. Reordered older messages therefore cannot lower an already observed causal timestamp.
4. Use HLC for MVCC versions
An MVCC write can use its HLC as the version. A read transaction starts at t and keeps t+ε as an uncertainty bound, where ε is the maximum physical clock skew allowed by the cluster. If it sees a version v after t and no later than t+ε, it cannot tell whether that version committed before the read or came from a fast clock. A safe implementation waits, advances the read timestamp, or restarts. CockroachDB's transaction-layer documentation describes HLC's physical and logical components and this uncertainty-retry behavior.
This turns synchronization error into observable retry cost. Monitor ε, uncertainty-retry rate, and logical-counter growth instead of looking only at average latency.
5. Compare alternatives
- Vector clocks identify concurrency, but metadata grows with the participant set; they fit systems that need explicit conflict detection with a small replica set.
- HLC uses fixed-width physical-plus-logical stamps for MVCC, audit, and ordering. It cannot prove that two concurrent events are unrelated, and it cannot complete a global commit protocol by itself.
- TrueTime-like bounded-time services expose time intervals with an error bound and can support stronger external consistency; they require specialized clock infrastructure or commit waiting.
The decision rule is: choose HLC for low metadata, near-physical timestamps, and comparable versions; retain vector context when concurrency must be detected exactly; add consensus or a bounded-time service for external consistency.
6. Failure cases and verification
- Physical rollback: inject a backward jump and verify
pnever decreases and stamps remain increasing. - Remote reordering: deliver a larger stamp and then a smaller one; the latter must not lower local state.
- Logical growth: freeze physical time and generate events rapidly; verify a protection path before overflow.
- Skew above
ε: inject clock drift and verify startup rejection, read-only degradation, or visible retries instead of silent consistency claims. - MVCC retry storm: record window-hit rate, retry counts, and node distribution to separate true conflicts from clock skew.
High-quality sample answer
“I would separate clock guarantees from storage guarantees. The clock keeps (p,l), where p is the greatest physical time observed and l advances when physical time does not advance or when a remote stamp has the same maximum physical component. Every outbound message carries the HLC. The receiver takes the maximum physical component of local, remote, and current time, then makes the logical component greater than every source at that maximum. A causal chain therefore gets strictly increasing stamps even if a wall clock moves backward.
“For MVCC, a read transaction has a start timestamp t and skew bound ε. Seeing a version between t and t+ε is ambiguous, so I retry or move the read timestamp forward. That turns clock error into an explicit retry cost; I monitor skew, logical counters, and window hits. HLC is useful for low-metadata version ordering, but concurrent events can still receive an arbitrary comparable order. It does not provide vector-clock concurrency detection or the external-consistency guarantee of consensus or TrueTime.”
Common mistakes
- Mistake → overwrite the local stamp with
now→ a clock rollback moves versions backward → retain the maximum physical component and increment logically. - Mistake → keep only the maximum remote physical time → causal order at the same physical time is lost → increment beyond both local and remote logical values on a tie.
- Mistake → claim HLC identifies every concurrent relationship → one scalar comparison cannot prove “concurrent” → carry vector or explicit causal context for conflict detection.
- Mistake → ignore a future-looking version → it may have existed before the read under clock skew → use the
εwindow and retry or advance the read timestamp. - Mistake → omit skew monitoring → retry storms look like database conflicts → record per-node skew, window hits, and logical-counter growth.
Follow-ups and responses
If two concurrent writes have comparable HLC values, which one wins?
HLC supplies an ordering key, not the real-world order. If last-writer-wins is acceptable, define a deterministic (HLC, node-id) tie-breaker. If concurrent edits must not be lost, retain multiple versions or carry vector context for an application merge. State clearly that this is a conflict policy, not HLC's causal proof.
What if maximum skew grows from 50 milliseconds to 2 seconds?
Stop treating the old ε as safe, isolate the drifting node, and repair time synchronization. Increasing ε raises MVCC uncertainty retries; shrinking it risks reading the wrong version. If the bound cannot be restored, pause writes, degrade to read-only, or add stronger coordination. The threshold, alert, and recovery action belong in the operating policy.
How do you prevent a logical counter from growing without bound at high throughput?
Limit events per physical tick, use a sufficiently wide integer, and alert near the limit. You can wait for physical time to advance, increase time resolution, or reject writes; truncating the counter would break monotonicity. A stress test should freeze now and exercise the pre-overflow protection path.
Why not use a database auto-increment sequence directly?
A single sequence gives a total order, but cross-region writes must synchronously reach the coordinator, adding latency and reducing availability. HLC lets nodes generate near-time stamps locally for version ordering and causal hints. When strict global commit order is required, use a consensus sequence, TrueTime, or equivalent coordination.