AnyLearn
All lessons
Programmingadvanced

Failure and Time in Distributed Systems

Distributed systems break in ways single machines don't: nodes crash silently, messages vanish, and clocks lie. Learn the standard failure taxonomy, why a global clock is physically impossible, and how Lamport and vector clocks let you reason about causality without one.

Updated · AI-authored, review-gated · how lessons are made

Not signed in: your progress and quiz score won't be saved.
Progress1 / 8

System models: synchronous vs. asynchronous

Every distributed systems proof assumes a system model that bounds what can go wrong.

  • Synchronous model: message delivery has a known upper bound Δ\Delta; clock drift is bounded; steps complete in bounded time. Rare in practice — a GC pause or a busy NIC can blow the bound.
  • Partially synchronous model (Dwork-Lynch-Stockmeyer): the system is eventually synchronous — bounds exist but you don't know when they kick in. Matches real WANs and cloud networks.
  • Asynchronous model: no timing guarantees whatsoever. Messages can be delayed arbitrarily. Hardest to work with — algorithms that are correct here are correct everywhere.

Most production consensus protocols (Raft, Paxos) are designed for partial synchrony: they are safe (never return wrong answers) in an asynchronous model, but live (eventually return answers) only under partial synchrony.

Full lesson text

All 8 steps on one page, for reading, reference, and search.

Show

1. System models: synchronous vs. asynchronous

Every distributed systems proof assumes a system model that bounds what can go wrong.

  • Synchronous model: message delivery has a known upper bound Δ\Delta; clock drift is bounded; steps complete in bounded time. Rare in practice — a GC pause or a busy NIC can blow the bound.
  • Partially synchronous model (Dwork-Lynch-Stockmeyer): the system is eventually synchronous — bounds exist but you don't know when they kick in. Matches real WANs and cloud networks.
  • Asynchronous model: no timing guarantees whatsoever. Messages can be delayed arbitrarily. Hardest to work with — algorithms that are correct here are correct everywhere.

Most production consensus protocols (Raft, Paxos) are designed for partial synchrony: they are safe (never return wrong answers) in an asynchronous model, but live (eventually return answers) only under partial synchrony.

2. Failure taxonomy

Failures fall into a hierarchy — each level strictly includes the one above.

Failure typeWhat happensDetectability
Crash-stopNode halts permanently, sends nothing afterEasy (timeout)
Crash-recoveryNode halts then restarts, may lose in-memory stateMedium (rejoin protocol)
OmissionNode runs but drops some sends or receivesHard (message vs. node dead?)
ByzantineNode behaves arbitrarily — lies, replays, colludesVery hard (requires n>3fn > 3f nodes)

The hierarchy matters for algorithm design: a protocol that tolerates crash-stop failures fails catastrophically against Byzantine nodes. Most data-center systems assume crash-recovery; Byzantine tolerance (BFT) is reserved for blockchains or safety-critical systems where nodes may be adversarially controlled.

3. Why there is no global clock

Physical clocks on separate machines drift — even NTP-synchronized clocks have error on the order of milliseconds (wide-area) to microseconds (GPS-disciplined PTP). The fundamental constraint is the speed of light: a clock sync message takes nonzero time in transit, so you can never know the sender's clock reading right now.

The consequence: you cannot order events across nodes by wall-clock time. Consider two writes at t1=10:00:00.001t_1 = 10:00:00.001 on node A and t2=10:00:00.000t_2 = 10:00:00.000 on node B — which came first? Without coordination, you don't know. Spanner's TrueTime API bounds clock uncertainty and forces transactions to wait out the uncertainty window — expensive hardware, not a free lunch. For most systems the answer is logical clocks.

4. Lamport logical clocks

Leslie Lamport's 1978 insight: define a partial order directly from communication — no wall-clock needed.

Happens-before (\to) is the smallest relation satisfying:

  1. If aa and bb are events on the same process and aa comes before bb, then aba \to b.
  2. If aa = "send message mm" and bb = "receive mm", then aba \to b.
  3. Transitivity: aba \to b and bcb \to c implies aca \to c.

Events not related by \to are concurrent. Lamport assigns integer timestamps:

On every event: L = L + 1
On send: attach L to message
On receive(msg): L = max(L, msg.L) + 1

Guarantee: abL(a)<L(b)a \to b \Rightarrow L(a) < L(b). The converse is false — identical or higher timestamps don't imply causality. Lamport clocks capture direction but not full causal history.

5. Vector clocks

Vector clocks fix Lamport's converse gap. Each of nn processes maintains a vector VV of nn counters.

# Process i initializes: V = [0] * n
# On internal event: V[i] += 1
# On send: V[i] += 1; attach V
# On receive from j with timestamp T:
#   V[k] = max(V[k], T[k]) for all k
#   V[i] += 1

Comparison rules:

  • VaVbV_a \leq V_b iff Va[k]Vb[k]V_a[k] \leq V_b[k] for all kk.
  • aba \to b iff VaVbV_a \leq V_b and VaVbV_a \neq V_b.
  • aba \parallel b (concurrent) iff neither VaVbV_a \leq V_b nor VbVaV_b \leq V_a.

Vector clocks are characterize happens-before: abVa<Vba \to b \Leftrightarrow V_a < V_b. The cost is O(n)O(n) space per message — fine for small clusters, painful for thousands of nodes (use version vectors or dotted version vectors instead).

6. Vector clock example: three processes

Three processes P1,P2,P3P_1, P_2, P_3 with vector clocks [v1,v2,v3][v_1, v_2, v_3]:

EventWhoClock after
e1e_1: internalP1P_1[1,0,0]
e2e_2: P1P_1 sends to P2P_2P1P_1[2,0,0]
e3e_3: P2P_2 receives from P1P_1P2P_2[2,1,0]
e4e_4: internalP3P_3[0,0,1]
e5e_5: P2P_2 sends to P3P_3P2P_2[2,2,0]
e6e_6: P3P_3 receives from P2P_2P3P_3[2,2,2]

Now: e1e6e_1 \to e_6 because [1,0,0]<[2,2,2][1,0,0] < [2,2,2]. And e1e4e_1 \parallel e_4 because [1,0,0][1,0,0] and [0,0,1][0,0,1] are incomparable — P1P_1 and P3P_3 never communicated before e4e_4. This is the causality structure vector clocks expose that Lamport timestamps cannot.

7. Happens-before across three processes

Arrows show happens-before relationships derived from message passing.

flowchart LR
  e1["P1: e1 [1,0,0]"]
  e2["P1: send e2 [2,0,0]"]
  e3["P2: recv e3 [2,1,0]"]
  e4["P3: e4 [0,0,1]"]
  e5["P2: send e5 [2,2,0]"]
  e6["P3: recv e6 [2,2,2]"]
  e1 --> e2
  e2 --> e3
  e3 --> e5
  e4 --> e6
  e5 --> e6

8. Practical uses of logical clocks

Logical clocks aren't academic — they drive production systems:

  • Conflict detection in CRDTs and eventual-consistent stores: Dynamo and Riak use vector clocks (actually version vectors) to detect when two writes are concurrent and hand the conflict to the application.
  • Distributed tracing: systems like Jaeger propagate a Lamport-style counter in HTTP headers (traceparent) to reconstruct request causality across microservices.
  • Optimistic replication: a replica accepts a write only if its vector clock shows the write's causal dependencies are already applied — preventing you from seeing a reply before its question.
  • Debugging distributed bugs: tools like Molly and DistSys-Tester inject failures and check vector clock orderings to find violations of expected causal constraints.

The core lesson: when you cannot trust clocks, define order through communication — and vector clocks make that order computable.

Check your understanding

The lesson ends with a 5-question quiz. Take it in the player above to see your score.

  1. A crash-recovery failure differs from a crash-stop failure in that:
    • The node sends incorrect data before halting.
    • The node may restart and rejoin, potentially with stale in-memory state.
    • The node drops some messages but never halts.
    • The node behaves arbitrarily under adversarial control.
  2. Lamport timestamps satisfy: if $a \to b$ then $L(a) < L(b)$. Which additional property do vector clocks add?
    • Vector clocks also bound the physical time difference between events.
    • Vector clocks make the converse true: $L(a) < L(b)$ implies $a \to b$.
    • Vector clocks characterize happens-before in both directions: $a \to b$ iff $V_a < V_b$.
    • Vector clocks reduce message overhead compared to Lamport timestamps.
  3. Process $P_1$ has vector clock $[3, 1, 0]$ and process $P_2$ has $[2, 2, 0]$. What is the relationship between the events at these timestamps?
    • $P_1$'s event happened before $P_2$'s event.
    • $P_2$'s event happened before $P_1$'s event.
    • The events are concurrent — neither happened before the other.
    • The events are on the same process, so physical time determines order.
  4. Why is the partially synchronous model more realistic than the fully synchronous model for most production networks?
    • Partial synchrony allows message reordering, which real networks never do.
    • Full synchrony requires all messages to arrive simultaneously, which TCP prevents.
    • GC pauses, CPU contention, and network congestion can violate fixed timing bounds, making guaranteed upper bounds unrealistic.
    • Partial synchrony is required by the TCP/IP specification.
  5. In a system using vector clocks for replication, node B receives a write with attached clock $[0, 1, 0]$, but B's current state is at $[0, 0, 0]$. What should B do?
    • Apply the write immediately and set its clock to $[0, 1, 0]$.
    • Reject the write permanently as out of order.
    • Check whether the write's causal dependencies are satisfied before applying; here they are, since $[0,0,0]$ precedes $[0,1,0]$.
    • Request the full history from node A to reconstruct causal ordering.

Related lessons

Programming
intermediate

Distributed Tracing and the Art of Throwing Data Away

Tracing every request through every service produces the most useful telemetry you have and more of it than anyone can afford. This lesson covers how context propagation actually stitches a trace together, the head versus tail sampling decision and why it determines which incidents you can debug, and the collector pipeline where all of it is enforced.

7 steps·~11 min
Programming
advanced

The Failure Modes the Design Creates

Reconciliation buys robustness and charges for it in a specific currency: nothing is ever definitely finished, commands succeed without meaning anything worked, and manual fixes are silently undone. This lesson covers the failures that come from the model rather than from bugs, how to debug them, and when the whole thing is the wrong tool.

8 steps·~12 min
Programming
advanced

Scheduling and Resources: Requests Are Not Limits

Two numbers govern where a pod lands and how it behaves under pressure, and they do completely different jobs. Requests are used for placement and are a promise; limits are enforced at run time and are a ceiling. This lesson separates them, covers the asymmetry between CPU and memory enforcement, and explains what actually happens when a node runs out.

8 steps·~12 min
Programming
advanced

Pods, Services, and the Label That Joins Them

The object types look like an arbitrary vocabulary until you notice they are layers of controllers, each reconciling the one below. This lesson works up from the pod, explains why a Service can point at something whose address changes constantly, and shows that the whole system is joined by one mechanism: a label match.

8 steps·~12 min