Overview
Imagine the store you will build in this course running as a single Spring Boot application with one PostgreSQL database on one machine. It is simple to reason about β until traffic grows beyond what that machine can handle, or the machine dies and the whole store goes down with it. The moment you split the work across several machines (a gateway, an account service, an order service, a database, a cache, a message broker), you have built a distributed system, and a new class of problems appears.
Distributed systems represent a computing paradigm where multiple independent computers collaborate to achieve a common goal, appearing as a single coherent system to the end-user3. Unlike centralised systems where all processing occurs on one machine, distributed systems span networks and leverage interconnected nodes (a node is any independent computer or process taking part in the system) to handle data storage, computation, and communication.
This architecture underpins modern cloud platforms (AWS, Google Cloud, Azure), large-scale applications (social networks, e-commerce), and scientific simulations. The foundational challenge is that nodes operate independently, communicate only by passing messages, and have no shared clock β making coordination a non-trivial engineering problem.
Learning objectives
By the end of this class you should be able to:
- Explain what makes a system distributed and why partial failure, unreliable networks and the absence of a global clock make it hard.
- Identify the 8 fallacies of distributed computing in a concrete design, such as a call between two microservices.
- Explain how logical clocks (Lamport) order events without a shared physical clock.
- Choose between consistency and availability for a given data domain using CAP and PACELC.
- Describe how Raft elects a leader and replicates a log with majority quorums, and size a cluster for a given number of failures.
- Compare strong consistency (quorums, consensus) with eventual consistency (LWW, vector clocks, CRDTs).
Roadmap
- This overview β what a distributed system is, why we build them, what they cost, and the three root problems (partial failure, unreliable network, no global clock).
- CAP Theorem β the consistency-versus-availability choice during a network partition, and its PACELC extension.
- Consensus & Algorithms β how nodes agree on a value despite failures: FLP, failure models, Paxos, Raft, quorums and CRDTs.
- Quiz β check your understanding.
Slides
Click the slides and use β β to move through them; F goes full screen and P opens the presenter view, with the notes. Open the slides in a new tab
Our store is a distributed system
A single click on "Place order" in the store already crosses the network several times. Follow the request:
flowchart LR
user([Browser]) -->|POST /orders| gw[Gateway]
gw -->|validate token| auth[Auth service]
gw -->|forward| order[Order service]
order -->|check stock| product[Product service]
product -->|cached price| redis[(Redis)]
order -->|INSERT order| pg[(PostgreSQL)]
order -->|OrderCreated| kafka[[Kafka]] Each arrow is a message over the network, and each box is a process that can fail on its own. If the product service is slow, the order service waits; if Kafka is down, the order may be stored but the event never published. Nothing in this picture shares memory or a clock β the services cooperate only by exchanging messages. That is exactly the definition of a distributed system, and it is why the rest of this class matters for the project.
Classification
Distributed systems can be classified along several axes:
| Dimension | Option A | Option B |
|---|---|---|
| Hardware/software uniformity | Homogeneous β all nodes run the same stack | Heterogeneous β diverse hardware and OS |
| Coupling | Tightly coupled β high interdependence, shared memory | Loosely coupled β communicate via messages, independent lifecycle |
| Topology | Client-server β hierarchical, servers hold resources | Peer-to-peer β egalitarian, all nodes share resources |
Web applications follow the client-server model (browsers request from servers). File-sharing systems like BitTorrent follow peer-to-peer. Most microservice architectures occupy the loosely-coupled middle ground: our store is heterogeneous (Java services, a Python notification service, PostgreSQL, Redis, Kafka), loosely coupled (services talk over HTTP and Kafka) and client-server (the browser calls the gateway, the gateway calls the services).
Advantages
| Benefit | How |
|---|---|
| Horizontal scalability | Add nodes instead of upgrading a single machine. Cassandra scales to petabytes by adding nodes seamlessly. |
| Fault tolerance | Replication across nodes keeps the system running despite individual failures. Google Spanner uses synchronous cross-datacenter replication. |
| Resource sharing | Idle capacity from many machines is pooled. Folding@home aggregates CPU cycles for scientific workloads. |
| Parallelism | Tasks are divided and processed concurrently. MapReduce (Hadoop) exemplifies this for big-data analytics. |
| Latency reduction | Nodes placed near users reduce round-trip time. CDNs like Akamai serve content from the nearest edge location. |
In the store, horizontal scalability is what Hands-on 7 does with Nginx in front of several replicas of a service, and latency reduction is what the Redis cache of Hands-on 8 provides.
Disadvantages
| Challenge | Root cause |
|---|---|
| Complexity | Coordination, synchronisation, and distributed state are hard to reason about and implement correctly. |
| Network overhead | Messages can be delayed, lost, or duplicated. Every failure mode must be handled explicitly. |
| Consistency vs availability | During a network partition, a replicated data store cannot be both consistent and available β see the CAP Theorem. |
| Expanded attack surface | More nodes mean more entry points. Byzantine faults (malicious nodes) require dedicated protocols. |
| Operational cost | Debugging, monitoring, and deploying distributed systems requires specialised tooling (Prometheus, Kubernetes, distributed tracing). |
The 8 Fallacies of Distributed Computing
L. Peter Deutsch and colleagues at Sun Microsystems2 listed the assumptions developers commonly make when building distributed systems β all of them wrong (the list had seven items in 1994; James Gosling added the eighth, the network is homogeneous, in 1997):
| Fallacy | Reality | In the store |
|---|---|---|
| The network is reliable | Packets are dropped, links fail, cables are cut | The call from order to product fails halfway |
| Latency is zero | Network round-trips add milliseconds; cross-region adds hundreds | Five sequential calls of 40 ms each make a 200 ms checkout |
| Bandwidth is infinite | Transferring large payloads saturates links | Returning the whole catalogue in one response |
| The network is secure | Traffic must be encrypted and authenticated at every hop | Services trust any caller inside the cluster |
| Topology doesn't change | Nodes go up and down; IPs change; services are rescheduled | A Kubernetes pod is rescheduled with a new IP |
| There is one administrator | Multiple teams, multiple cloud accounts, multiple policies | Another team changes a firewall rule |
| Transport cost is zero | Serialisation, compression, and egress fees are real | JSON parsing and cross-zone traffic on the cloud bill |
| The network is homogeneous | Different systems speak different protocols and encodings | Java services, a Python service, Kafka's binary protocol |
Design for failure, not against it
These fallacies share a common root: treating the network as a reliable local function call. The correct posture is to assume failure will happen and design every interaction to degrade gracefully.
The three root problems
Almost every difficulty in this course traces back to three facts.
1. Partial failure
On a single machine, a program either works or crashes as a whole. In a distributed system, part of the system can fail while the rest keeps running: the product service is down but the gateway and the order service are fine. Each component must decide what to do when one of its dependencies stops answering.
2. An unreliable network: slow or dead?
When the order service sends a request and gets no reply, it cannot know what happened. Consider the possibilities:
sequenceDiagram
participant O as Order service
participant P as Product service
O->>P: reserve 1 unit of product 42
Note over O,P: request lost? P crashed? reply lost? P just slow?
O-->>O: timeout after 2 s
O->>P: retry: reserve 1 unit of product 42
P-->>O: OK (but was the first one applied too?) From the caller's point of view, a lost request, a crashed server, a lost reply and a very slow server all look the same: silence. The only practical tool is a timeout β after a deadline, assume failure. But a timeout does not tell you whether the operation was executed, so a retry may apply it twice. That is why operations that may be retried must be idempotent (applying them twice has the same effect as applying them once β for example, by sending a unique request id that the server deduplicates). Timeouts, retries with backoff and circuit breakers are covered in Resilience Patterns.
3. No global clock
Each machine has its own quartz clock, and clocks drift: two servers synchronised with NTP (Network Time Protocol) can still disagree by milliseconds, and much more after a misconfiguration or a VM pause. So you cannot reliably order two events on different machines by comparing their wall-clock timestamps β an order created "at 10:00:00.120" on one node may actually have happened after a cancellation stamped "10:00:00.150" on another.
Time and ordering: logical clocks
Leslie Lamport1 observed that, for most purposes, we do not need the real time of events β we need their order, and specifically the happened-before relation (β):
- if a and b happen in the same process and a comes first, then a β b;
- if a is the sending of a message and b its receipt, then a β b;
- if a β b and b β c, then a β c.
Two events where neither happened-before the other are concurrent: neither could have influenced the other.
A Lamport clock is a counter kept by each process, updated with three rules:
- Before each local event, increment the counter.
- When sending a message, attach the current counter value.
- When receiving a message with value t, set the counter to
max(local, t) + 1.
sequenceDiagram
participant A as Order service
participant B as Kafka consumer
participant C as Notification
Note over A: create order (L=1)
A->>B: OrderCreated (L=2)
Note over B: receive: max(0,2)+1 = 3
Note over C: local event (L=1)
B->>C: SendEmail (L=4)
Note over C: receive: max(1,4)+1 = 5 The guarantee is: if a β b then L(a) < L(b). The converse is not true β a smaller Lamport timestamp does not prove that one event caused the other. Vector clocks fix this by keeping one counter per process; comparing two vectors tells you whether one event happened before the other or whether they are concurrent. They are used to detect conflicting writes in replicated databases (see Eventual consistency).
Where you will meet ordering in the course
Kafka does not use wall-clock time to order messages: it assigns each message a monotonically increasing offset inside a partition, so all consumers see the same order for that partition (Hands-on 9). Raft uses a logical counter called a term to tell old leaders from new ones (Consensus).
Do not order events by System.currentTimeMillis() across machines
Comparing timestamps produced by different servers is a classic source of bugs, for example in "last write wins" conflict resolution: a node with a fast clock silently wins every conflict. Use a single authority for ordering (a database sequence, a Kafka partition offset) or logical clocks.
Topics covered
-
CAP Theorem
The central trade-off in distributed data stores: Consistency, Availability, and Partition Tolerance cannot all be guaranteed simultaneously. Understand CP, AP, and CA system classes β and the PACELC extension.
-
Consensus & Algorithms
How distributed nodes reach agreement despite failures. Covers Paxos, Raft, quorum systems, failure models (crash-stop, Byzantine), the FLP impossibility result, and eventual consistency.
Connections
- Microservices β splitting the store into services is what turns it into a distributed system.
- Event-Driven Architecture and Hands-on 9 β asynchronous messaging with Kafka, ordered by partition offsets.
- Reliability β timeouts, retries, circuit breakers: designing for partial failure.
- Hands-on 7 and Hands-on 8 β horizontal scaling behind a load balancer, and a Redis cache.
- Hands-on 10 β distributed tracing, to see one request crossing many services.
Key takeaways
- A distributed system is a set of independent nodes that cooperate only by exchanging messages and have no shared clock; our store is one as soon as it has more than one service.
- We distribute for scalability, fault tolerance and latency β and pay in complexity, network failures and operational cost.
- The 8 fallacies are all false: design every remote call for failure.
- A caller cannot tell a slow node from a dead one: use timeouts, and make retried operations idempotent.
- Physical clocks drift; order events with a single authority or with logical clocks (Lamport, vector clocks).
-
LAMPORT, L. Time, Clocks, and the Ordering of Events in a Distributed System. CACM, 1978.Β β©
-
DEUTSCH, P. Fallacies of Distributed Computing, 1994.Β β©
-
VAN STEEN, M.; TANENBAUM, A. S. Distributed Systems, 4th ed., 2023.Β β©