Skip to main content

Welcome to a Grasp lesson

Create a free account to keep studying and create your own study path.

Lesson 3

Lamport Timestamps: Ordering Events in Distributed Systems

Recommended resources

Hello! Welcome to the third lesson in our course on distributed systems design.

In our last lesson, we established that physical clocks are fundamentally unreliable for ordering events across different machines. Clock skew, NTP adjustments, and leap seconds create a messy reality where you can't trust timestamps to tell you what happened first. This makes simple conflict resolution strategies like "Last Write Wins" dangerously fragile.

This lesson directly addresses the learning outcome: Apply logical clocks (Lamport timestamps) to establish a partial ordering of events. We will leave the world of physical time behind and enter the world of logical time, where the primary concern is not "when" an event happened, but "what caused" an event to happen.

From Time to Causality: The "Happens-Before" Relation

If we can't use physical clocks to order events, we need a new principle. In distributed systems, that principle is causality. Intuitively, if event A must happen for event B to be possible, then we say A "happened before" B. For example, sending a network request happens before receiving the response.

To formalize this, let's watch a video that introduces the problem of causal ordering and defines the crucial "happens-before" relationship.

Distributed Systems 3.3: Causality and happens-before

Martin Kleppmann introduces the concept of causality using a simple discussion thread example, demonstrating why physical timestamps fail to capture the correct order. He then formally defines the 'happens-before' relation.

Watch from the beginning to 11:31. Focus on understanding the three rules that define the 'happens-before' relation and what it means for two events to be 'concurrent'.

As the video explained, the "happens-before" relation (often written as A -> B) is defined by three simple rules:

  1. On the same node: If event A occurs before event B on the same node, then A -> B. This is based on the local execution order of a single process.
  2. Across nodes (messages): If A is the event of sending a message and B is the event of receiving that same message, then A -> B.
  3. Transitivity: If A -> B and B -> C, then A -> C. This allows us to chain causal dependencies across the system.

This set of rules defines a partial order. Some events are causally related (one happened before the other), but other events are concurrent. Concurrent events (written A || B) are those where neither A -> B nor B -> A is true. This doesn't mean they happened at the same physical time, but rather that they occurred independently, without knowledge of one another.

Our goal is to create a timestamping system that respects this causal order.


Lamport Timestamps: An Algorithm for Causal Ordering

In 1978, Leslie Lamport introduced a simple algorithm to assign a number to every event in a distributed system, such that the ordering of these numbers is consistent with the happens-before relationship. These numbers are called Lamport timestamps or logical clocks.

Let's watch a short video introducing the concept and the algorithm.

Distributed Systems 4.1: Logical time

This video introduces logical clocks as a solution to the causality problem and details the Lamport timestamp algorithm.

Watch from the beginning to 05:36. Pay close attention to the three rules for managing the timestamp counter.

The algorithm is elegant in its simplicity. Each node in the system maintains a single integer counter. Here are the rules, which you've just seen in the video:

  1. Initialization: Each node initializes its counter to 0.
  2. Local Event: Before executing an event, a node increments its counter.
  3. Sending a Message: When a node sends a message, it increments its counter and includes the new counter value in the message.
  4. Receiving a Message: When a node receives a message, it updates its own counter by taking the maximum of its current value and the timestamp from the message, and then increments it by 1. In pseudocode: counter = max(local_counter, message_counter) + 1.

Let's look at a static diagram to see this in action.

Lamport Timestamps Illustration
This diagram shows three processes (P1, P2, P3). Notice how P2's clock jumps from 2 to 3 upon receiving a message with timestamp 2 from P1. This is the `max(2, 2) + 1` rule. Similarly, P3's clock jumps from 2 to 7 upon receiving a message with timestamp 6 from P2, following the rule `max(2, 6) + 1`.

Now, let's see this animated in the video you started earlier.

Distributed Systems 4.1: Logical time

This brief segment shows a fully worked-out example of Lamport timestamps in action, similar to the diagram above.

Watch from 06:07 to 07:23 to see another visualization of the algorithm.

Test your understanding!

Consider the following system with three nodes: A, B, and C. Their Lamport clocks are all initialized to 0. The following events occur in sequence:

  1. Node A has a local event.
  2. Node A sends a message (M1) to Node B.
  3. Node C has a local event.
  4. Node B receives message M1 from Node A.
  5. Node B sends a message (M2) to Node C.
  6. Node C receives message M2 from Node B.

What is the final Lamport timestamp on Node C after event 6?

Show answer

Let's trace the clocks on each node: (A, B, C) starts at (0, 0, 0).

  1. A has a local event: A increments its clock. (1, 0, 0)
  2. A sends M1 to B: A increments its clock and sends M1(ts=2). (2, 0, 0)
  3. C has a local event: C increments its clock. (2, 0, 1)
  4. B receives M1(ts=2): B's clock becomes max(0, 2) + 1 = 3. (2, 3, 1)
  5. B sends M2 to C: B increments its clock and sends M2(ts=4). (2, 4, 1)
  6. C receives M2(ts=4): C's clock becomes max(1, 4) + 1 = 5. (2, 4, 5)

The final Lamport timestamp on Node C is 5.


Properties and Limitations

Lamport timestamps have one crucial guarantee, known as the Clock Consistency Condition:

If event A happens-before B (A -> B), then the Lamport timestamp of A is less than the Lamport timestamp of B (L(A) < L(B)).

This is exactly what we wanted: the timestamps respect causality.

However, the reverse is not true. If L(A) < L(B), it does not necessarily mean A -> B. It's possible that A and B are concurrent. This is the primary limitation of Lamport clocks. You can't use them to tell if two events are causally related or just happened to get ordered timestamps by chance.

Distributed Systems 4.1: Logical time

This final segment from Kleppmann's lecture discusses this key limitation and then introduces a powerful technique to create a total order from Lamport timestamps.

Watch from 07:23 to 11:50. Focus on the tie-breaking mechanism using node IDs and the final summary of what Lamport clocks can and cannot do.

Creating a Total Order

Despite not being able to distinguish concurrency, Lamport clocks can be used to create a total order of all events in the system that is consistent with causality. This is extremely useful for algorithms that require all nodes to process events in the same order (e.g., for state machine replication).

The trick, as you saw, is to use a tie-breaker. Instead of just a timestamp L(E), we use a pair: (L(E), NodeID).

We can then define a total order < such that event A < B if:

  • L(A) < L(B), OR
  • L(A) = L(B) AND NodeID_A < NodeID_B (assuming node IDs can be lexicographically or numerically compared).

This ensures that every pair of distinct events has a defined order, and that order will never violate causality.

For further reading and a different perspective, the following article provides a concise explanation of Lamport clocks and their properties.

Distributed systems for fun and profit

This article from 'Distributed systems for fun and profit' provides a clear, concise summary of Lamport clocks with a simple code example, reinforcing the rules we've just learned.

Read the section titled 'Vector clocks (time for causal order)'. Focus specifically on the paragraphs and code block describing the 'Lamport clock'.


Conclusion

In this lesson, we took our first step into the world of logical time, which is foundational to reasoning about distributed systems.

Here are the key takeaways:

  • Causality is Key: When physical time is unreliable, we rely on the happens-before relation (->) to define a partial order of events based on local execution and message passing.
  • Lamport Timestamps Capture Causality: Lamport clocks are simple counters that generate timestamps L(E) for every event E, guaranteeing that if A -> B, then L(A) < L(B).
  • The Algorithm: The rules are simple: increment for local events, and upon receiving a message, update your clock to max(local, received) + 1.
  • Limitation: You cannot infer causality from Lamport timestamps alone. If L(A) < L(B), events A and B might be causally related, or they might be concurrent.
  • Total Ordering: By combining the Lamport timestamp with a unique node ID (L(E), NodeID), we can create a total order of all events that is consistent with the partial causal order.

We've seen that Lamport clocks can tell us that A might have caused B, but they can't tell us for sure. What if we need to know definitively whether two events are concurrent?

Preview of the next lesson: We will explore vector clocks. Vector clocks are an extension of Lamport clocks that solve this exact problem. They allow a node to not only determine if one event happened before another but also to detect if two events are concurrent. This provides a much richer understanding of the system's causal history.