Chapter 04 · Time, Ordering, Logical Clocks, and Versioning
Hybrid Logical Clocks and Practical Time-Aware Ordering
Combine physical-time structure with logical monotonicity using a Hybrid Logical Clock, while keeping clock uncertainty and protocol guarantees explicit.
Learning outcomes
Pure Lamport clocks give causal-compatible order but no approximate wall time. Raw wall clocks give human-meaningful time but can move backward or disagree. A Hybrid Logical Clock (HLC) combines a physical-time-derived component with a logical counter, allowing the clock to stay near physical time while maintaining the logical monotonicity needed for causality-aware protocols.
Explain the two-component HLC intuition: physical-like value plus logical counter.
Update an HLC on local events and message receipt so causally later events compare greater.
Show how an HLC remains monotonic through a simulated backward physical-clock input.
Distinguish HLC guarantees from perfect UTC, bounded uncertainty, linearizability, and concurrency detection.
Identify practical uses such as version/timestamp ordering and snapshot metadata while preserving clock-health monitoring.
1. Why hybridize logical and physical time?
Many systems need timestamps that are easy to compare with retention windows, logs, or time-based snapshots, yet also need local/message ordering to remain monotonic when physical clocks wobble. The HLC work by Kulkarni and colleagues addresses this gap. An HLC maintains a logical clock whose main component stays close to the node's physical/NTP clock and uses a counter to preserve ordering when physical time does not advance enough.
The simplified representation in this lesson is a pair
(l,c). The first component l tracks
the maximum relevant physical/logical time seen;
c orders events that share that
l value.
2. Local-event update
On a local event, compare the current HLC physical component with the current physical-clock reading. If physical time advanced beyond the HLC, adopt it and reset the logical counter. If physical time stayed equal or moved backward, keep the HLC physical component and increment the logical counter. The HLC therefore remains monotonic even when the physical input is temporarily non-monotonic.
physical_now > hlc.l : hlc = (physical_now, 0)
physical_now <= hlc.l: hlc = (hlc.l, hlc.c + 1)
3. Receive-event update
A receive event must be later than the receiver's current HLC, the sender's attached HLC, and the receiver's current physical reading. The algorithm chooses the maximum physical component and then advances the logical counter according to which source or sources supplied that maximum. This keeps the receive timestamp greater than the send timestamp while retaining proximity to physical time.
| Case for new l | Logical counter idea |
|---|---|
| local l and remote l both equal new l | max(local c, remote c) + 1 |
| only local l equals new l | local c + 1 |
| only remote l equals new l | remote c + 1 |
| physical_now is strictly greatest | 0 |
4. What HLC does and does not prove
| Property | HLC status |
|---|---|
| Causal monotonicity | preserved by the update rules: happened-before events advance |
| Human-readable physical proximity | much closer to physical/NTP time than a pure event counter, under the clock assumptions |
| Perfect UTC or zero uncertainty | not provided |
| External consistency / linearizability | not provided merely by attaching an HLC |
| Concurrency detection from scalar order | not provided; unrelated events can still be totally ordered by their HLC values |
| Protection from a wildly broken clock source | requires operational bounds and monitoring; HLC is not a substitute for clock health |
This distinction is essential. Some production databases use HLC-like metadata inside larger transaction/replication protocols. Their externally visible guarantees come from the whole protocol, not from the clock data type alone.
5. Deliberately wrong approach — “HLC means timestamps are trustworthy enough for every invariant”
An HLC can make causal ordering robust to ordinary physical-clock regressions, but it does not convert approximate time into a universal concurrency token. If AtlasMart uses only an HLC comparison to decide who owns the last inventory unit, concurrent requests can still need coordination or a conflict rule. If compliance requires traceability to UTC within a bound, the operator still needs monitored clock synchronization and uncertainty/error policy.
The safer framing is: HLC is ordering metadata with physical-time structure. Then separately state the consistency protocol, clock-health assumptions, allowable skew, failure behavior, and business invariant.
6. AtlasMart lab — HLC through clock regression and a message
from dataclasses import dataclass
@dataclass(order=True, frozen=True)
class HLC:
physical: int
logical: int
class HLCNode:
def __init__(self, name):
self.name = name
self.hlc = HLC(0, 0)
def local(self, physical_now):
old = self.hlc
l = max(old.physical, physical_now)
c = old.logical + 1 if l == old.physical else 0
self.hlc = HLC(l, c)
return self.hlc
def receive(self, physical_now, remote):
old = self.hlc
l = max(old.physical, remote.physical, physical_now)
if l == old.physical == remote.physical:
c = max(old.logical, remote.logical) + 1
elif l == old.physical:
c = old.logical + 1
elif l == remote.physical:
c = remote.logical + 1
else:
c = 0
self.hlc = HLC(l, c)
return self.hlc
A, B = HLCNode("A"), HLCNode("B")
print("A local event at physical 1000:", A.local(1000))
print("A clock source moves backward to 980:", A.local(980))
msg = A.local(990)
print("A sends message while physical says 990:", msg)
print("\nB physical clock is 970 when message arrives")
recv = B.receive(970, msg)
print("B receive HLC:", recv)
print("B local at physical 975:", B.local(975))
print("\nproperties shown by this simulation")
print("- HLC did not move backward when physical input moved backward")
print("- receive timestamp is greater than the sent timestamp")
print("- physical component remains near the supplied physical-clock values in this toy history")
print("- scalar HLC order still does NOT prove that two unrelated events were causal")
Expected output
A local event at physical 1000: HLC(physical=1000, logical=0)
A clock source moves backward to 980: HLC(physical=1000, logical=1)
A sends message while physical says 990: HLC(physical=1000, logical=2)
B physical clock is 970 when message arrives
B receive HLC: HLC(physical=1000, logical=3)
B local at physical 975: HLC(physical=1000, logical=4)
properties shown by this simulation
- HLC did not move backward when physical input moved backward
- receive timestamp is greater than the sent timestamp
- physical component remains near the supplied physical-clock values in this toy history
- scalar HLC order still does NOT prove that two unrelated events were causal
Node A's physical input falls from 1000 to 980, yet its HLC remains at physical component 1000 and advances the logical counter. The message carries that state to B, whose own physical input is only 970; B's receive HLC advances beyond the send. The simulation deliberately avoids claiming a real-world skew bound.
Verification checklist
- Physical inputs are synthetic integers; the host clock is untouched.
- A's HLC never decreases during the backward physical input.
- B's receive HLC is greater than A's sent HLC.
- The physical component remains near the sample physical values in this small model.
- The lesson explicitly states that scalar HLC comparison does not detect concurrency.
Check your understanding
- Why does an HLC need a logical counter in addition to a physical-like component?
- What happens when the physical clock moves backward below the current HLC physical component?
- What must a receive HLC be greater than?
- Does HLC by itself provide linearizable database operations?
- Why must operators still monitor clock synchronization and uncertainty?
Review the answers
The counter orders events when physical time does not advance enough, preserving monotonic and causal progress without inventing a larger wall time for every event.
The HLC keeps its current physical component and increments the logical counter instead of moving backward.
The relevant local history and the remote send timestamp, while also incorporating the current physical reading through the maximum rule.
No. Linearizability depends on the complete replication, consensus, read/write protocol, and failure assumptions.
The physical proximity property depends on clock behavior; compliance, leases, expiration, and time-based safety conditions can require explicit bounds that HLC alone does not establish.
7. Production judgment and next bridge
HLCs are useful when a system wants logically safe ordering that remains time-like: change streams, snapshot timestamps, version metadata, or distributed transaction protocols can benefit. Record the exact HLC algorithm and comparison semantics because implementations vary. Persist and recover clock state carefully if protocol correctness depends on monotonicity, and test restarts plus large clock regressions in isolated simulation.
Lesson 5 moves from clock structures to application contracts. Version numbers, ETags, timestamps, and idempotency keys answer different questions: “has this resource changed?”, “may I overwrite this exact representation?”, “approximately when?”, and “has this logical request already produced a side effect?” Mixing them creates subtle lost updates and duplicate actions.
Authoritative references
- Kulkarni et al. — Logical Physical Clocks / Hybrid Logical Clocks — primary HLC design and its relationship to logical and physical clocks
- Lamport — Time, Clocks, and the Ordering of Events — foundational logical-clock and physical-clock framing
- RFC 5905 — NTPv4 — physical clock synchronization and discipline context that HLC does not replace