Approach 6: Logical Clocks
Why this matters: logical clocks sidestep the clock problem entirely — no synchronization, no drift, no NTP. The cost is size, and watching that cost collide with the 64-bit budget is the lesson.
Key takeaway
Logical clocks — Lamport clocks and vector clocks — rely on monotonically increasing counters rather than wall-clock time. They cannot drift because they are not measuring anything physical.
Lamport clocks
Lamport clocks establish a partial ordering of events. The algorithm is small:
1. Each process keeps a counter, initialized to zero.
2. Increment the counter before every local event.
3. When sending a message, include the current counter as a timestamp.
4. On receiving a message, set the clock to:
max(local clock, received timestamp) + 1
before recording the receipt event.
This guarantees that if event a causally precedes event b, the timestamp of a is less than that of b.
Lamport clocks provide a partial ordering consistent with the happened-before relationship. A total order can be derived by breaking ties with node or process identifiers, though the resulting order depends on the identifier assignment you chose.
Vector clocks
Vector clocks maintain causal history — they capture happened-before relationships between events. Each node tracks a counter per node, so the full dependency structure is visible.
The chapter's proposed ID layout:
1 bit 53 bits 10 bits [sign] [vector clock] [worker] 1 + 53 + 10 = 64 bits
| Field | Bits | Purpose |
|---|---|---|
| Sign bit | 1 | Always zero |
| Vector clock | 53 | The counters of each node |
| Worker number | 10 | 2^10 = 1,024 worker IDs |
The ID pattern is [vector-clock][worker-id]. Following three nodes A, B, and C in a data center:
Start: A = [0,0,0] B = [0,0,0] C = [0,0,0] Event A1 on node A: A = [1,0,0] -> ID: [1,0,0][A] Event C1 on node C: C = [0,0,1] -> ID: [0,0,1][C] Event B1 on node B, having seen A1: B = [1,1,0] -> ID: [1,1,0][B]
Read B = [1,1,0]: node B knows about one event from A and one of its own, and nothing from C. That single vector encodes B's entire causal history — which is exactly what Lamport's scalar counter could not do.
Why vector clocks don't fit
Vector clocks capture full causal history but require a vector size of n, where n is the number of nodes. In large systems — or systems where every client (a web browser, say) is a node — the storage and bandwidth requirements become prohibitive.
Scorecard
| Unique | Scalable | Available | 64-bit numeric ID | Causality maintained | |
|---|---|---|---|---|---|
| Using a range handler | |||||
| Using Twitter Snowflake | weak | ||||
| Using vector clocks | weak | can exceed |
The first approach to fully satisfy causality — and it pays for that with the size and scalability columns.
Key takeaway
Lamport clocks are compact and give only a partial order you cannot invert. Vector clocks give true causality and grow with the cluster. Neither fits a fixed 64-bit budget at scale — which sends us back to physical time, done properly.
Interview signal by level
| Level | What a strong answer sounds like |
|---|---|
| L4 | "Logical clocks use counters instead of real time." |
| L5 | Distinguishes them: "Lamport gives a partial order; vector clocks track a counter per node so you can actually detect concurrency." |
| Staff+ | Names the asymmetry and the size wall: "with Lamport, a-before-b implies L(a) < L(b) but not the converse — so it can't prove causality. Vector clocks can, but they're O(n) in cluster size, which blows a fixed 64-bit budget the moment n is large or every client is a node." |
Next: what happens if you buy better clocks.