Lamport timestamps are a clever way to impose a consistent ordering on events happening across multiple machines that don’t have a perfectly synchronized clock.
Imagine you have three servers, node1, node2, and node3, all running an application. They need to know the order in which certain events occurred, like a user updating a record.
Here’s what it looks like in action. Let’s say node1 has a local clock C1, node2 has C2, and node3 has C3.
Initial State:
node1: C1 = 0
node2: C2 = 0
node3: C3 = 0
Event 1: node1 sends a message.
node1increments its clock:C1 = 1.node1attaches the timestamp1to the message.node1sends the message tonode2.
Event 2: node2 receives the message from node1.
node2receives the message with timestamp1.node2updates its clock to be the maximum of its current clock and the received timestamp, then increments:C2 = max(C2, 1) + 1 = max(0, 1) + 1 = 2.node2processes the message.
Event 3: node2 sends a message.
node2increments its clock:C2 = 3.node2attaches the timestamp3to the message.node2sends the message tonode3.
Event 4: node3 receives the message from node2.
node3receives the message with timestamp3.node3updates its clock:C3 = max(C3, 3) + 1 = max(0, 3) + 1 = 4.node3processes the message.
Event 5: node3 has a local event.
node3increments its clock:C3 = 5.- This local event now has timestamp
5.
Event 6: node3 sends a message.
node3increments its clock:C3 = 6.node3attaches the timestamp6to the message.node3sends the message tonode1.
Event 7: node1 receives the message from node3.
node1receives the message with timestamp6.node1updates its clock:C1 = max(C1, 6) + 1 = max(1, 6) + 1 = 7.node1processes the message.
Now, if you look at the timestamps associated with events:
node1’s send (Event 1):1node2’s receive (Event 2):2node2’s send (Event 3):3node3’s receive (Event 4):4node3’s local event (Event 5):5node3’s send (Event 6):6node1’s receive (Event 7):7
This sequence of timestamps 1, 2, 3, 4, 5, 6, 7 represents a happened-before relationship. If event A has a lower timestamp than event B, it’s highly probable that A happened before B. Even though clocks are not synchronized, this system guarantees that if event A truly happened before event B (either concurrently on the same node, or because A caused B via a message), then the timestamp of A will be less than the timestamp of B.
The core problem this solves is establishing a consistent, global ordering of events in a distributed system where nodes cannot rely on a single, perfectly synchronized clock. Without this, you can’t definitively say which action truly preceded another when those actions occur on different machines. This is fundamental for tasks like distributed consensus, garbage collection, and maintaining consistent state across replicas.
The rules are simple:
- Local Event: When a node experiences a local event (not a message receive or send), it increments its own clock and assigns that value as the timestamp for the event.
- Sending a Message: Before sending a message, the node increments its clock, assigns the new value as the timestamp for the message, and sends it.
- Receiving a Message: When a node receives a message with timestamp
T, it updates its own clock tomax(its_current_clock, T) + 1. The+1ensures the receive event’s timestamp is strictly greater than the send event’s timestamp.
The most surprising aspect of Lamport timestamps is that while they establish a total order on events, they don’t necessarily reflect the actual wall-clock time. Two events with timestamps 5 and 6 might have occurred very close together in real time, or event 5 could have happened significantly earlier than event 6 but involved a much longer processing delay or message latency. The ordering is causal, not temporal.
The next concept to explore is how to achieve a true synchronization of events, not just a causal ordering, which leads to Vector Clocks.