Distributed Computing: Principles, Algorithms, and Systems — Under the Hood¶
Source: Distributed Computing: Principles, Algorithms, and Systems — Ajay D. Kshemkalyani & Mukesh Singhal, Cambridge University Press, 2008 (756 pages)
Reading contract: This is a theory-oriented reading of a 2008 textbook for readers who can track system and fault models. Every theorem or message bound must name synchrony, channel reliability and ordering, membership, crash or Byzantine faults, authentication, quorum construction, and termination assumptions. Proof sketches are derivations; product mappings and round-trip counts are examples. A protocol is complete only with safety and liveness arguments, recovery state, retry/idempotency rules, and fault-injection evidence for the claimed model. Outside that model, retain the result as unproven rather than extending it by analogy.
1. The Distributed Execution Model: Events, Causality, and Global State¶
A distributed system is not a sequential machine. There is no global clock — each process has its own local clock and communicates only by message passing. The fundamental unit of analysis is an event: a local computation, a send, or a receive.
flowchart LR
subgraph P1["Process P1"]
E1["e₁¹ (local)"] --> E2["e₂¹ (send m1)"] --> E3["e₃¹ (receive m2)"] --> E4["e₄¹ (local)"]
end
subgraph P2["Process P2"]
F1["f₁² (local)"] --> F2["f₂² (receive m1)"] --> F3["f₃² (send m2)"] --> F4["f₄² (local)"]
end
E2 -->|"m1 (message in transit)"| F2
F3 -->|"m2"| E3
subgraph HAPPENS_BEFORE["Happens-Before (→) Relation"]
HB1["e₂¹ → f₂² (send before receive)"]
HB2["f₂² → f₃² (same process, sequential)"]
HB3["f₃² → e₃¹ (send before receive)"]
HB4["e₂¹ → e₃¹ (transitivity)"]
end
Lamport's happens-before (→) is a partial order — not all event pairs are comparable. Two events a and b are concurrent (a ∥ b) if neither a → b nor b → a. This is the root cause of distributed system complexity: concurrent events cannot be linearized without coordination.
2. Logical Clocks: Assigning Timestamps Without a Global Clock¶
Lamport Scalar Clocks¶
Each process Pi maintains a counter C[i]. The rules:
1. Before every event: C[i] += 1
2. On send: piggyback C[i] in message
3. On receive(msg with timestamp t): C[i] = max(C[i], t) + 1
sequenceDiagram
participant P1 as P1 (C=0)
participant P2 as P2 (C=0)
participant P3 as P3 (C=0)
Note over P1: C=1: local event e₁
P1->>P2: m1 (timestamp=1)
Note over P2: C=max(0,1)+1=2: receive m1
Note over P2: C=3: local event f₂
P2->>P3: m2 (timestamp=3)
Note over P3: C=max(0,3)+1=4: receive m2
Note over P1: C=2: local event e₂
Note over P1,P3: Lamport: a→b ⟹ C(a) < C(b)\nBut C(a) < C(b) ⟹ NOT necessarily a→b\n(false positives: concurrent events may share ordering)
Limitation: Scalar clocks cannot detect concurrency. If C(a) < C(b), it could mean a → b OR a ∥ b.
Vector Clocks: Capturing Full Causality¶
Process Pi maintains vector VC[i][1..n]. Rules:
1. Before event at Pi: VC[i][i] += 1
2. On send: piggyback entire vector VC[i]
3. On receive at Pi from Pj with timestamp VT: VC[i][k] = max(VC[i][k], VT[k]) for all k, then VC[i][i] += 1
sequenceDiagram
participant P1 as P1 VC=[0,0,0]
participant P2 as P2 VC=[0,0,0]
participant P3 as P3 VC=[0,0,0]
Note over P1: VC=[1,0,0]: send m1
P1->>P2: m1 (VC=[1,0,0])
Note over P2: VC=[1,1,0]: receive m1, then +1 own
Note over P2: VC=[1,2,0]: send m2
P2->>P3: m2 (VC=[1,2,0])
Note over P3: VC=[1,2,1]: receive
Note over P1: VC=[2,0,0]: independent local event
Note over P1,P3: e@P1=[2,0,0], f@P3=[1,2,1]\ne∥f: neither dominates the other component-wise
Vector clock comparison: VC(a) < VC(b) iff VC(a)[k] ≤ VC(b)[k] for all k and strict for at least one. This is a necessary and sufficient condition for a → b. Concurrent events are detected when neither dominates.
3. Global State and Consistent Cuts¶
A global state is a tuple of local process states and channel states. A consistent cut is a global state where for every message received, the corresponding send is also included.
flowchart LR
subgraph TIME["Space-Time Diagram"]
direction TB
P1_LINE["P1: e₁ ——● e₂ ——● e₃ ——●"]
P2_LINE["P2: f₁ ——● f₂ ——● f₃ ——●"]
P3_LINE["P3: g₁ ——● g₂ ——● g₃ ——●"]
end
subgraph CUTS["Cut Comparison"]
CUT_A["Consistent Cut C₁:\ne₂, f₁, g₃\nAll received messages\nalso have senders included"]
CUT_B["Inconsistent Cut C₂:\ne₁, f₂, g₁\nf₂ = receive(m from P1)\nbut P1 cut at e₁ (before send)\n→ INCONSISTENT"]
end
Chandy-Lamport Snapshot Algorithm¶
Records a consistent global state without freezing the system:
sequenceDiagram
participant P1 as P1 (initiator)
participant P2 as P2
participant P3 as P3
P1->>P1: Record own state S1
P1->>P2: MARKER (on channel c12)
P1->>P3: MARKER (on channel c13)
Note over P1: Begin recording messages on incoming channels
P2->>P2: Receive MARKER from P1\n→ Record state S2\nRecord c12 state = {} (empty: MARKER was first)
P2->>P3: MARKER (on channel c23)
Note over P2: Record incoming msgs from P3 (not yet seen MARKER from P3)
P3->>P3: Receive MARKER from P1\n→ Record state S3
P3->>P3: Receive MARKER from P2\n→ Record c23 state = {msgs since S3 snapshot}
Note over P1,P3: Global snapshot = (S1, S2, S3, c12={}, c13={}, c23={msgs})
Note over P1,P3: This is a consistent cut:\neach received message also has its send in snapshot
Key insight: The MARKER acts as a timestamp separator. Everything before the MARKER on a channel belongs to the snapshot; everything after does not. The algorithm is non-intrusive — normal computation continues.
4. Distributed Mutual Exclusion: Algorithms and Complexity¶
Lamport's Algorithm (1978)¶
Uses Lamport clocks to totally order requests. Every site maintains a request queue sorted by (timestamp, site_id).
sequenceDiagram
participant Si as Site Si
participant Sj as Site Sj (all other sites)
participant CS as Critical Section
Si->>Sj: REQUEST(tsi, i) broadcast to all
Si->>Si: Add (tsi, i) to own queue
Sj-->>Si: REPLY(tsj) after adding to own queue
Note over Si: Si enters CS when:\n1. (tsi,i) is at head of queue\n2. Received REPLY from ALL other sites
Si->>CS: Enter Critical Section
CS->>Si: Exit CS
Si->>Sj: RELEASE broadcast
Sj->>Sj: Remove (tsi,i) from queue
Message complexity: 3(N-1) messages per CS entry (N-1 REQUESTs + N-1 REPLYs + N-1 RELEASEs). Synchronization delay: T (one message round trip).
Ricart-Agrawala Algorithm (1981): Optimized¶
Eliminates explicit RELEASE messages by merging them into deferred REPLYs:
sequenceDiagram
participant Si as Si (wants CS)
participant Sj as Sj (in CS or wants CS)
participant Sk as Sk (idle)
Si->>Sj: REQUEST(tsi, i)
Si->>Sk: REQUEST(tsi, i)
Sk-->>Si: REPLY immediately (Sk doesn't want CS)
Note over Sj: Sj in CS → defer REPLY to Si
Sj->>CS: (finishes CS)
Sj-->>Si: REPLY (deferred)
Note over Si: Received N-1 REPLYs → Enter CS
Si->>CS: Enter CS
Message complexity: 2(N-1) messages (N-1 REQUESTs + N-1 REPLYs). Correctness: Total order on requests via timestamps ensures no two sites are in CS simultaneously.
Maekawa's Quorum-Based Algorithm: √N Messages¶
Instead of broadcasting to all N sites, each site broadcasts to only its quorum set of size ~√N:
flowchart TD
subgraph QUORUM_SETS["Quorum Sets for N=9 sites (3×3 grid)"]
Q1["R(S1) = {S1, S2, S3, S4, S7}"]
Q2["R(S5) = {S5, S2, S8, S4, S6}"]
Q3["Any two quorum sets intersect:\nR(S1) ∩ R(S5) = {S2, S4}"]
end
DEADLOCK["Deadlock Problem:\nSi locks Sij, Sj locks Sjk, Sk locks Ski\n→ Circular wait"]
RESOLVE["Resolution:\nFAILED / INQUIRE / YIELD messages\n→ 5√N messages worst case"]
QUORUM_SETS --> DEADLOCK --> RESOLVE
Why quorums guarantee safety: Any two quorum sets must intersect — so two sites can never simultaneously hold locks on disjoint sets. The intersection site acts as the "common voter" serializing access.
5. Deadlock Detection: Wait-For Graphs¶
Centralized Deadlock Detection¶
flowchart TD
subgraph WFG["Wait-For Graph (WFG)"]
P1(["P1"]) -->|"waiting for resource held by"| P2(["P2"])
P2 -->|waiting for| P3(["P3"])
P3 -->|waiting for| P1
NOTE["Cycle P1→P2→P3→P1 = DEADLOCK"]
end
subgraph CENTRALIZED["Centralized Algorithm"]
LOCAL1["Local WFG at Site 1\nP1 → P2"] -->|periodic update| CONTROLLER["Central Deadlock\nDetector"]
LOCAL2["Local WFG at Site 2\nP2 → P3"] --> CONTROLLER
LOCAL3["Local WFG at Site 3\nP3 → P1"] --> CONTROLLER
CONTROLLER -->|"Cycle detection\n(DFS/BFS)"| RESOLVE["Kill youngest\nor cheapest process"]
end
Chandy-Misra-Haas Algorithm for Distributed Deadlock (AND model)¶
sequenceDiagram
participant P1 as P1 (blocked)
participant P2 as P2 (blocked, intermediary)
participant P3 as P3 (blocked)
Note over P1: P1 is blocked waiting for P2
P1->>P2: PROBE(P1, P1, P2) [initiator, sender, receiver]
Note over P2: P2 is also blocked → propagate PROBE
P2->>P3: PROBE(P1, P2, P3)
Note over P3: P3 is blocked, waiting for P1
P3->>P1: PROBE(P1, P3, P1)
Note over P1: Receive PROBE with initiator=P1\n→ DEADLOCK DETECTED
Note over P1: P1 initiates victim selection:\nkill the process with max PID in cycle
Complexity: O(e) messages where e = edges in WFG. Probes propagate along waiting edges — the PROBE returns to its initiator only if there is a cycle.
6. Consensus and Agreement: The FLP Impossibility¶
The Fischer-Lynch-Paterson (FLP) impossibility theorem (1985) is the most important result in distributed systems theory:
FLP scope: No deterministic consensus protocol can guarantee termination in every admissible execution of a fully asynchronous message-passing system with even one possible crash failure. The result does not say safety is impossible or that practical protocols never terminate under added timing assumptions.
stateDiagram-v2
[*] --> BIVALENT_INITIAL: Initial state\n(some process may crash)
BIVALENT_INITIAL --> DECISION_ATTEMPT: Execute one step
DECISION_ATTEMPT --> BIVALENT_AGAIN: Step taken by potentially\ncrashed process — indistinguishable\nfrom live-but-slow process
BIVALENT_AGAIN --> DECISION_ATTEMPT: Repeat forever
note right of BIVALENT_AGAIN: Bivalent = both 0 and 1\nstill reachable\n\nMonovalent = only 0 or 1\nreachable (decided)\n\nFLP: Can never deterministically\ntransition bivalent→monovalent\nin async system with failures
Why this matters: Any system claiming Byzantine/crash fault tolerance in an asynchronous network must either: 1. Use randomization (Randomized consensus, Ben-Or algorithm) 2. Use partial synchrony assumptions (Paxos, Raft — assume messages eventually arrive) 3. Solve a weaker problem (k-set consensus, approximate agreement)
Paxos: Consensus Under Partial Synchrony¶
Paxos assumes eventually synchronous channels — messages may be delayed but eventually arrive. It uses two phases:
sequenceDiagram
participant PROPOSER as Proposer (Leader)
participant A1 as Acceptor 1
participant A2 as Acceptor 2
participant A3 as Acceptor 3
Note over PROPOSER: Phase 1a: Prepare
PROPOSER->>A1: PREPARE(n=5)
PROPOSER->>A2: PREPARE(n=5)
PROPOSER->>A3: PREPARE(n=5)
Note over A1,A3: Accept if n > highest_promised
A1-->>PROPOSER: PROMISE(n=5, last_accepted=null)
A2-->>PROPOSER: PROMISE(n=5, last_accepted=(n=3, v=42))
A3-->>PROPOSER: PROMISE(n=5, last_accepted=null)
Note over PROPOSER: Phase 2a: Accept\nIf any PROMISE had prior value,\nmust use that value (v=42)\nOtherwise propose own value
PROPOSER->>A1: ACCEPT(n=5, v=42)
PROPOSER->>A2: ACCEPT(n=5, v=42)
PROPOSER->>A3: ACCEPT(n=5, v=42)
A1-->>PROPOSER: ACCEPTED(n=5)
A2-->>PROPOSER: ACCEPTED(n=5)
Note over PROPOSER: Quorum (2/3) → COMMIT v=42
PROPOSER->>A1: COMMIT(v=42)
PROPOSER->>A2: COMMIT(v=42)
PROPOSER->>A3: COMMIT(v=42)
Why Phase 1b must return the highest prior accepted value: If an acceptor has already accepted value v in a prior round, there may be a quorum that has committed v. The new proposer must preserve this value to prevent two different values being committed in different rounds.
Raft: Understandable Paxos¶
stateDiagram-v2
[*] --> Follower: Node starts
Follower --> Candidate: Election timeout\n(no heartbeat from leader)
Candidate --> Leader: Receives votes from majority
Candidate --> Follower: Discovers leader\nor higher term
Leader --> Follower: Discovers higher term
Leader --> Leader: Sends heartbeats\nevery 150-300ms
state Leader {
[*] --> AppendEntries_RPC
AppendEntries_RPC --> Commit: Majority acknowledgment
Commit --> [*]
}
Raft log commitment: Majority replication is necessary, but the current leader advances commit through the Raft commitment rules, including the restriction on directly committing entries from prior terms. Client reply timing is an implementation contract. Uncommitted entries may be overwritten; committed-entry durability still assumes the Raft storage and membership model.
7. Failure Detectors: The Theory Behind Heartbeats¶
Chandra and Toueg (1996) formalized failure detectors as oracle modules:
flowchart TD
subgraph CLASSES["Failure Detector Classes (by two properties)"]
subgraph COMPLETENESS["Completeness\n(correct detectors eventually suspect crashed processes)"]
STRONG["Strong: every crashed process\neventually suspected by ALL correct processes"]
WEAK["Weak: every crashed process\neventually suspected by SOME correct process"]
end
subgraph ACCURACY["Accuracy\n(how often correct processes are wrongly suspected)"]
STRONG_ACC["Strong: no correct process ever suspected"]
WEAK_ACC["Weak: some correct process is never suspected"]
EVENTUAL_S["Eventual Strong: after some time,\nno correct process ever suspected"]
EVENTUAL_W["Eventual Weak: after some time,\nsome correct process never suspected"]
end
end
STRONG --> PERFECT["Perfect Failure Detector\n(Strong Completeness + Strong Accuracy)\nRequires synchrony"]
EVENTUAL_S --> EVENTUALLY_PERFECT["Eventually Perfect ◇P\n(eventual strong both)\nSufficient for consensus!"]
EVENTUAL_W --> EVENTUALLY_WEAK["Eventually Weak ◇W\n(weakest detector for consensus)"]
Theorem (Chandra-Toueg): Consensus is solvable with failure detector class ◇W (eventually weak) even in asynchronous systems. This justifies why Zookeeper, etcd, and Raft can solve consensus despite the FLP impossibility — they use timeouts (implementing ◇P) and accept false suspicions temporarily.
Phi Accrual Failure Detector (used in Akka, Cassandra)¶
Instead of binary "alive/dead", outputs a suspicion level φ:
flowchart TD
HEARTBEATS["Inter-arrival intervals of heartbeats\n[t₁, t₂, t₃, ..., tₙ]"] --> STATS["Compute distribution:\nmean μ, std dev σ\n(exponential distribution assumed)"]
STATS --> PHI["φ(t_now) = -log₁₀(P(T > t_now))\nwhere T ~ Exponential(1/μ)"]
PHI -->|"φ < threshold (e.g. 8)"| ALIVE["Process considered ALIVE"]
PHI -->|"φ ≥ threshold"| SUSPECTED["Process SUSPECTED\n(application decides action)"]
NOTE["φ = 8 → P(false suspicion) ≈ 10⁻⁸\nφ = 10 → P(false suspicion) ≈ 10⁻¹⁰\nAdjust threshold for network conditions"]
8. Distributed Transactions: 2PC and 3PC Internals¶
Two-Phase Commit (2PC): Blocking Protocol¶
sequenceDiagram
participant COORD as Coordinator
participant P1 as Participant 1
participant P2 as Participant 2
participant P3 as Participant 3
Note over COORD: Phase 1: Voting
COORD->>P1: PREPARE (canCommit?)
COORD->>P2: PREPARE
COORD->>P3: PREPARE
P1-->>COORD: YES (logged PREPARED to WAL)
P2-->>COORD: YES
P3-->>COORD: NO (e.g., constraint violation)
Note over COORD: Any NO → ABORT
COORD->>P1: ABORT
COORD->>P2: ABORT
COORD->>P3: ABORT
Note over COORD,P3: BLOCKING SCENARIO:\nIf coordinator crashes after\nparticipants voted YES but before\nsending COMMIT/ABORT,\nparticipants are BLOCKED waiting forever
2PC blocking problem: A participant that has voted YES is uncertain — it cannot unilaterally abort (another participant may have committed) nor commit (another may have aborted). It must wait for the coordinator to recover.
Three-Phase Commit (3PC): Non-Blocking¶
3PC adds a pre-commit phase that allows participants to detect coordinator failure and safely commit:
sequenceDiagram
participant COORD as Coordinator
participant P1 as Participant 1
participant P2 as Participant 2
COORD->>P1: PREPARE
COORD->>P2: PREPARE
P1-->>COORD: VOTE_COMMIT
P2-->>COORD: VOTE_COMMIT
COORD->>P1: PRE-COMMIT ← NEW PHASE
COORD->>P2: PRE-COMMIT
P1-->>COORD: ACK
P2-->>COORD: ACK
COORD->>P1: COMMIT
COORD->>P2: COMMIT
Note over P1,P2: If coordinator crashes after PRE-COMMIT:\nnew coordinator queries participants.\nAll saw PRE-COMMIT → safe to COMMIT\n(all voted YES, none can abort)
Non-blocking scope: 3PC's non-blocking argument requires bounded-delay or failure-detection assumptions and no partition that makes states indistinguishable. A bound on the number of crashes alone is insufficient. The pre-commit phase does not make arbitrary partitioned executions safe.
9. Peer-to-Peer Overlay Networks: Chord DHT Internals¶
Consistent Hashing Ring¶
flowchart LR
subgraph RING["Chord Ring (m=6 bits, 2⁶=64 positions)"]
N0["Node 0\n(holds keys 57-0)"]
N14["Node 14\n(holds keys 1-14)"]
N32["Node 32\n(holds keys 15-32)"]
N45["Node 45\n(holds keys 33-45)"]
N51["Node 51\n(holds keys 46-51)"]
N57["Node 57\n(holds keys 52-57)"]
end
N0 --> N14 --> N32 --> N45 --> N51 --> N57 --> N0
Chord Finger Table: O(log N) Lookup¶
Each node n maintains a finger table where finger[i] = successor(n + 2^(i-1) mod 2^m):
flowchart TD
subgraph FT_N0["Finger Table of Node 0 (m=6)"]
F1["finger[1] = succ(0+1) = N14"]
F2["finger[2] = succ(0+2) = N14"]
F3["finger[3] = succ(0+4) = N14"]
F4["finger[4] = succ(0+8) = N14"]
F5["finger[5] = succ(0+16) = N32"]
F6["finger[6] = succ(0+32) = N45"]
end
subgraph LOOKUP["Lookup key k=40 from N0"]
STEP1["N0: finger[6]=N45 > 40? No → finger[5]=N32 > 40? No"]
STEP2["N0 forwards to N32"]
STEP3["N32: finger[6]=succ(32+32)=N0 → wrap. finger[5]=succ(32+16)=N51 > 40? Yes"]
STEP4["N32 forwards to N45"]
STEP5["N45: 40 ∈ (32, 45] → N45 is responsible. FOUND."]
end
FT_N0 --> LOOKUP
Lookup complexity: O(log N) hops with O(log N) finger table entries per node. When a node joins, it needs to update at most O(log² N) other nodes' finger tables.
10. Spanning Trees and Broadcast/Convergecast¶
Distributed algorithms for information dissemination rely on spanning trees — tree overlays connecting all processes with no cycles.
sequenceDiagram
participant ROOT as Root (P1)
participant P2 as P2
participant P3 as P3
participant P4 as P4 (leaf)
Note over ROOT: Broadcast: root sends message
ROOT->>P2: FLOOD(msg, P1)
ROOT->>P3: FLOOD(msg, P1)
P2->>P4: FLOOD(msg, P1) [P2 is P4's parent in tree]
Note over P4: Received — leaf node, no children
Note over P4,ROOT: Convergecast: aggregate result back up
P4-->>P2: RESULT(partial: subtree of P4)
P3-->>ROOT: RESULT(partial: subtree of P3)
P2-->>ROOT: RESULT(partial: subtree of P2 + P4)
Note over ROOT: Combine all results → global aggregate (min/max/sum)
Message complexity: Broadcast = O(n) messages on tree. Convergecast = O(n) messages. Building the spanning tree = O(m + n log n) messages (Gallagher-Humblet-Spira MST algorithm).
11. Byzantine Fault Tolerance: Dealing with Liars¶
In the classic unauthenticated Byzantine agreement model, tolerating up to f arbitrary faulty processes requires n ≥ 3f + 1. Authentication, synchrony, quorum and protocol assumptions can change the exact resilience statement.
sequenceDiagram
participant G as General (Commander)
participant L1 as Lieutenant 1 (loyal)
participant L2 as Lieutenant 2 (loyal)
participant T as Traitor
G->>L1: ATTACK
G->>L2: ATTACK
G->>T: ATTACK
T->>L1: (claims General said) RETREAT
T->>L2: (claims General said) ATTACK
Note over L1: Received: ATTACK (from G), RETREAT (from T)\nMajority of 3 loyal process values → ?
Note over L1,L2: With n=4, f=1: n ≥ 3(1)+1=4 ✓\nAlgorithm: OM(m) runs m+1 rounds\nwhere m = number of traitors\nEach loyal Lt eventually decides on majority vote
Why n ≥ 3f + 1: With n = 3f, the f traitors can confuse the 2f loyal generals into a tie. The extra f+1 loyal generals provide the decisive majority.
Practical BFT: PBFT Protocol¶
sequenceDiagram
participant CLIENT as Client
participant PRIMARY as Primary Replica
participant R1 as Replica 1
participant R2 as Replica 2
participant R3 as Replica 3 (faulty)
CLIENT->>PRIMARY: Request(op)
Note over PRIMARY: Phase 1: Pre-prepare
PRIMARY->>R1: PRE-PREPARE(v, n, digest(m))
PRIMARY->>R2: PRE-PREPARE(v, n, digest(m))
PRIMARY->>R3: PRE-PREPARE(v, n, digest(m))
Note over R1,R3: Phase 2: Prepare (multicast to all)
R1->>R2: PREPARE(v, n, digest, i=1)
R2->>R1: PREPARE(v, n, digest, i=2)
R3--xR1: PREPARE (faulty: wrong digest)
Note over R1: Received 2f PREPARE msgs → PREPARED
Note over R1,R3: Phase 3: Commit
R1->>R2: COMMIT(v, n, digest, i=1)
R2->>R1: COMMIT(v, n, digest, i=2)
Note over R1: 2f+1 COMMIT msgs → EXECUTE and REPLY to client
R1-->>CLIENT: Reply(result)
R2-->>CLIENT: Reply(result)
Note over CLIENT: Accept reply after f+1 identical replies\n(guarantees at least one reply from honest replica)
PBFT complexity: O(n²) messages per request (all replicas multicast to all). This limits PBFT to small replica counts (~dozens). Modern systems like HotStuff reduce this to O(n) via threshold signatures.
12. Distributed Shared Memory and Memory Consistency Models¶
flowchart TD
subgraph MODELS["Memory Consistency Models (weakest to strongest)"]
EVENTUAL["Eventual Consistency\nReplicas diverge temporarily\n→ eventually converge (DNS, DynamoDB)"]
CAUSAL["Causal Consistency\nCausally related writes seen in order\nConcurrent writes may differ (COPS)"]
SEQUENTIAL["Sequential Consistency\n(Lamport)\nAll processes see same total order\nNot necessarily real-time"]
LINEARIZABLE["Linearizability\n(Herlihy-Wing)\nSequential + real-time order\netcd, Zookeeper, Spanner"]
end
EVENTUAL --> CAUSAL --> SEQUENTIAL --> LINEARIZABLE
LINEARIZABLE -->|"Higher cost\n(requires coordination)"| PERF["Lower Performance"]
EVENTUAL -->|"Lower cost\n(async replication)"| PERF2["Higher Performance"]
CAP Theorem Internals¶
flowchart TD
CAP["CAP Theorem:\nIn the presence of network Partition,\nchoose Consistency OR Availability\n(but not both)"]
subgraph CA["CP Systems (Consistent + Partition-tolerant)"]
ETCd["etcd/ZooKeeper\n(Raft/Paxos)\nRejects writes if no quorum\n→ Unavailable during partition"]
SPANNER["Google Spanner\n(TrueTime + Paxos)\nStrongly consistent,\nbut can't serve during partition"]
end
subgraph AP["AP Systems (Available + Partition-tolerant)"]
CASSANDRA["Apache Cassandra\n(Eventual consistency)\nAlways accepts writes\nReads may see stale data"]
DYNAMO["Amazon DynamoDB\n(tunable consistency)\nSloppy quorums allow divergence"]
end
CAP --> CA
CAP --> AP
13. Complete Data Flow: Message Through a Distributed System¶
sequenceDiagram
participant CLIENT as Client
participant LB as Load Balancer
participant SVC_A as Service A (replica 1)
participant SVC_A2 as Service A (replica 2)
participant DB_COORD as DB Coordinator (Paxos leader)
participant DB_F1 as DB Follower 1
participant DB_F2 as DB Follower 2
CLIENT->>LB: HTTP Request
LB->>SVC_A: Forward (round-robin)
SVC_A->>DB_COORD: BEGIN TRANSACTION\nWRITE(key=k, val=v)
DB_COORD->>DB_F1: AppendEntries(log entry)
DB_COORD->>DB_F2: AppendEntries(log entry)
DB_F1-->>DB_COORD: ACK (quorum: 2/3)
DB_COORD->>DB_COORD: Commit (mark durable in WAL)
DB_COORD-->>SVC_A: COMMIT OK (with vector clock VC=[5,3,2])
SVC_A-->>CLIENT: 200 OK
Note over SVC_A2: Lagging replica\nVC=[5,2,2] (not yet seen entry)
CLIENT->>LB: Read same key
LB->>SVC_A2: Forward
SVC_A2->>DB_F2: READ(k)
DB_F2-->>SVC_A2: Old value (DB_F2 not yet applied commit)
Note over SVC_A2: Stale read — AP/eventual consistency\nFor strong consistency: SVC_A2 must\nread from leader (DB_COORD)
Summary: Complexity Landscape¶
block-beta
columns 3
H1["Algorithm"]:1 H2["Messages per Operation"]:1 H3["Synchronization Delay"]:1
R1["Lamport ME"]:1 M1["3(N-1)"]:1 D1["T (one round trip)"]:1
R2["Ricart-Agrawala"]:1 M2["2(N-1)"]:1 D2["T"]:1
R3["Maekawa Quorum"]:1 M3["5√N (with deadlock)"]:1 D3["2T"]:1
R4["Paxos Consensus"]:1 M4["4(N-1) typical"]:1 D4["2 round trips"]:1
R5["PBFT Byzantine"]:1 M5["O(N²)"]:1 D5["3 round trips"]:1
R6["Chord Lookup"]:1 M6["O(log N) hops"]:1 D6["O(log N) × T"]:1
R7["Chandy-Lamport Snapshot"]:1 M7["O(e) — one per channel"]:1 D7["Non-blocking"]:1
Stronger guarantees often add coordination, messages, or latency under the same fault and workload model, but batching, locality, hardware and weaker baseline implementations can change measured performance. The table is a model-specific comparison, not a universal ranking of etcd, Kafka, Cassandra, or ZooKeeper.