Logical Clocks¶
Lamport, Vector, and Hybrid Logical Clocks for causal ordering in distributed systems.
Logical clocks for distributed systems simulation.
Provides three classic logical clock algorithms used in distributed systems:
- LamportClock: Monotonic counter for total ordering (Lamport 1978).
- VectorClock: Per-node counters for causal ordering (Fidge/Mattern 1988).
- HybridLogicalClock: Physical + logical components (Kulkarni et al. 2014).
These are pure algorithm classes — not Entities. Entities store them as fields
and call their methods during event handling, exactly like NodeClock and
FixedSkew/LinearDrift.
Usage::
from happysimulator import LamportClock, VectorClock, HybridLogicalClock
# Lamport: simple monotonic counter
clock = LamportClock()
clock.tick() # Local event
ts = clock.send() # Returns timestamp to embed in message
clock.receive(ts) # max(local, remote) + 1
# Vector clock: per-node counters for causal ordering
vc = VectorClock("node-1", ["node-1", "node-2", "node-3"])
vc.tick()
snapshot = vc.send() # dict[str, int] to embed in message
vc.receive(snapshot) # Element-wise max + increment own
# HLC: physical + logical (CockroachDB/Spanner-style)
hlc = HybridLogicalClock("node-1", physical_clock=node_clock)
ts = hlc.now() # HLCTimestamp(physical_ns, logical, node_id)
hlc.receive(remote_ts)
LamportClock ¶
Monotonic counter for establishing total ordering of events.
Each local event increments the counter. When sending a message, the
current counter is included. On receive, the counter advances to
max(local, remote) + 1, ensuring causal ordering.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
initial
|
int
|
Starting counter value (default 0). |
0
|
receive ¶
Update counter on receiving a message.
Sets counter to max(local, remote) + 1.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
remote_ts
|
int
|
The Lamport timestamp from the received message. |
required |
VectorClock ¶
Per-node counters for determining causal ordering between events.
Each node maintains a vector of counters (one per node in the system).
The happened_before relation enables detecting causally related vs.
concurrent events — fundamental to conflict detection in replicated
datastores (Dynamo, Riak) and consistency verification.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
node_id
|
str
|
This node's identifier. |
required |
node_ids
|
list[str]
|
All node identifiers in the system (including this node). |
required |
send ¶
Increment own counter and return a snapshot for embedding in a message.
receive ¶
Update vector on receiving a message.
Takes the element-wise max of local and remote vectors, then increments own counter.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
remote
|
dict[str, int]
|
The vector clock snapshot from the received message. |
required |
happened_before ¶
Test if this clock causally precedes other.
Returns True if all components of this vector are <= the
corresponding components of other, and at least one is
strictly less.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
other
|
VectorClock
|
The vector clock to compare against. |
required |
is_concurrent ¶
Test if this clock and other are concurrent (neither happened-before).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
other
|
VectorClock
|
The vector clock to compare against. |
required |
merge ¶
Create a new VectorClock with element-wise max (no increment).
Unlike receive(), this does NOT increment the local counter.
Useful for read-only merges (e.g., computing a combined view).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
other
|
VectorClock
|
The vector clock to merge with. |
required |
Returns:
| Type | Description |
|---|---|
VectorClock
|
A new VectorClock with the merged vector. |
HLCTimestamp
dataclass
¶
Immutable timestamp from a Hybrid Logical Clock.
Total ordering is defined by (physical_ns, logical, node_id).
Attributes:
| Name | Type | Description |
|---|---|---|
physical_ns |
int
|
Nanosecond wall-clock component. |
logical |
int
|
Logical counter within the same physical tick. |
node_id |
str
|
Originating node identifier. |
HybridLogicalClock ¶
HybridLogicalClock(
node_id: str,
physical_clock: NodeClock | None = None,
wall_time: Callable[[], Instant] | None = None,
)
Hybrid Logical Clock combining physical and logical components.
HLC provides the best of both worlds: timestamps that respect causality (like Lamport clocks) while staying close to physical time (enabling bounded-staleness reads). Used in CockroachDB, Spanner, and similar systems.
The physical component comes from either a NodeClock (for skew
modeling) or a plain callable returning an Instant.
Algorithm (Kulkarni et al. 2014):
- now/send: Read physical time
pt. Ifpt > last.physical_ns, reset logical to 0. Otherwise keep physical from last and increment logical. Return(pt, logical, node_id). - receive: Take max of physical time, last physical, and remote physical. Adjust logical based on which components tied.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
node_id
|
str
|
This node's identifier. |
required |
physical_clock
|
NodeClock | None
|
A |
None
|
wall_time
|
Callable[[], Instant] | None
|
A callable returning the current |
None
|
Raises:
| Type | Description |
|---|---|
ValueError
|
If both or neither time source is provided. |
now ¶
Generate a timestamp for the current instant.
Reads the physical clock and advances the logical component if the physical time hasn't changed since the last call.
send ¶
Generate a timestamp to embed in an outgoing message.
Equivalent to now() — included for API symmetry with
LamportClock and VectorClock.
receive ¶
Update clock state on receiving a remote timestamp.
Implements the HLC receive algorithm: takes the max of physical time, last local physical, and remote physical, then adjusts the logical component based on which values tied.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
remote
|
HLCTimestamp
|
The HLC timestamp from the received message. |
required |