CMP-410 · Lesson 1 of 12 · 38 min
What makes distributed systems hard
A distributed system is a collection of independent computers that appears to its users as a single coherent system. That definition, from Tanenbaum and van Steen, captures both the promise and the difficulty. The promise is that many ordinary machines, connected by a network, can provide more capacity, more storage and more reliability than any one machine could. The difficulty is the word appears: the machines do not share memory or a clock, messages between them can be delayed, reordered or lost, and any machine can fail at any moment while the others carry on. Leslie Lamport put the problem memorably in a 1987 email: a distributed system is one in which the failure of a computer you did not even know existed can render your own computer unusable.
Every large service you use is a distributed system. A search query fans out to thousands of machines holding pieces of an index. A payment passes through an authorization service, a fraud model, a ledger and a notification system, each replicated across data centers. A chat message is written to a replicated log before your friend's phone sees it. This course is about how such systems are built so that they keep working, and keep giving correct answers, when parts of them fail. It builds on CMPUT-379, which covered processes, threads, locks and the operating system's view of one machine, and on CMPUT-313, which covered the network protocols that carry messages between machines. We will cite those courses rather than repeat them, and we will point to CMPUT-291 for single-node transactions, to CMPUT-404 for web architecture, to AI-480 for distributed training and to the DCE program for the physical side of data centers.
Throughout the course we follow one invented service, Ledgerline, a payments ledger that small businesses use to record invoices, payments and refunds. Ledgerline must never lose a recorded payment, must never record one twice, and must keep answering while machines, racks and even whole data centers fail. Each lesson adds a piece of its design, and the capstone in lesson 12 assembles them into a geo-distributed service.
Why distribute at all?
There are four main reasons to build a distributed system rather than buy a bigger computer. The first is scale: no single machine can store the web or serve a billion users, and beyond a point a bigger machine costs far more than several ordinary ones. The second is fault tolerance: a single machine is a single point of failure, while a replicated service survives the loss of some replicas. The third is latency: users are spread across the world, and light in fiber travels only about 200 km per millisecond, so a server near the user answers faster than one on another continent. The fourth is that some problems are inherently distributed: a bank's branches, a fleet of vehicles or a network of sensors are already in different places, and the system must connect them.
| Goal | What it means | Typical technique |
|---|---|---|
| Scalability | Handle more load by adding machines rather than replacing them | Partitioning data and work; stateless front ends |
| Fault tolerance | Keep working when components fail | Replication, retries, failover |
| Low latency | Answer quickly for users everywhere | Caching; replicas near users; content delivery networks |
| Availability | Answer requests a high fraction of the time | Redundancy and graceful degradation |
| Transparency | Hide distribution from users and, where sensible, from programmers | Naming, location-independent APIs, replication behind one interface |
Transparency deserves a comment. Early distributed systems research aimed to hide distribution completely, making a remote call look exactly like a local one. Experience showed that total transparency is neither achievable nor desirable. A 1994 paper by Waldo, Wyant, Wollrath and Kendall, A Note on Distributed Computing, argued that latency, memory access, partial failure and concurrency make remote operations fundamentally different from local ones, and that pretending otherwise produces fragile systems. Modern practice hides what can be hidden, such as where a replica is located, and exposes what cannot, such as the possibility that a call times out.
The fallacies of distributed computing
In the 1990s, engineers at Sun Microsystems, notably L. Peter Deutsch and later James Gosling, collected a list of false assumptions that programmers new to distributed systems tend to make. The list has become known as the eight fallacies of distributed computing. Each fallacy, if believed, leads to a characteristic kind of bug.
| Fallacy | Reality | Typical bug when ignored |
|---|---|---|
| The network is reliable | Packets and connections are lost; switches fail; cables are cut | A call hangs forever with no timeout |
| Latency is zero | A cross-continent round trip takes tens of milliseconds | A loop that makes one remote call per item is unusably slow |
| Bandwidth is infinite | Links saturate, especially between data centers | Replication traffic starves user traffic |
| The network is secure | Traffic can be observed or altered | Unauthenticated internal services are exploited |
| Topology does not change | Machines, routes and addresses change constantly | Hard-coded addresses break after a migration |
| There is one administrator | Many teams and organizations control parts of the path | A firewall change elsewhere silently blocks traffic |
| Transport cost is zero | Serialization, encryption and cross-region traffic cost CPU and money | Chatty protocols dominate the cloud bill |
| The network is homogeneous | Different hardware, operating systems and protocol versions coexist | A rolling upgrade breaks because two versions disagree on a message format |
The first fallacy is the most important for this course. Lesson 11 of CMPUT-313 explained how TCP provides reliable, ordered delivery over an unreliable network, but TCP's guarantee is limited: it delivers bytes in order while the connection lasts. When a connection breaks, the sender cannot know which of its last messages arrived and were acted upon. Every protocol in this course must cope with that uncertainty.
Partial failure and the problem of not knowing
On a single computer, failure is usually total: if the power goes, every process stops together, and when the machine restarts it can examine a consistent state on disk. In a distributed system, failure is partial. One machine crashes while others run; a network link fails so that some machines can talk to each other and others cannot; a machine pauses for several seconds during garbage collection and then resumes as though nothing happened. The defining difficulty is that a machine generally cannot tell which of these has happened. If node A sends a request to node B and receives no reply, the possibilities include: the request was lost; B crashed before processing it; B processed it and crashed before replying; B is processing it slowly; the reply was lost; or the reply is merely delayed. From A's point of view these are indistinguishable. A can wait longer or give up, but it cannot know.
This leads to the central technique of the field, the timeout, and to its central weakness. A timeout converts the absence of a message into a suspicion of failure. If the timeout is short, A will often suspect B wrongly, while B is merely slow; if it is long, A will take a long time to react to real failures. No choice of timeout is correct in all circumstances, and much of the cleverness in later lessons, from failure detectors in lesson 11 to leader election in lesson 9, is about acting safely on suspicions that may be wrong.
System models
To reason about distributed algorithms we need precise assumptions about what can go wrong. A system model specifies assumptions about timing and about failures. The choice of model determines which problems are solvable at all: some problems that are easy in one model are provably impossible in another.
Timing assumptions come in three main forms. In the synchronous model there are known upper bounds on message delay, on the time a process takes for a step, and on clock drift. Algorithms can then use timeouts with certainty: if no reply arrives within the bound, the sender has definitely crashed. In the asynchronous model there are no bounds at all; messages are eventually delivered, but may take arbitrarily long. This is the safest model to design for, because an algorithm correct in it is correct whatever the timing, but it is also the most restrictive: lesson 8 shows that consensus cannot be solved deterministically in an asynchronous system if even one process may crash. The partially synchronous model, introduced by Dwork, Lynch and Stockmeyer in 1988, sits between: the system behaves asynchronously for an unknown period but eventually respects bounds that are themselves unknown. This is the model that most practical protocols, including Paxos and Raft, assume for progress, while relying only on asynchrony for safety.
Failure assumptions form a hierarchy. In the crash-stop model a process works correctly until it halts, and never returns. In the crash-recovery model a process may halt and later restart, losing its memory but keeping whatever it wrote to stable storage; this is the realistic model for servers that reboot. In the omission model a process may fail to send or receive some messages. In the Byzantine model, named after Lamport, Shostak and Pease's 1982 paper on the Byzantine generals, a faulty process may behave arbitrarily, including lying, sending conflicting messages to different peers or colluding with other faulty processes. Byzantine faults model bugs, corrupted hardware and malicious participants. Tolerating them costs much more: lesson 8 shows that crash-tolerant consensus needs \(2f + 1\) replicas to survive \(f\) failures, while Byzantine-tolerant consensus needs \(3f + 1\).
| Model | What a faulty process may do | Replicas needed to tolerate f faults (consensus) | Where it is assumed |
|---|---|---|---|
| Crash-stop | Stop permanently | 2f + 1 | Textbook algorithms; short-lived processes |
| Crash-recovery | Stop, then restart from stable storage | 2f + 1, if state is persisted before replying | Raft, Paxos, ZooKeeper, most databases |
| Omission | Drop some messages it should send or receive | 2f + 1 with retransmission | Lossy networks, overloaded servers |
| Byzantine | Anything, including lying and collusion | 3f + 1 | Blockchains, some aerospace and financial systems |
Networks have failure modes too. A network partition splits the machines into groups that can communicate within a group but not between groups. Partitions happen more often than intuition suggests: a 2014 study by Bailis and Kingsbury collected dozens of publicly reported partitions caused by switch failures, misconfigurations and software bugs, and cloud providers report them regularly. A partition is especially dangerous because each side may believe the other has crashed and carry on independently, the situation called split brain. Lesson 7's CAP theorem is fundamentally a statement about what a system can promise during a partition.
Scale makes rare events common
A component that fails once every three years seems reliable. In a fleet of a hundred thousand machines, it fails about ninety times a day. Large operators therefore design on the assumption that something is always broken. The next program makes this concrete.
# Failures are normal at scale: expected failures per day in a fleet of machines.
import math
def p_at_least_one(p_each, n):
return 1 - (1 - p_each) ** n
# Suppose a single server has a 3-year mean time between failures (MTBF).
mtbf_days = 3 * 365
p_day = 1 - math.exp(-1 / mtbf_days) # chance one server fails on a given day
print(f"one server fails on a given day with probability {p_day:.5f}")
for n in (10, 100, 1000, 10000, 100000):
expected = n * p_day
print(f"{n:6d} servers: expected failures/day = {expected:7.2f}, "
f"P(at least one today) = {p_at_least_one(p_day, n):6.1%}")one server fails on a given day with probability 0.00091
10 servers: expected failures/day = 0.01, P(at least one today) = 0.9%
100 servers: expected failures/day = 0.09, P(at least one today) = 8.7%
1000 servers: expected failures/day = 0.91, P(at least one today) = 59.9%
10000 servers: expected failures/day = 9.13, P(at least one today) = 100.0%
100000 servers: expected failures/day = 91.28, P(at least one today) = 100.0%A related effect concerns latency rather than failure. Jeffrey Dean and Luiz André Barroso described it in The Tail at Scale (2013). When a user request fans out to many servers in parallel, as a search query does, the request completes only when the slowest server replies. If each server is slow on 1 percent of requests, a request that touches one server is slow 1 percent of the time, but one that touches a hundred servers is slow 63 percent of the time. Rare slowness in a component becomes common slowness for the system.
# The "tail at scale": a request that fans out to many servers waits for the slowest one.
import random
random.seed(450)
def server_latency_ms():
# Most replies take about 10 ms; 1 in 100 hits a slow path (garbage collection,
# a busy disk, a queue behind a large request) and takes about 1 second.
if random.random() < 0.01:
return random.uniform(800, 1200)
return random.gauss(10, 2)
def request(fanout):
# The user-facing request finishes only when every sub-request has replied.
return max(server_latency_ms() for _ in range(fanout))
TRIALS = 20000
print("fan-out median ms p99 ms share of requests over 500 ms")
for fanout in (1, 10, 100, 1000):
lat = sorted(request(fanout) for _ in range(TRIALS // max(1, fanout // 10)))
n = len(lat)
slow = sum(1 for x in lat if x > 500) / n
print(f"{fanout:7d} {lat[n // 2]:11.1f} {lat[int(n * 0.99)]:8.1f} {slow:8.1%}")
# The same effect computed exactly: P(at least one slow reply) = 1 - 0.99^fanout
for fanout in (1, 10, 100, 1000):
print(f"exact P(slow) for fan-out {fanout:4d}: {1 - 0.99 ** fanout:.1%}")fan-out median ms p99 ms share of requests over 500 ms
1 10.0 16.9 0.9%
10 13.2 1162.0 9.4%
100 924.0 1197.0 63.6%
1000 1174.6 1199.7 100.0%
exact P(slow) for fan-out 1: 1.0%
exact P(slow) for fan-out 10: 9.6%
exact P(slow) for fan-out 100: 63.4%
exact P(slow) for fan-out 1000: 100.0%The simulation shows the tail moving first. With one server the 99th percentile is about 17 ms; with ten servers the median is still about 13 ms but the 99th percentile is over a second; with a hundred servers even the median request waits for a slow reply, and the simulated share of slow requests matches the exact value \(1 - 0.99^{n}\). Dean and Barroso described techniques to tame the tail: hedged requests, which send a second copy of a request to another replica if the first has not replied within, say, the 95th-percentile latency, and then use whichever reply arrives first; tied requests, which send to two replicas at once and let the second cancel itself when the first starts work; and micro-partitioning, so that slow servers can shed load quickly. These are distributed systems techniques in miniature: they accept that individual components misbehave and engineer the whole to behave well anyway.
Safety and liveness
Correctness properties of distributed algorithms are usually split into two kinds, a distinction formalized by Lamport and by Alpern and Schneider. A safety property says that something bad never happens: two nodes never both believe they are leader in the same term; a payment is never recorded twice; a read never returns a value that was never written. A liveness property says that something good eventually happens: every request eventually receives a reply; a new leader is eventually elected after a crash. Safety properties can be violated at a specific moment, and the violation cannot be undone. Liveness properties can only be violated over an infinite execution, because there is always hope that the good thing will happen later.
The distinction matters because the two kinds of property respond differently to bad conditions. Well-designed protocols keep safety under all conditions, however asynchronous the network or however many timeouts fire wrongly, and guarantee liveness only when conditions are reasonably good: a majority of nodes are up and messages are delivered within some bound for long enough. This is exactly the design of Paxos and Raft. When you read a protocol, the first questions to ask are which properties are safety properties, which are liveness properties, and under what assumptions each holds.
Building blocks and a map of the course
Distributed systems are built from a small number of recurring ideas, which the rest of the course develops. Lesson 2 covers communication: remote procedure calls, messaging, timeouts, retries and idempotency. Lesson 3 covers time: why physical clocks cannot be trusted for ordering, and how logical clocks capture causality. Lesson 4 uses those clocks to record consistent global states and to coordinate, through mutual exclusion and leader election. Lesson 5 covers distributed transactions, where several machines must commit or abort together. Lessons 6 and 7 cover replication and the consistency models that describe what replicated data looks like to clients, including the CAP theorem. Lessons 8 and 9 cover consensus, the problem at the heart of fault-tolerant replication, through Paxos and Raft. Lesson 10 covers partitioning and scaling. Lesson 11 covers fault tolerance in practice: failure detection, resilience patterns and site reliability engineering. Lesson 12 assembles the pieces into a geo-distributed design for Ledgerline.
One more piece of orientation. Distributed systems is a field where informal reasoning fails often and expensively. Protocols that look obviously correct contain subtle bugs that appear only under rare interleavings of messages and failures. Practitioners therefore lean heavily on precise specifications, on proofs, on model checking with tools such as TLA+, on deterministic simulation testing, and on adversarial testing that injects partitions and crashes, such as Kyle Kingsbury's Jepsen analyses. We will use small, seeded simulations in Python throughout the course for the same reason: a simulation lets us watch an algorithm under failures that would be hard to arrange on real machines, and replay exactly the same scenario.
Worked examples
Worked problem
Diagnosing a missing reply
Ledgerline's invoice service sends "record payment 812 of 40 dollars" to the ledger and gets no reply within its 2-second timeout. List what may have happened, and say why simply resending the request could be harmful.
Show solution
- The request may have been lost, so the ledger never saw it.
- The ledger may have crashed before or after applying the payment.
- The ledger may have applied it and the reply may have been lost or delayed.
- The ledger may simply be slow, for example during a long garbage-collection pause.
- If the payment was applied and the invoice service resends, the ledger may record it twice unless the request carries an identifier the ledger can recognize.
Answer: The sender cannot distinguish these cases. Resending is safe only if the operation is idempotent or deduplicated by a unique request identifier, which lesson 2 develops.
Worked problem
Fan-out and the tail
A dashboard request queries 50 shards in parallel. Each shard answers within 20 ms on 99.5 percent of requests. What fraction of dashboard requests take longer than 20 ms?
Show solution
- The request is fast only if all 50 shards are fast: \(0.995^{50}\).
- \(0.995^{50} \approx e^{50 \ln 0.995} \approx e^{-0.2506} \approx 0.778\).
- So about \(1 - 0.778 = 0.222\) of requests wait for at least one slow shard.
Answer: About 22 percent of dashboard requests exceed 20 ms, although each shard is slow only 0.5 percent of the time.
Worked problem
Safety or liveness?
Classify: (a) "no two replicas apply different payments at log position 17"; (b) "every recorded payment is eventually visible at every replica"; (c) "a client never sees its own payment disappear after it was acknowledged".
Show solution
- (a) A violation happens at a specific moment and cannot be undone: safety.
- (b) It promises something good eventually: liveness.
- (c) A violation is a specific bad event, a read that misses an acknowledged write: safety.
Answer: (a) and (c) are safety properties; (b) is a liveness property.
Common mistakes
MistakeTreat a remote call as a slow local call.
InsteadRemote calls can fail partially, time out with an unknown outcome, and interleave with other clients; design for these cases explicitly.
MistakeAssume a missing reply means the request was not processed.
InsteadThe request may have been processed and only the reply lost; use idempotent operations or request identifiers.
MistakeChoose a timeout once and forget it.
InsteadTimeouts trade false suspicions against slow detection; base them on measured latency and revisit them as load changes.
MistakeAssume rare failures can be ignored.
InsteadAt scale, a once-in-three-years failure per machine becomes many failures per day; failure handling is part of normal operation.
MistakeDesign a protocol that is safe only when the network behaves.
InsteadKeep safety under all timing conditions and require good conditions only for liveness, as Paxos and Raft do.
Summary and key terms
- A distributed system is a set of independent computers that appears to users as one coherent system; it has no shared memory or global clock.
- Systems are distributed for scale, fault tolerance, low latency and because some problems are inherently spread out.
- The eight fallacies, starting with "the network is reliable", describe assumptions that lead to characteristic bugs.
- Partial failure means a node cannot distinguish a crashed peer from a slow one or a lost message; timeouts turn silence into suspicion.
- System models specify timing (synchronous, partially synchronous, asynchronous) and failures (crash-stop, crash-recovery, omission, Byzantine).
- At scale, rare failures and rare slowness become common; fan-out multiplies tail latency.
- Safety properties say nothing bad happens; liveness properties say something good eventually happens. Good protocols keep safety always and liveness under reasonable conditions.
- Distributed system
- Independent computers that cooperate over a network and appear to users as a single system.
- Partial failure
- A failure of some components while others continue to run, often undetectable with certainty.
- Timeout
- A limit on how long to wait for a message, after which the sender suspects a failure.
- Asynchronous model
- A system model with no bounds on message delay or processing speed.
- Partial synchrony
- A model in which timing bounds hold only after an unknown point in time.
- Crash-recovery
- A failure model in which processes may stop and later restart with their stable storage intact.
- Byzantine failure
- A failure in which a process may behave arbitrarily, including maliciously.
- Network partition
- A failure that splits nodes into groups that cannot communicate with each other.
- Tail latency
- The latency of the slowest requests, such as the 99th percentile.
- Safety property
- A property stating that something bad never happens.
- Liveness property
- A property stating that something good eventually happens.
Practice problems
Work each problem on paper first, then reveal the answer to check.
Give three reasons to build a distributed system rather than use one large computer.
Show answer
Scale beyond one machine, tolerance of individual failures, and lower latency for users in different places.
Why is total transparency of distribution undesirable?
Show answer
Latency and partial failure make remote operations fundamentally different; hiding them produces fragile programs.
After a timeout, what are the possible states of a request?
Show answer
Not received, received and not processed, processed with the reply lost or delayed, or still being processed.
In which model can a timeout prove that a process has crashed?
Show answer
The synchronous model, where message delays and processing times are bounded.
How many replicas are needed to tolerate two crash failures in consensus? Two Byzantine failures?
Show answer
Five (2f + 1) and seven (3f + 1).
What is split brain?
Show answer
Two parts of a partitioned system each acting as though the other has failed, for example by both accepting writes as leader.
If each of 200 servers is slow on 0.1 percent of requests, what fraction of requests that touch all 200 are slow?
Show answer
About \(1 - 0.999^{200} \approx 18\) percent.
What is a hedged request?
Show answer
A duplicate request sent to another replica after a short delay; the first reply wins.
Is "a leader is eventually elected" a safety or liveness property?
Show answer
Liveness.
Why do practitioners use model checking and simulation for distributed protocols?
Show answer
Bugs appear only under rare interleavings of messages and failures that informal reasoning and ordinary testing miss.
Lesson quiz · 5 questions
Score 60% or more to complete this lesson.
Create a free account to save your progress, take quizzes and earn a certificate.