Key Question
If A happens before B and B happens before C, what can we conclude? And what does it mean for two events to be βconcurrentβ?
Deep Dive
The third and final rule of the happened-before relation is transitivity: if a β b and b β c, then a β c. This may seem trivial β it is a property we expect from any ordering relation β but it is enormously consequential. Transitivity allows causality to chain across processes and across time, linking events that have no direct connection.
Consider a chain of three processes:
Process P1 Process P2 Process P3
| | |
a1 | |
| | |
a2 βββββββββββββ b1 |
| | |
| b2 βββββββββββββ c1
| | |
| | c2
- a2 β b1 (send-receive)
- b2 β c1 (send-receive)
- But also: a2 β b1 β b2 β c1, so a2 β c1 (by transitivity)
The event at P1 (sending a message) causally influences an event at P3 two steps away. Even though no message traveled directly from P1 to P3, the information carried by the first message eventually reached P3 through an intermediate process. This is how causality ripples through a distributed system: an action on one machine can have downstream effects on many machines, through chains of message passing.
Now we come to a subtle and important concept: concurrent events. Two events a and b are concurrent (written a || b) if neither a β b nor b β a holds. The term βconcurrentβ is slightly misleading β it does not mean the events happened at the same physical time. It means there is no causal relationship between them. No chain of causality connects them in either direction.
Process P1 Process P2
| |
a1 b1
| |
a2 b2
| |
a3ββββββββββββββ b3 (send-receive)
| |
a4 b4
In this diagram:
- a1 and b1 are concurrent: there is no message connecting them, no causal chain.
- a1 and b2 are concurrent: same reason.
- a3 and b3: a3 β b3 (send-receive).
- a4 and b3: a3 β a4 (same process) and a3 β b3, so a4 and b3? Actually, a3 β a4 and a3 β b3. But do we know a4 β b3? No. a4 happened after the send on P1, but nothing tells us a4 happened before b3 or vice versa. They are concurrent.
- Wait, letβs reconsider. a3 β b3. a3 β a4. We have a3 β b3 and a3 β a4. But we donβt have a β b or b β a linking a4 and b3. So a4 || b3. Correct.
Concurrency is the source of all interesting problems in distributed systems. When two events are concurrent, their relative order is undefined β and if they conflict (e.g., two writes to the same data item), the system must decide how to resolve the conflict. This is why the CAP theorem matters, why conflict-free replicated data types (CRDTs) exist, and why vector clocks were invented.
Concurrency also helps us reason about what a process βknows.β If a β b, then b may have information about a. If a || b, then b has no information about a, and any decision made by b is made without knowledge of a.
Check Your Understanding
- In the 3-process diagram above, is a1 causally related to c2? Trace the chain.
- Explain in one sentence why concurrent does not mean βat the same physical time.β
- Two bank transactions update the same account balance. They arrive at different servers that do not communicate. Are the transactions concurrent? What problem does this create?
The βSo What?β
Transitivity explains why a change in one service can cascade through a chain of downstream services β a concept central to debugging distributed traces and understanding causal consistency. Concurrency is the fundamental reason distributed databases have conflicts. Every time you see a βconflict detectedβ error in a distributed system, you are seeing the consequences of concurrent events that the system could not order. Understanding concurrency is understanding the limits of what a distributed system can know.
βοΈ Exercises
Topic 3: Happened-Before Relation β Exercises
Exercise 1: Identifying Causal Relationships
Given the space-time diagram below, list ALL pairs of events that are causally related (a β b) and ALL pairs that are concurrent (a || b).
Process A Process B Process C
| | |
a1 | |
| b1 |
a2ββββββββββββββb2 |
| | c1
a3ββββββββββββββb3 |
| | c2
| | |
Use the notation (a1, b1) to mean βa1 and b1.β Include all pairing combinations across processes, not just within them. (You do not need to list same-process pairs β they are all ordered by rule 1.)
Exercise 2: Message Chain Causality
Process A sends a message M1 to Process B. After receiving M1, Process B sends a message M2 to Process C. After receiving M2, Process C performs an internal event.
(a) Draw the space-time diagram of this scenario (3 processes, 2 messages). (b) Is Aβs send of M1 causally related to Cβs internal event? Explain using the happened-before rules. (c) Would the answer change if B sent M2 before receiving M1? Why?
Exercise 3: Network Delay and Causality
Two events occur on different machines:
- Event a: A sends a message to B.
- Event b: B receives the message 5 seconds later (due to network congestion).
After receiving the message, B performs event c.
(a) Is b β c? Why? (b) Is a β b? Why? (c) Now suppose after sending the message, A performs event d (a local computation) 2 seconds before the message is delivered. Is d β c? Trace the chain. (d) What if A and Bβs clocks were perfectly synchronized, and the timestamps showed d at T=10:00:00.003, c at T=10:00:00.001? Does this contradict the happened-before relation? Why would clock-based ordering give the wrong answer?
Exercise 4: Concurrent Writes in Practice
A distributed key-value store has three replicas: R1, R2, R3. A client writes value βXβ to key βkβ and receives acknowledgment from R1. The write propagates to R2 and R3. Meanwhile, another client (unaware of the first write) writes value βYβ to the same key βkβ and receives acknowledgment from R2.
(a) Are the two writes concurrent? Why? (b) R3 receives the write of βYβ after it has already received βX.β From R3βs perspective, which value is βcorrectβ? (c) If the system uses last-writer-wins (LWW) based on physical timestamps, and the clocks on client 1 and client 2 differ by 50 seconds, what could go wrong? (d) What does the happened-before relation tell us that timestamps do not, in this scenario?
ποΈ View Solutions
Topic 3: Happened-Before Relation β Solutions
Solution 1
Within-process relationships (all by rule 1):
- A: a1 β a2 β a3
- B: b1 β b2 β b3
- C: c1 β c2
Send-receive relationships (rule 2):
- a2 β b2 (message from A to B)
- a3 β b3 (another message from A to B)
Transitive relationships (rule 3):
From a1:
- a1 β a2 β b2, so a1 β b2
- a1 β a2 β b2 β b3, so a1 β b3
- a1 β a2 β b2 β b3 β b3 β β¦ actually b3 is followed by nothing in B that reaches C. But wait, a3 β b3, not a2 β b2 β b3. Let me re-trace.
Actually letβs reconsider. The diagram shows a2 β b2 and a3 β b3. There is no message from B to C.
So transitive relationships:
- a1 β b2 (a1 β a2 β b2)
- a1 β b3 (a1 β a2 β b2 β b3? No, a2 β b2, and b2 β b3 in B, so a1 β a2 β b2 β b3, yes)
- a1 β b3 also via a1 β a3 β b3 (a1 β a3, a3 β b3, so a1 β b3)
- a2 β b3 (a2 β b2 β b3)
- a2 β b3 also via a2 β a3 β b3
- a3 β b3 directly (message)
- No relationships with C β there is no message from A or B to C, and no message from C to A or B.
Concurrent pairs:
All pairs involving C are concurrent (no messages to/from C):
- a1 || c1, a1 || c2
- a2 || c1, a2 || c2
- a3 || c1, a3 || c2
- b1 || c1, b1 || c2
- b2 || c1, b2 || c2
- b3 || c1, b3 || c2
Also concurrent across A and B:
- a1 || b1 (no path either direction)
- a2 || b1 (a2 β b2, and b1 β b2, but no path a2 β b1 or b1 β a2)
- a3 || b1 (similar reasoning)
- a3 || b2 (a3 β b3, b2 β b3, no path between a3 and b2 directly)
Any pair not connected by a β b chain is concurrent.
Solution 2
(a)
Process A Process B Process C
| | |
a1 (send M1)ββββb1 (recv M1) |
| | |
| b2 (send M2)ββββc1 (recv M2)
| | |
| | c2 (internal)
(b) Yes, Aβs send (a1) is causally related to Cβs internal event (c2).
- a1 β b1 (rule 2: send-receive of M1)
- b1 β b2 (rule 1: same process)
- b2 β c1 (rule 2: send-receive of M2)
- c1 β c2 (rule 1: same process)
- By transitivity (rule 3): a1 β b1 β b2 β c1 β c2, therefore a1 β c2
(c) If B sent M2 before receiving M1, then b2 (send M2) would come before b1 (receive M1) in process B. In that case:
- a1 β b1 (send-receive of M1)
- b2 β c1 (send-receive of M2)
- But a1 and b2? We have b2 β b1 (b2 before b1 in B). And a1 β b1. But do we have a1 β b2? No. a1 β b1 and b2 β b1, but we need a1 β β¦ β b2. b2 happens before b1, so a1 cannot β b2. And b2 β a1? No, b2 β c1, no path to a1. So a1 and b2 are concurrent. And since b2 β c1 β c2, we have a1 || c2.
The answer changes completely: if B sends M2 before receiving M1, then M2 was sent without knowledge of M1, and Cβs internal event is not causally dependent on Aβs send.
Solution 3
(a) Yes, b β c. Both events are in the same process (B), and b (receive) happens before c (internal event). Rule 1: same-process ordering.
(b) Yes, a β b. a is the send event and b is the receive event of the same message. Rule 2: send happens before receive. The 5-second delay does not affect this β the relationship holds regardless of how long the message takes.
(c) Yes, d β c. Here is the chain:
- a (send) happens before d (local event after send) on process A: a β d (rule 1)
- a β b (rule 2: send-receive)
- d and b: a β d and a β b. Is d β b? We donβt know d β b directly. But d happens after a on the same process. b happens after a (message receive after send). But do we have a relation between d and b? a β d and a β b. But do we know d β b? No, d happens after a on A, but we donβt have a message from A to B after d. d and b are either concurrent orβ¦ wait.
Actually let me reconsider. a β d (same process). a β b (send-receive). We have a β d and a β b. But no path from d to b. And b happens when it happens β we donβt have b β d either (b is in B, d is in A, no message). So d and b are concurrent.
d β c? We need d β β¦ β c. We have b β c (same process). But if d || b, then d β c is not guaranteed.
Hmm, I was too hasty. Let me reconsider the scenario. The key is: did A send another message after d? The scenario says: A sends a message (a), then performs local computation d (2 seconds before delivery). No second message is sent. So d is not connected to Bβs events. Therefore d and b are concurrent, and d and c are concurrent.
Wait, but is d β b possible? d occurs on A after the send. The send is a β B. The message is in transit. d happens before the message is received. But d is on A, b is on B. No message from d to b. So no causal link.
Actually I realize the scenario might be interpreted differently. Let me re-read: βafter sending the message, A performs event d (a local computation) 2 seconds before the message is delivered.β So timeline: a (send), then d (local), then b (receive on B). d occurs on A before the message is received on B.
Is d β c? We need a chain. d β β¦ β c. d is on A. Nothing connects d to B (no message from d). So d || b and d || c. The answer is no, d β c is NOT guaranteed.
Wait, but can we say a β d β β¦ and a β b β c? We know a is before both d and b. So a β d and a β b. But from d, is there any path to b or c? No. So d || c.
(d) If timestamps showed c at T=10:00:00.001 and d at T=10:00:00.003, clock-based ordering would say d happened after c. But the happened-before relation says they are concurrent β they have no causal link. If the clocks are not perfectly synchronized (which they never are), the timestamp ordering is meaningless. Even with perfect clocks, the ordering βc at 0.001, d at 0.003β does not mean c β d β causality cannot be established by timestamps alone. The events are on different machines with no message between them, so they are concurrent regardless of what the clocks say.
Solution 4
(a) Yes, the two writes are concurrent. Client 1 writes to R1, and Client 2 writes to R2. Neither client knew about the otherβs write β there is no causal chain connecting the two writes. No message, no shared state, no happened-before relation. They are concurrent (w1 || w2).
(b) From R3βs perspective, both values are βcorrectβ in the sense that both writes happened without knowledge of the other. R3 cannot automatically determine which value is the βlatestβ because there is no causal relationship between the writes. R3 has a conflict: two concurrent values for the same key.
(c) With LWW based on physical timestamps: if Client 1βs clock is 50 seconds ahead of Client 2βs clock, then Client 1βs write of X will have a timestamp 50 seconds βin the futureβ compared to Client 2βs write of Y. Even if Client 2βs write actually happened later in real time, the timestamp will make it appear older. LWW would select X (the βlaterβ timestamp), even though the correct behavior depends on application semantics, not clock readings.
(d) The happened-before relation tells us that the writes are concurrent β they are causally independent. This means the system cannot automatically resolve the conflict without application help. Timestamps (even with NTP) might assign an arbitrary ordering that could be wrong due to clock drift. The happened-before relation forces the system to acknowledge the conflict and either present both versions to the application or use a deterministic but semantically meaningful tiebreaker (like the applicationβs own merge logic).