Part Five

The Modern
Machine

Parts I through IV built one computer. A single CPU, with its kernel, speaking through one network stack to one TLS endpoint, holding bytes in one relational database, defended by a layered stack we can now name. Part V steps back. The machine that runs nearly all code in 2026 is many processors working as one: sixteen cores in your laptop, sixty-four in a cloud server, and beyond them thousands of machines in a region, tied together by software that must reach agreement despite delay and failure. Part V is about that scaling, mathematically and physically. Then Chapter 18 closes the book.

CHAPTER 16
Parallelism: When One Core Isn't Enough
Threads vs processes. Race conditions and the locks that prevent them. Lock-free programming and the hard math of memory ordering. GPU architecture. Amdahl's law and Gustafson's law. NUMA, MESI, the modern hardware reality.
CHAPTER 17
The Cloud: Distributed Systems at Scale
Virtualization. Containers revisited. Kubernetes and the control loop. Distributed consensus: Paxos, Raft, the FLP impossibility. The CAP theorem. Microservices, serverless, and the architectural pendulum.
CHAPTER 18
Epilogue: What You Now See
The closing synthesis. The whole arc in one view. The computer as a mathematical object. From sand to civilization. What's permanent and what isn't. Where computer science intersects with everything else. The reading list. The final note.
Chapter 16

Parallelism
When One Core
Isn't Enough

For most of computing's history, faster software meant faster clocks. Around 2004 the clock speed of consumer CPUs stopped climbing at its old pace (heat density made each further step too costly), and the industry pivoted to putting many cores on the same chip and asking software to use them. Twenty years later, every phone has six to ten cores, every laptop has eight to sixteen, and every cloud server has sixty-four or more. Writing code that uses them correctly is hard, because its bugs depend on timing and so hide in testing and appear under load.

TopicsThreads · races · CAS · GPU · Amdahl · NUMA · MESI
Era covered1967 → present
Cluster1 machine · 8 cores · one shared memory
Chapter 16 hero · Parallelism When One Core Isn't Enough 8 × 5 = 40 CORES all running at once · all sharing memory faster software is no longer faster clocks · it's more cores
01 · Threads vs processes

The two ways to make your program use more than one core.

By 1990, the kernel had given UNIX programmers a clean answer to "how do I run two things at once": fork. The child process ran in parallel with the parent, with its own address space, its own file descriptors, its own everything (Chapter 4). That separation was exactly the problem when the two processes wanted to cooperate. A parent that wanted to share a million-element array with its child had to copy it across the kernel boundary with every message, or set up by hand a region of memory both could see. POSIX 1003.1c, the threads part of the POSIX standard for UNIX-style systems, ratified in 1995, codified the alternative, the thread: a separate stream of execution that can be scheduled onto a different core and shares the address space of its parent process, so it can read and write the same memory directly. Threads are the cheap, fast, dangerous answer to "how do I use more than one core"; processes are the slow, safe one. The next thirty years of concurrent programming are about the price of the danger.

Fig 16.1 · Threads vs processes · the same parallelism, two isolation models
Fig 16.1 · Threads vs processes · the same parallelism, two isolation models Left: one process with three threads, each with its own stack, all sharing one memory that holds x = 0. Thread 1 writes x = 5 and threads 2 and 3 read 5 at once: fast, with no copy, but a bug in one thread can damage all of them. Right: three processes, each with its own memory. Process A writes x = 5 while B and C still hold 0 until A sends a message through the kernel, by pipe or socket: walls keep them safe, but every share is a copy. threads: one memory shared memory x = 0 x = 5 t1 stack write 5 t2 stack reads 5 t3 stack reads 5 processes: own memory A stack x = 0 x = 5 B stack x = 0 x = 5 still 0 got it C stack x = 0 x = 5 still 0 got it kernel: pipe, socket send fast: no copy one bug hits all safe: walls every share is a copy

Threads share one memory, so a value one writes the others can read at once, and a bug in one can damage all of them. Processes each have their own memory: nothing passes until one sends a message through the kernel, which is slower but walls each one off.

How it works
  1. What is shared. Threads share the heap, globals and open files; each has its own stack and registers. Processes share nothing unless they set up shared memory or talk through the kernel.
  2. The costs. Creating a thread or switching between threads of one process is cheaper than doing the same for processes, which need their own page tables (Chapter 4).
  3. Who uses which. C, C++, Java and Rust default to threads; browsers put each site in its own process for safety.

Where parallelism actually helps

Not every program benefits from more cores. Programs whose work naturally divides into independent chunks (image filters processing pixels, web servers handling unrelated requests, simulations that run many independent random trials) are called embarrassingly parallel and scale almost linearly with core count. Programs whose steps depend on each other benefit much less, or not at all. Amdahl's law (Section 05) gives the mathematics; the practical point is that adding cores is no substitute for serial speed. A 64-core server runs a purely sequential program no faster than a 1-core machine with the same clock speed.

02 · Race conditions and locks

Two threads · one variable · everything depends on luck.

The price of shared memory is the lost addition of Chapter 4, now between threads: it shows up the first time two threads modify the same variable concurrently. Two threads each increment a shared counter a million times. The expected result is two million. The actual result is nearly always well short of it and changes from run to run, depending on how the threads happen to be scheduled. In source code the increment counter++ looks atomic (one indivisible step), but underneath it is three operations: read the value into a register, add one, write it back. If two threads' three-step sequences overlap, the writes can stomp on each other. That is a race condition: the result depends on which thread gets there first.

Fig 16.2 · The classic race · two threads, one counter, lost updates
Fig 16.2 · The classic race · two threads, one counter, lost updates counter++ is three steps: load, add one, store. With the counter at 100, thread 1 loads 100, then thread 2 loads 100 too. Thread 1 adds and stores 101. Thread 2 adds to its own stale copy and stores 101 as well. Two increments ran, and the counter rose by one: expected 102, got 101. counter++ is three steps: load it, add 1, store it thread 1 counter thread 2 load 100 load 100 add 101 store 101 → add 101 ← store 101 100 100 100 101 101 101 lost two increments, +1: expected 102, got 101

counter++ looks like one step but is three: load, add, store. When thread 2 loads before thread 1 has stored, both add one to the same 100 and both store 101, so one increment is lost.

How it works
  1. Why it hides. The bug depends on which instructions interleave, so it rarely shows in testing and appears under load.
  2. The fixes. Make the read-modify-write one indivisible step (an atomic increment, Fig 16.4) or let only one thread in at a time (a mutex, Fig 16.3).

The mutex · serialise the critical section

The classic fix is a mutex (mutual exclusion lock; Chapter 4's semaphore set to 1 is one). Each thread, before touching the shared counter, calls mutex.lock(); after touching it, calls mutex.unlock(). Only one thread can hold the lock at a time; any other thread that calls lock() blocks until the holder releases. The region between lock and unlock is the critical section, and all critical sections that touch the same data are serialised. The mutex itself is built on atomic CPU instructions (such as compare-and-swap, Section 03), which the hardware guarantees are indivisible.

C++: the bug and the fix
// the race (an unoptimised build shows it): counter ends well short of 2,000,000, and differs from run to run
int counter = 0;
void worker() {
    for (int i = 0; i < 1'000'000; ++i)
        ++counter;                  // three steps: LOAD; ADD; STORE
}

// the fix: std::mutex serialises the critical section
std::mutex m;
int counter = 0;
void worker() {
    for (int i = 0; i < 1'000'000; ++i) {
        std::lock_guard<std::mutex> g(m);
        ++counter;                  // only one thread holds the lock at a time
    }                               // RAII: g's destructor releases the lock
}

// or, for this specific case, an atomic: no lock at all, single CPU instruction
std::atomic<int> counter = 0;
void worker() {
    for (int i = 0; i < 1'000'000; ++i)
        counter.fetch_add(1, std::memory_order_relaxed);   // relaxed: atomic, with no ordering promise (Section 03)
}

Deadlock · the trap when locks compose

Locks solve the race; they introduce a new failure mode. Deadlock happens when two or more threads each hold a lock another one needs, and all of them wait forever. The canonical recipe: thread A holds lock L1 and tries to acquire L2; thread B holds L2 and tries to acquire L1. Each waits for the other, and the program freezes. Deadlock is mathematically characterised by a cycle in the "wait-for" graph: an arrow from each waiting thread to the thread whose lock it wants. The standard prevention is lock ordering: every thread acquires locks in the same global order, which keeps the wait-for graph acyclic, and with no cycle there is no deadlock.

Fig 16.3 · Mutex protects · deadlock is the new failure mode
Fig 16.3 · Mutex protects · deadlock is the new failure mode Two threads, A and B, and two locks, L1 and L2, run two ways. Taking them in opposite orders: A holds L1, B holds L2, then A waits for L2 and B waits for L1. The waits form a cycle and both wait forever. Taking them in the same order, L1 first: B waits for L1 while A takes L2, finishes and releases both; then B takes them in turn. With no cycle there is no deadlock. opposite order A B L1 L2 cycle: both waitforever same order: L1 first A B L1 L2 A finishes,lets go then B runsno cycle holds waits for

Left, a deadlock: each thread holds one lock and waits for the other's, and the waits close a cycle. Right: both ask for L1 first, so B waits until A has finished with both locks, and no cycle can form.

How it works
  1. Lock ordering. Large codebases such as Linux and PostgreSQL document a hierarchy of locks; taking them out of order is a review red flag, and Linux's lockdep checks for it at run time.
  2. Livelock. The same shape with threads that keep backing off and retrying in step instead of blocking: busy, yet no progress.

Therac-25, 1985–87. The first known software-caused medical fatalities were race conditions. The Therac-25 radiation therapy machine used a single computer to control beam intensity and targeting. Its predecessors had hardware interlocks (physical switches that blocked the beam in an unsafe setting); the engineers had concluded the software was reliable enough to remove them. A specific keystroke sequence, fast enough to interleave with the beam-mode-select state machine, could leave the machine in high-power X-ray mode while the turntable that shapes the beam was set for low-power electron mode. Six patients received radiation overdoses of roughly a hundred times the prescribed dose; three died.

03 · Lock-free programming

Concurrency without locks · the deeper rabbit hole.

Locks have a cost beyond their nanosecond overhead: they serialise. A queue protected by a single mutex can be used by one thread at a time regardless of how many cores are available. In high-performance contexts (kernels, databases, garbage collectors), engineers reach for lock-free data structures: queues, stacks, hash tables built so that multiple threads can read and write concurrently without ever blocking each other. The mechanism is the same atomic CPU instruction that mutexes are built on, used directly: compare-and-swap (CAS). With CAS, a thread can perform an "if the value is still x, change it to y; otherwise tell me what it is now" operation as a single indivisible step. Built carefully, CAS-based data structures can be wait-free (every thread always makes progress) or lock-free (the system as a whole always makes progress, even if individual threads can starve). Maurice Herlihy's 1991 paper Wait-Free Synchronization made the stricter idea precise, proving a hierarchy of which data structures can be implemented wait-free on which atomic primitives and turning an engineering practice into a mathematical theory. The cost is that the code is hard to write correctly, and reasoning about it requires understanding memory orderings, the rules by which writes from one core become visible to others.

Fig 16.4 · Compare-and-swap · the atomic step lock-free code is built on
Fig 16.4 · Compare-and-swap · the atomic step lock-free code is built on The race of Fig 16.2 replayed with compare-and-swap, which stores a new value only if the old one is still there. With the counter at 100, both threads read 100. Thread 1 swaps 100 for 101 and succeeds. Thread 2 tries to swap 100 for 101 and fails, because the counter now holds 101; it reads again and swaps 101 for 102. Two increments, and the counter rose by two, with no lock taken. CAS(addr, expected, new): store new only if addr still holds expected thread 1 counter thread 2 read 100 100 read 100 100 CAS 100→101 ✓ 101 CAS 100→101 ✗ 101 ≠ 100 read 101 101 CAS 101→102 ✓ 102 two increments, +2: 102, no lock taken

Compare-and-swap stores a new value only if the old one is still there, in one indivisible step. Thread 2's swap fails because thread 1 got there first, so it reads again and retries: both increments land, and no thread ever waits on a lock.

How it works
  1. In hardware. lock cmpxchg on x86; on ARM a load-exclusive/store-exclusive pair, or a single CAS instruction since ARMv8.1. Languages expose it as compare_exchange (C++, Rust) or compareAndSet (Java).
  2. The price. Under heavy contention threads keep losing and retrying; and a value that changes from A to B and back to A fools the check (the ABA problem).
  3. What it builds. Locks themselves, lock-free queues and stacks (Michael and Scott's queue, 1996), and reference counts.

Memory orderings · why the world is not sequentially consistent

On many modern multi-core CPUs, the order in which one core's writes become visible to another core can differ from the order they were issued. Out-of-order execution (Chapter 1, Section 04) and store buffers (holding areas where a core parks its writes until the cache takes them, which can let a later write out before an earlier one) let writes appear reordered. On a weakly ordered CPU (one that allows this freely), such as ARM, a thread that writes x = 1; flag = true; may have another thread on a different core observe flag = true while still reading x = 0. Programs that depend on cross-thread ordering must say so, by annotating each atomic operation with a memory ordering (a contract about what other cores are guaranteed to see). The pair that repairs the broken example: the writer tags its flag store release (a promise: every write before it on this thread, the x = 1 included, is visible by the time the flag is); the reader tags its flag load acquire (the matching demand: no later load on this core, the load of x included, may run ahead of the flag load, so everything the releaser promised is visible by then). Release and acquire meet at the flag, and x crosses with it. Above the pair sits sequentially consistent (the strongest ordering: one global order every thread agrees on, priced accordingly); below it sits relaxed, which promises atomicity and nothing else.

Fig 16.5 · Memory orderings · relaxed vs release and acquire
Fig 16.5 · Memory orderings · relaxed vs release and acquire A writer sets x to 1 and then a flag to 1; a reader waits for the flag and then reads x. Run with relaxed ordering on a weakly ordered processor such as ARM, other cores can see the flag before x, so the reader sees the flag but reads x as 0. Run with a release store on the flag and an acquire load on the reader's side, x becomes visible no later than the flag, so the reader reads 1. relaxed, on ARM or POWER write seen read x=1 flag=1 flag=1 x=1 flag=1 x=0 release / acquire write seen read x=1 flag=1 x=1 flag=1 flag=1 x=1 release store / acquire load

A writer sets x, then a flag; a reader waits for the flag, then reads x. With relaxed ordering a weakly ordered CPU can show the flag first and the reader gets the old x; a release store on the flag paired with an acquire load guarantees x arrives with it.

How it works
  1. Hardware differs. x86 keeps stores in order, so this test passes there even with relaxed atomics, and the bug surfaces only on weakly ordered CPUs such as ARM, POWER or RISC-V; the compiler may reorder the two writes on any machine unless told not to.
  2. Defaults. Java and Go atomics are sequentially consistent; C++ lets the programmer choose per operation and defaults to sequentially consistent; Rust makes every atomic operation name its ordering.
04 · GPU architecture

The other shape of parallelism · thousands of small cores doing the same thing.

The CPU we have built (out-of-order execution, branch prediction, caches, all the machinery of Chapter 1) is optimised for fast sequential execution: do one thing, do it as fast as possible, then move to the next. The GPU is the opposite: thousands of small cores (GPU makers call them lanes), grouped so that each group runs in lockstep, executing the same instruction on different data. It grew out of 3D graphics, where every pixel on a million-pixel screen needs the same shading calculation (working out its colour) applied to different inputs. Around 2007, NVIDIA realised that this hardware was useful for any embarrassingly parallel computation and exposed it through the CUDA programming model. Today GPUs train and run most large machine-learning models, render essentially all real-time graphics, and carry a growing share of scientific simulation.

Fig 16.6 · CPU vs GPU · few clever cores vs thousands of simple ones
Fig 16.6 · CPU vs GPU · few clever cores vs thousands of simple ones The same job, shading 64 pixels, on two machines. A CPU with four fast cores shades four pixels per quick step and needs sixteen steps. A GPU with sixty-four simple lanes, each four times slower, shades all sixty-four in a single long step and finishes in a quarter of the time. CPU: 4 fast cores 4 pixels per step GPU: 64 slow lanes all 64 in one step done: 1 long step done: 16 steps one long step = four short ones

A CPU spends its transistors on a few cores that run one stream of instructions fast; a GPU spends them on many simple lanes that run the same instruction on different data. On 64 pixels the slower lanes finish first because they all work at once; on a long chain of dependent, branching steps the CPU wins.

How it works
  1. Real sizes. A desktop CPU has 8 to 24 cores; an NVIDIA H100 (SXM) has 132 streaming multiprocessors with about 17,000 lanes in all, run in groups of 32 that share one instruction (Fig 16.7).
  2. Where the transistors go. A CPU core spends them on out-of-order execution, branch prediction and large caches; a GPU lane has almost none of that, and hides memory delays by switching between thousands of waiting threads.
  3. Why it took over. Graphics, video and neural networks are the same arithmetic over huge arrays; training a network is mostly matrix multiplication, exactly the GPU's shape.

SIMD and the CUDA hierarchy

The GPU's organising principle is SIMD (Single Instruction, Multiple Data). A SIMD instruction operates on a vector of values at once: add these 32 numbers to those 32 numbers in one instruction. CPUs have SIMD too (SSE, AVX on x86; NEON on ARM), typically 128 or 256 bits wide, which holds four or eight 32-bit numbers at once. GPUs take this much further: a CUDA warp is 32 threads, one per lane, running in lockstep. Many warps are grouped into a block; many blocks make a grid; the entire grid is dispatched to the GPU as a single kernel launch (here a kernel is the function the GPU runs, not the operating system). The programmer writes code that looks like it runs on a single thread; the runtime replicates it across the grid.

Fig 16.7 · The CUDA hierarchy · thread → warp → block → grid
Fig 16.7 · The CUDA hierarchy · thread → warp → block → grid A zoom through CUDA's hierarchy: a grid of blocks is one kernel launch; a block holds up to 1,024 threads in warps; a warp is 32 threads that execute one instruction together. When the code branches on the thread number, if the lane is less than 16 run path A, else run path B, lanes 0 to 15 run path A while lanes 16 to 31 wait, then the other way round: a warp split by a branch pays for both paths. grid of blocks block: up to 1,024 threads warp 0 warp 1 warp 2 0 31 warp: 32 threads, one instruction if lane < 16: path A, else path B step 1: lanes 0–15 run path A, 16–31 wait step 2: lanes 16–31 run path B, 0–15 wait a split warp pays for both paths

A kernel launch is a grid of blocks, each block a set of warps, each warp 32 threads that execute one instruction together. When a branch splits a warp, the two halves take turns while the other waits, so divergent code runs at a fraction of the speed.

How it works
  1. Blocks. Threads in one block share fast on-chip memory (up to 227 KB per block on an H100) and can wait for each other; in the classic model separate blocks cannot, and share data through slow global memory.
  2. Memory. Registers are per thread and nearly free; global memory (HBM, 80 GB on an H100) is huge and slow per access. Fast kernels keep data in the block's on-chip memory (CUDA calls it shared memory) and have each warp read neighbouring addresses together (coalesced access).
  3. The model. The programmer writes one thread's code; the launch runs it for every thread of the grid, each finding its own index.
05 · Amdahl & Gustafson

The mathematics of how much speedup more cores can possibly give you.

Adding cores to a computation is not free, and it is not unbounded. Two laws govern what you can expect. Amdahl's law (Gene Amdahl, 1967) says that if a fraction p of a program's running time is parallelisable, the sequential rest sets a hard ceiling on speedup that no number of cores can lift. The ceiling is brutal in practice: a program that spends 90% of its time in parallel work maxes out at 10×.

The ceiling, in one equation

If a fraction p of the one-core running time parallelises perfectly and the remaining 1−p is strictly sequential, count that running time as 1. On N cores the sequential part still takes 1−p and the parallel part takes p/N, so the speedup (old time over new time) is:

S(N) = 1 / ((1 − p) + p / N)

The p/N term shrinks toward zero as you add cores; the (1−p) term does not move. So S(N) → 1 / (1 − p) as N → ∞. A program that is 95% parallel cannot exceed 20× speedup.

"The effort expended on achieving high parallel processing rates is wasted unless it is accompanied by achievements in sequential processing rates of very nearly the same magnitude."

Gene Amdahl, "Validity of the Single Processor Approach to Achieving Large Scale Computing Capabilities," AFIPS, 1967
Fig 16.8 · Amdahl's law · the ceiling that won't move
Fig 16.8 · Amdahl's law · the ceiling that won't move One job whose work is 5% serial and p = 95% parallel. As the number of cores doubles from 1 to 1,024, the parallel 95% of the time bar shrinks as 95 divided by N, but the serial 5% never does, so the bar approaches the serial floor. Below, the speedup curve S(N) = 1 / (0.05 + 0.95/N) climbs steadily on the doubling axis and then flattens against its ceiling of 20: 5.9 at 8 cores, 15.4 at 64, 19.6 at 1,024. time for one job: 5% serial, p = 95% parallel N = 1 core · speedup 1.0× N = 2 cores · speedup 1.9× N = 4 cores · speedup 3.5× N = 8 cores · speedup 5.9× N = 16 cores · speedup 9.1× N = 32 cores · speedup 12.5× N = 64 cores · speedup 15.4× N = 128 cores · speedup 17.4× N = 256 cores · speedup 18.6× N = 512 cores · speedup 19.3× N = 1024 cores · speedup 19.6× serial parallel part shrinks as 95/N ceiling 1/(1 − p) = 20× 0 10 20 1 32 1024 cores, N (doubling)

Add cores and only the parallel part of a job shrinks; the serial part stays. With 5% serial work the speedup is 5.9× on 8 cores, 15.4× on 64 and 19.6× on 1,024, and no number of cores can take it past 20×.

How it works
  1. Other values. 50% parallel tops out at 2×, 90% at 10×, 99% at 100×: going from 95% to 99% parallel raises the ceiling from 20× to 100×, which no number of extra cores can do.

Gustafson's law (1988) appears to contradict Amdahl's; it is actually the answer to a different question: what happens when the problem grows with the machine? Amdahl fixes the problem and asks how much faster more cores make it; Gustafson fixes the time and asks how much bigger a problem the extra cores can tackle. If problem size grows with the available compute (more particles in a physics simulation, more pixels in a rendering), the sequential portion stays roughly constant while the parallel portion scales, so the work done in the same time, compared with one core, is S(N) = (1 − p) + p·N: the serial share is done once, the parallel share on each of the N cores. It grows nearly linearly with cores. This is why GPU-driven workloads (training a bigger neural network on more GPUs) and supercomputer workloads (simulating a finer-grained physical model on more nodes) do scale to thousands of cores: the problem grew with the resources.

Fig 16.9 · Amdahl vs Gustafson · same hardware, different question
Fig 16.9 · Amdahl vs Gustafson · same hardware, different question One program whose work is 5% serial, on 1 to 64 cores, asked two questions. Amdahl keeps the job fixed and asks how much faster it runs: the time bar shrinks toward its serial floor, and the speedup reaches 15.4 at 64 cores, never more than 20. Gustafson keeps the time fixed and asks how much more work gets done: the parallel part grows with the cores, so the work done in the same time reaches 60.9 times as much at 64 cores. N = 1 core 2 cores 4 cores 8 cores 16 cores 32 cores 64 cores Amdahl: same job, how much faster? time 1.00 · speedup 1.0× time 0.53 · speedup 1.9× time 0.29 · speedup 3.5× time 0.17 · speedup 5.9× time 0.11 · speedup 9.1× time 0.08 · speedup 12.5× time 0.06 · speedup 15.4× ceiling 20× Gustafson: same time, how much more? time fixed work done 1.0× · grows with N work done 2.0× · grows with N work done 3.9× · grows with N work done 7.7× · grows with N work done 15.3× · grows with N work done 30.5× · grows with N work done 60.9× · grows with N work done in that time:

Amdahl keeps the job fixed and asks how much faster more cores make it: with 5% of the time serial, 15.4× on 64 cores and never past 20×. Gustafson keeps the time fixed and asks how much more work fits: the parallel part grows with the machine, so 64 cores do about 61 times the work.

How it works
  1. The two formulas. Amdahl: S = 1 / ((1 − p) + p/N). Gustafson: S = (1 − p) + p·N, the same as N − (1 − p)(N − 1).
  2. Which applies. One fixed compile, or one query you want answered sooner, is Amdahl; a bigger simulation or model in the same hour is Gustafson.
  3. Who. John Gustafson made the argument in 1988, after his team at Sandia ran problems scaled to 1,024 processors.
06 · NUMA & MESI · the modern hardware reality

Memory is not flat any more · cache coherence is the only reason it pretends to be.

On a multi-core server, memory is not uniformly accessible. In a machine with two CPU sockets (two processor chips, each in its own socket on the motherboard), each socket has its own local DRAM. This is NUMA (Non-Uniform Memory Access): a thread that runs on socket 0 but accesses memory allocated on socket 1 pays a 50–100% latency penalty per access. Each core also has its own L1 and L2, and cache coherence is the property that those private caches still agree with one another. A hardware protocol called MESI (Modified, Exclusive, Shared, Invalid) maintains coherence by tracking the state of every cache line (the block a cache stores and moves as one unit, usually 64 bytes) on every core and ensuring that no two cores have conflicting writes to the same line. The price is cache-line ping-pong: when two cores keep writing the same line they take turns invalidating each other's copy, and every write waits for the line to cross over.

Fig 16.10 · NUMA · two sockets, two memories, asymmetric latency
Fig 16.10 · NUMA · two sockets, two memories, asymmetric latency Two processor sockets, each with four cores and its own memory, joined by a link. Core 0 sends two reads at the same moment: one to its own socket's memory, one across the link to the other socket's. The local read comes back first; the remote one takes about 1.6 times as long. socket 0 0 1 2 3 its memory socket 1 4 5 6 7 its memory link round trip, from core 0 local remote 1.6×

In a two-socket server each socket has its own memory: a core reaches its own quickly and the other socket's only across the link, at roughly one and a half to two times the delay. The program sees one flat memory; only the clock shows where the data lives.

How it works
  1. The link. Intel calls it UPI, AMD Infinity Fabric; the ratio of remote to local delay depends on the platform.
  2. Keeping data near. The operating system tries to run a thread on the socket that holds its memory and to allocate new memory there (first touch); numactl lets a program choose.
  3. When it bites. A thread that drifts to the other socket, or one big allocation on the wrong node, can slow a program sharply while its results stay correct.
Fig 16.11 · MESI · how four cores keep one cache line consistent
Fig 16.11 · MESI · how four cores keep one cache line consistent Four cores and one cache line; each core's copy is modified, exclusive, shared or invalid. Core A reads it and holds the only copy, exclusive. Core B reads it, and both copies become shared. Core A writes: it sends an invalidate on the bus, B's copy becomes invalid and A's modified. Core C reads: A supplies the new data and writes it back, and A and C become shared, while B stays invalid and D never held the line. core A I E S M S core B I I S I I core C I I I I S core D I I I I I bus A readsno one else has it B readscopies shared A writesinvalidate the others C readsA supplies, writes back modified exclusive shared invalid

Every core's copy of a cache line is modified (changed here, so memory is out of date), exclusive (the only copy, unchanged), shared or invalid. A core may write only once every other copy is invalid, so a write broadcasts an invalidate and a later reader must fetch the new value.

How it works
  1. The rule. Many readers or one writer, never both: at most one copy is M or E, and then no other copy is valid.
  2. Ping-pong. Two cores writing the same line take turns invalidating each other, and every write waits for the line to move. If the two values are unrelated but share one cache line, that is false sharing; padding them apart fixes it.
  3. Variants. AMD adds an Owned state (MOESI) and Intel a Forward state (MESIF), so a shared line can be passed between cores without going through memory.

The cache as a side channel. In January 2018, Spectre and Meltdown (Chapter 1) turned the cache against itself: it keeps what thrown-away speculative work loaded, so timing reads of it leaks a secret across security boundaries. The mitigations needed updates to the processors' own internal code (microcode) and kernel page-table isolation (KPTI, which keeps the kernel's memory unmapped while a program runs, so every system call must switch page tables), and they left a lasting ~5–30% slowdown on syscall-heavy workloads.

Closing the chapter · seam to Chapter 17

Parallelism scales the single computer up: from one core to sixty-four, from one socket to two, from CPU to GPU. It runs against three constraints: Amdahl's ceiling on speedup, NUMA's penalty for non-local memory, and MESI's cache-line ping-pong. Writing high-performance code means designing around all three. Above the single computer are many computers, sharing no memory, communicating over an unreliable network, and having to reach agreement although each of them can fail. That is the problem of Chapter 17, governed by the mathematics of distributed consensus and the CAP theorem.

Chapter 17

The Cloud
Distributed Systems
at Scale

Most code in 2026 runs in someone else's datacenter, on hardware its developers will never touch, alongside thousands of other programs they will never know about, coordinating across a network that delays, drops and cuts off messages without warning. The cloud is the substrate of most web services and mobile backends, much of AI training, and a growing share of the world's large databases. It is built from a handful of abstractions: virtualization, containers, orchestration, consensus protocols, service meshes.

TopicsHypervisor · Docker · Kubernetes · Paxos/Raft · CAP · microservices
Era covered1968 → present
Cluster10,000 machines · 3 regions · one system
Chapter 17 hero · The Cloud Distributed Systems at Scale leaderx = 5 x = 5follower x = 5follower downfollower, failed x = 5follower dashed: the leader sends each change to every follower FIVE NODES · ONE TRUTH agreeing when any machine can fail is hard
01 · Virtualization

One physical machine · pretending to be many.

The cloud's foundational abstraction is the virtual machine: software that emulates a complete computer (CPU, memory, disks, network) well enough that an unmodified operating system runs inside it without knowing it isn't running on real hardware. The technology dates to IBM's CP/CMS in 1968: a hypervisor on the System/360 that let many isolated CMS sessions, each inside its own virtual System/360, share one expensive mainframe. The x86 world rediscovered virtualization in the late 1990s (VMware founded 1998), and CPU-level support arrived with Intel VT-x in 2005 and AMD-V in 2006. These instruction-set extensions let a hypervisor run an unmodified guest operating system without rewriting its code as it runs. AWS launched EC2 in 2006, and the public clouds turned the technology into a commodity.

Fig 17.1 · Type 1 vs Type 2 hypervisors · bare metal vs hosted
Fig 17.1 · Type 1 vs Type 2 hypervisors · bare metal vs hosted Two stacks. Type 1: guest operating systems run on a hypervisor that runs directly on the hardware, as in public clouds. Type 2: guests run on a hypervisor that is an application on a host operating system such as macOS or Windows, beside an ordinary app, a browser. In both, a guest's ordinary instruction, such as an add, runs straight on the processor, while a sensitive one, labelled read CPU id (it asks what processor this is), traps down to the hypervisor, which answers on the guest's behalf, and the guest resumes. type 1: bare metal guest A guest B hypervisor as in public clouds hardware type 2: hosted guest A browser hypervisor host OS macOS, Windows hardware add read CPU id add: runs straight on the CPU read CPU id: traps hypervisor answers the trap, guest resumes

A guest's ordinary instructions run straight on the processor; sensitive ones, which read or change the real machine (asking what processor this is, touching a device), trap to the hypervisor, which answers for it. A type 1 hypervisor sits on the bare hardware, as in every public cloud; a type 2 runs as an application inside a desktop operating system.

How it works
  1. The hardware help. Intel VT-x and AMD-V (2005–2006) add a mode beneath the guest's own kernel mode, so every sensitive instruction traps cleanly to the hypervisor.
  2. Before that. A few x86 instructions touched privileged state without trapping, so VMware rewrote guest code as it ran (binary translation) and Xen had guests call the hypervisor on purpose (paravirtualization).
  3. Examples. Type 1: KVM, Xen, ESXi, Hyper-V. Type 2: VirtualBox, VMware Workstation, Parallels.

The economic consequence is the on-demand, elastic cloud. Before EC2, deploying a web service required buying or leasing physical servers, racking them in a colocation facility, and waiting weeks for capacity. After EC2, a single API call got you a fresh VM in minutes (today, in under one), in any of dozens of regions worldwide (one at launch), billed only for what you used: by the hour at launch, by the second today. The capability transformed startup economics: Instagram could scale from zero to millions of users without ever owning a server.

02 · Containers revisited

Lighter than VMs · faster to start · the deployment unit of the modern web.

Chapter 4, Section 06, introduced containers as kernel-level isolation built on namespaces and cgroups. The economics that followed turned containers into the dominant deployment unit of the 2020s. Where a VM takes tens of seconds to boot, megabytes to gigabytes of memory, and contains an entire operating system, a container starts in a fraction of a second, weighs a few megabytes, and shares the host's kernel. Docker, released in 2013, gave containers a usable interface and a portable image format (an image is a program packaged with every file it needs to run). It initially drove the kernel's namespaces and cgroups through LXC, a user-space container runtime it soon replaced with its own. Running docker run nginx fetches a layered image from a registry, an online store of images (the first time only), creates the namespaces, configures cgroups, and starts the process; once the image is on the machine, that takes well under a second. Against a VM, that is a tenfold to hundredfold improvement in start time and a tenfold one in density (how many fit on one server). By 2014 Google alone was starting over two billion containers every week; the largest Kubernetes clusters run tens of thousands at a time.

Fig 17.2 · Docker image layers · rebuild and ship only what changed
Fig 17.2 · Docker image layers · rebuild and ship only what changed An image built from five Dockerfile steps, one layer each, bottom up: a Linux base, installing Python, copying the package list, installing the packages, and copying app.py. After app.py is edited, the rebuild checks the cache from the bottom up: the four layers below come from the cache unchanged and only the top layer is rebuilt, and only that layer is sent to the registry. A second image built on the same base shares the bottom layer, stored once. image A, layers bottom up FROM a Linux base cached RUN install python3 cached COPY package list cached RUN install packages cached COPY app.py editedchecking cachebottom up rebuilt registry only this layeris sent image B, another service: same base layer stored once

A Dockerfile is the recipe an image is built from: each of its steps produces one read-only layer holding the files that step added or changed, named by a hash of its contents. Change app.py and only its layer is rebuilt and pushed to the registry; images that share a base store it once.

How it works
  1. Order matters. A changed step invalidates its layer and every layer above it, so Dockerfiles put what changes least (the base, the packages) first and the code last.
  2. At run time. The base layer holds a Linux system's files and libraries but no kernel: the container uses the host's. The layers are stacked into one read-only file system, with a thin writable layer on top for the running container; a write to a lower file copies it up first (copy-on-write).
  3. The idea. Name things by the hash of their contents and share them by reference, as Git stores its objects.

The microVM convergence. The trade between VM isolation and container speed has been narrowing. Chapter 4's Firecracker microVMs (2018), and Kata Containers the same year, are lightweight VMs that boot in about an eighth of a second and weigh a few megabytes, giving near-VM isolation at close to container speed and density. Google's gVisor (2018) attacks the same trade from the other side: no VM is booted, and an ordinary process answers the container's system calls itself, passing only a few on to the host kernel, so much less of the real kernel is within reach of a hostile container. AWS Lambda, a service that runs a customer's small function on demand (Section 06), puts every function in its own Firecracker microVM, reused for later calls of the same function but never shared between functions or customers. A microVM gets its speed by stripping the VM down to a minimal guest kernel and a handful of virtual devices; stripped that far, a VM loses most of its speed penalty and keeps its security advantage.

03 · Kubernetes & the control loop

The orchestrator that took over deployment.

A handful of containers on one host is easy. A thousand containers across a hundred hosts, with health checks, automatic restarts, rolling updates, traffic routing, secret management, and storage attachment, is not. The system that solved this problem, and won the orchestration wars in the late 2010s, is Kubernetes, originally built at Google (where its ancestor, Borg, had by then been running internally for over a decade), open-sourced in 2014, and now the dominant way to run containerised software in the cloud. Kubernetes does not run bare containers: it runs pods, each one or more containers started together on the same machine. Kubernetes is not one program but a set of cooperating control-plane components (the parts that decide what should run where) and a fleet of worker nodes (the machines that run the pods), glued together by a simple architectural idea: the control loop. The user declares what the system should look like; the controllers continuously observe what it does look like and reconcile the difference.

Fig 17.3 · Kubernetes architecture · control plane + workers
Fig 17.3 · Kubernetes architecture · control plane + workers A Kubernetes cluster: a control plane of API server, etcd, scheduler and controllers, above three worker nodes, each running a kubelet. One request traced: the user applies a pod, which goes to the API server, which stores it in etcd. The scheduler, watching the API server, picks node 2 and the choice is recorded; node 2's kubelet, also watching, starts the pod. A note says every component talks only to the API server. control plane user: apply API server etcd scheduler controllers all talk only to API server node 1 kubelet node 2 kubelet node 3 kubelet pod apply: the API server takes it stored in etcd scheduler sees it, picks node 2 choice recorded kubelet on node 2 starts it

Every Kubernetes component talks only to the API server, which keeps the cluster's desired state in etcd. A new pod is stored there, the scheduler watching the API server picks it a node, and that node's kubelet, also watching, starts it.

How it works
  1. etcd. etcd is a small replicated key-value store whose copies stay in agreement through Raft (Fig 17.5); production control planes run three or five copies.
  2. Controllers. Loops that watch one kind of object and act until reality matches it: a Deployment controller, a ReplicaSet controller and many more (Fig 17.4).
  3. On each node. The kubelet runs the pods assigned to it through a container runtime such as containerd, and kube-proxy routes service traffic to them.

The control loop · the deepest pattern in Kubernetes

Every controller in Kubernetes, meaning every piece of logic that maintains some kind of object (a stored record of what should exist, such as "three copies of this pod"), is a reconcile loop: it observes the current state of the world, compares it to the desired state, takes whichever action will move current closer to desired, and then does the same again, for as long as the object exists. The loop is idempotent (run twice with nothing changed, the second run does nothing) and keeps no memory of what it did: a command that fails is simply tried again by the next pass, which sees that the change still hasn't happened. The pattern of declared desired state, observed actual state, continuous reconciliation is borrowed from control theory (Chapter 10's TCP congestion control is the same feedback shape, except that nobody declares its target speed, so it has to probe for it) and runs through the tools that build infrastructure from a written description (infrastructure as code): Argo CD and Flux run it as a loop over a Git repository, while Terraform and Pulumi run it only when a person asks, working out the changes needed (the plan) and applying them once.

Fig 17.4 · The reconcile loop · observe, diff, act, repeat
Fig 17.4 · The reconcile loop · observe, diff, act, repeat A controller loops through observe, diff and act. The desired state is three replicas, and three pods are running. One pod crashes. On the next pass the controller observes two running, the difference is one, and it asks for a new pod. The pass after finds three of three and does nothing. observe diff act desired: 3 actual: 3 2 3 idle:3 = 3, nothing to do a pod crashes observe:2 running diff:want 3, have 2: +1 act:add one pod next pass:3 = 3, nothing to do

A controller compares what was asked for with what is running and takes one step to close the gap, again and again. When a pod crashes the next pass sees two of three and asks for one more (the scheduler and a kubelet start it, as in Fig 17.3); the pass after sees nothing to do.

How it works
  1. Level, not edge. The controller acts on the current difference, not on remembered events, so a missed crash or a failed start is simply caught on the next pass.
  2. Triggers. A controller wakes when the API server reports a change to what it watches, and again on a timer (30 seconds in the code below), so a crash is noticed at once and quiet drift is caught later.
Go: every Kubernetes controller, in essence
// the reconcile loop, simplified: Kubernetes calls this on every
// change to the object or its pods, and again after RequeueAfter
func Reconcile(req Request) (Result, error) {
    // 1. read desired state (from etcd, via the API server)
    desired, err := apiServer.Get(req.Name)
    if errors.IsNotFound(err) {
        // the user deleted the object, so clean up and stop
        return Result{}, cleanup(req.Name)
    }

    // 2. observe actual state (the running pods, also via the API server)
    actual, err := cluster.Get(req.Name)

    // 3. diff and act: exactly one step toward convergence
    switch {
    case actual == nil:
        return Result{}, cluster.Create(desired)
    case !equal(desired, actual):
        return Result{}, cluster.Update(desired)
    default:
        // already converged; re-run later in case the world drifts
        return Result{RequeueAfter: 30 * time.Second}, nil
    }
}
04 · Distributed consensus

Many machines · agreement · the math says it shouldn't quite work · we make it work anyway.

Kubernetes' "source of truth", etcd, runs a distributed consensus protocol called Raft, because the moment you need multiple machines to agree on something (what is the current version of the configuration, who is the current leader, what is the next entry in the log), the network's unreliability becomes a mathematical problem. Messages can be delayed arbitrarily, machines can crash, and the network can partition into halves that can't reach each other. Distributed consensus is the formal problem of getting n machines to agree on a value despite up to f of them failing. Its classic protocols are Lamport's Paxos (1989) and Diego Ongaro and John Ousterhout's Raft (2014), which was explicitly designed to be more understandable than Paxos and has since become the dominant consensus protocol in industry.

Paxos · the harder ancestor

Paxos works in two-phase rounds, and each node can play two roles: a proposer, which suggests a value, and an acceptor, which votes on one. A proposer first sends a prepare message with a unique proposal number to a majority of acceptors; each acceptor that has not already promised a higher number replies with a promise not to accept anything lower, returning whatever value (if any) it had previously accepted. The proposer then sends an accept request carrying the highest-numbered previously accepted value, or its own if none was returned; once a majority accepts, the value is chosen. The majorities are the trick: any two of them share at least one acceptor, so a proposer preparing a new round is guaranteed to meet a witness of any value already chosen, and is forced to carry that value forward instead of its own. The mechanism is correct under any pattern of message loss, duplication, or reordering. Lamport proved it in 1989. The difficulty is cognitive. Reasoning about Paxos requires holding several rounds and proposal numbers in your head simultaneously, and Lamport's original presentation used a parable about a fictional Greek parliament that, instead of clarifying things, kept the paper unpublished for most of a decade (submitted 1990, printed 1998, with the parable still in it). The field then waited until 2001 for Paxos Made Simple, which explained the same protocol in plain language. Raft's contribution was the same safety in a complete design built to be understood: one strong leader, numbered terms (election periods, each with at most one leader) and an explicit log (the ordered list of commands every node keeps). In its authors' classroom study, most students answered questions on Raft better than on Paxos, and a protocol that is easier to understand is easier to implement correctly, which is why etcd uses Raft.

"A distributed system is one in which the failure of a computer you didn't even know existed can render your own computer unusable."

Leslie Lamport, attributed (~1987) · Turing Award 2013
Why a quorum is N/2 + 1

Raft tolerates up to f node failures with a cluster of N = 2f + 1 nodes. A quorum, the smallest set of nodes whose agreement counts as the cluster's decision, has this size (⌊ ⌋ means round down, so 5 / 2 becomes 2):

Q = ⌊N / 2⌋ + 1

So 5 nodes need 3, 7 need 4, 9 need 5. The reason: any two quorums must intersect in at least one node, so a leader cut off from a newer term cannot keep a majority without that shared node, which has seen the newer term and turns the old leader's messages away, so it steps down. This intersection property is what prevents split brain, where two groups of nodes each believe they are in charge and make conflicting decisions. It is also why even-numbered cluster sizes are wasteful (4 nodes need 3, so they survive one failure, no more than a cluster of 3).

Fig 17.5 · Raft · leader election and log replication
Fig 17.5 · Raft · leader election and log replication Five nodes, A to E; E is down. No leader's heartbeat arrives, and A's election timeout runs out first: it becomes a candidate for term 2 and asks for votes. B, C and D vote for it and A becomes leader. A client sends x; A appends it to its log and sends it to the others. B and C acknowledge first, so with A's own copy three of five nodes hold x, a majority, and x is committed. D's copy arrives later, marked late. A B C D E down candidate leader x x x xlate all follow; no heartbeat arrives A times out first: votes for A? A timed out first; B, C, D vote: A leads term 2 A appends x, sends it out A, B, C hold x (3 of 5): committed; D late

When no leader is heard from, the node whose random timeout runs out first asks for votes and leads once a majority agrees. It appends each command to its log and copies it to the others; once a majority hold it, the entry is committed, so five nodes keep working with two down.

How it works
  1. Majorities. In log replication an entry counts once a majority hold it, so any later leader's majority includes a node that has it, and that node will not vote for a candidate whose log lacks it (below): a committed entry is never lost.
  2. Votes. In leader election a node votes once per term, and only for a candidate whose log is at least as up to date as its own; random timeouts make split votes rare.
  3. Where it runs. etcd, Consul and CockroachDB use Raft.

FLP impossibility · why this is harder than it looks

The deep reason consensus is hard is the FLP impossibility result (Fischer, Lynch, Paterson, 1985): in an asynchronous distributed system where messages can be delayed arbitrarily and even one process can crash, no deterministic consensus protocol can guarantee both safety (never reaching an inconsistent decision) and liveness (always eventually reaching a decision). Some run of the protocol can always be constructed in which it spins forever without deciding. Every real-world consensus protocol (Paxos, Raft, PBFT, Tendermint) works around FLP by adding an assumption, and the assumption is the price.

Fig 17.6 · FLP impossibility · safety vs liveness vs asynchronous failure
Fig 17.6 · FLP impossibility · safety vs liveness vs asynchronous failure Node A needs node C's vote to decide. In one world C is slow and its vote is still on the way; in the other C has crashed. A's waiting clock runs the same in both, so A cannot tell them apart. If A decides now, the slow world's late vote may disagree with its decision, and safety is lost. If A keeps waiting, in the crashed world it waits for ever, and liveness is lost. Real systems time out, assuming delays eventually end, as Raft does. world 1: C is slow A C A needs C’s vote… world 2: C crashed A C A needs C’s vote… from A, the two look the same decide now:C's late vote may disagree, safety lost decide now:C's late vote may disagree, safety lost keep waiting:in world 2, for ever; liveness lost keep waiting:in world 2, for ever; liveness lost real systems:time out, assume delays end (Raft) real systems:time out, assume delays end (Raft)

If messages can be delayed without limit, a node cannot tell a slow peer whose vote it needs from a crashed one. Deciding without it risks disagreeing later, and waiting risks waiting for ever: no deterministic protocol rules out both, so real systems assume delays end and use timeouts, and a timeout starts a new election, never a decision.

How it works
  1. In practice. Raft never decides wrongly, but during a long network storm it can stall, electing leader after leader until messages arrive in time again.
FLP, in one line

Fischer, Lynch, and Paterson, 1985 (∄ reads "there is no"):

∄ deterministic protocol that solves consensus in an asynchronous system with even one crash failure.

The proof is short: some starting configuration is bivalent, its outcome still open, and from any bivalent configuration the adversary can delay exactly the message that would settle it, keeping the outcome open. The protocol either decides early (and risks being wrong) or waits forever. Real systems escape by dropping one of the theorem's conditions: partial synchrony (Paxos, Raft) gives up unbounded delay, assuming messages eventually arrive within some bound; randomisation (Ben-Or) gives up determinism, flipping coins so that no fixed schedule of delays can keep the outcome open for ever; eventual consistency (Dynamo) gives up agreement itself, serving now and reconciling later.

05 · The CAP theorem

Pick two · the unavoidable trade in distributed storage.

A different impossibility result governs distributed storage rather than distributed agreement. Eric Brewer's CAP theorem (conjectured 2000, proven 2002 by Gilbert & Lynch) says that any distributed data store can provide at most two of three properties: Consistency (every read returns the most recent write; not the C of Chapter 13's ACID, which keeps a database's rules intact), Availability (every request gets a real answer, not an error), and Partition tolerance (the system keeps working when the network splits the cluster). Network partitions happen whether you like it or not, so the practical choice during a partition is between C and A. A consistent system, partitioned, refuses requests on any side that cannot reach a majority of its replicas (you lose A). An available system, partitioned, serves possibly stale data (you lose C).

"The '2 of 3' formulation was always misleading because it tended to oversimplify the tensions among properties. Now such nuances matter. CAP prohibits only a tiny part of the design space: perfect availability and consistency in the presence of partitions, which are rare."

Eric Brewer, "CAP Twelve Years Later: How the 'Rules' Have Changed," IEEE Computer, 2012
Fig 17.7 · CAP · when the network splits, consistency or availability
Fig 17.7 · CAP · when the network splits, consistency or availability Two replicas both hold x = 1. The link between them is cut. A client writes x = 2 to replica 1, and another client asks replica 2 for x. In a consistent system, replica 1 refuses the write, and replica 2, unable to confirm the latest value, returns an error. In an available system, replica 1 takes the write and replica 2 answers with what it has, the stale x = 1. CP: stay consistent replica 1 replica 2 x = 1 x = 1 write x = 2 refused read x? error: can’t confirm AP: stay available replica 1 replica 2 x = 1 x = 2 x = 1 write x = 2 read x? x = 1 (stale) partition: the link is cut

When the link between replicas breaks, neither side can confirm a change with the other. CP systems refuse the write and the read; AP systems take the write and answer the read with the stale value they have.

How it works
  1. PACELC. Daniel Abadi (2010): even with no partition, a system trades latency against consistency, waiting for replicas or answering from the nearest one.
Fig 17.8 · CAP in production · which database picked which side
Fig 17.8 · CAP in production · which database picked which side Systems that choose consistency: coordination stores such as etcd, for leader election and cluster configuration; databases for ledgers and orders; a database with a primary, which steps down without a majority. Systems that choose availability: a database for sensor data and feeds; stores like Amazon’s Dynamo, for shopping carts; DNS, where a stale name beats none. When a partition strikes, the first group refuses writes it cannot confirm and the second accepts them and reconciles later. CP: refuse rather than lie AP: answer, reconcile later coordination: etcd who leads, cluster config write refused databases ledgers, orders write refused database with a primary primary steps down write refused database sensor data, feeds keeps answering stores like Amazon’s Dynamo shopping carts keeps answering DNS names: stale beats none keeps answering network healthy: all agree partition: each picks its failure

Each production store picked the failure its users can bear. Coordination services and ledgers refuse what they cannot confirm; carts, feeds and DNS keep answering and reconcile when the network heals.

How it works
  1. Reconciling. AP stores settle diverged copies afterwards: last writer wins, vector clocks (which spot the conflict so the application can merge the versions), or data types that merge by design (CRDTs). A CP database with a primary (the one copy that takes every write, like Raft's leader) has it step down when it loses its majority.
  2. Tunable. Many stores let each request choose: Cassandra's quorum reads and writes, DynamoDB's strongly consistent reads, at the cost of latency or availability.
  3. Spanner. Google's Spanner database runs on a private network that makes partitions rare; when one does happen, it still chooses consistency.
06 · Microservices, serverless, and the pendulum

Architecture is fashion · the same problems keep returning.

The dominant application architecture of the late 2010s was microservices: take what would once have been a single monolithic application and decompose it into dozens or hundreds of small services, each owning its own data, communicating over the network. The motivation was real: independent deployability, team autonomy, freedom for each team to choose its tools, fault isolation. So was the cost: every former function call became a network call, every guarantee one program gave for free (an item is never sold twice) became a distributed consistency problem, and every former crash, which stopped the whole program, became a partial failure: one service down or slow while the rest keep running and must cope, with work left half done (an order taken, the payment not). To make those network calls survivable, many teams put a small proxy, called a sidecar, beside every service, and a service mesh tells every sidecar how to encrypt, time out and retry (Fig 17.10). By the early 2020s the pendulum had partly swung back. Many companies that had decomposed eagerly found they had built a "distributed monolith", with all the complexity of microservices and none of the benefits, and consolidated.

Fig 17.9 · Monolith vs microservices · same business logic, different deployment
Fig 17.9 · Monolith vs microservices · same business logic, different deployment One checkout, orders then login then payment, traced two ways. In a monolith the three are modules in one process sharing one database, and each step is a function call. As microservices they are three separately deployed services, each with its own database, and each step is a network call; here the call to payment times out and has to be retried. monolith: one process, one database orders login payment one database 2 function calls: done microservices: three services, three databases orders own DB login own DB payment own DB payment call timed out: retry 2 network calls, 1 timed out: retried, done

The same checkout, built two ways: inside a monolith each step is a function call that cannot get lost; between microservices each is a network call that can time out and has to be retried.

How it works
  1. What it costs. Every former function call can be slow or fail, and a retried call must be safe to repeat (an idempotency key) or a timed-out payment can charge twice. One order spans several databases, so instead of one transaction it needs a saga: each step commits in its own database, and if a later step fails, undo steps (a refund, a restock) reverse the ones already done. Following a request across services needs tracing that tags every call with one id.
  2. The usual advice. Split only where a team needs that independence; a well-structured monolith is the cheaper default.
Fig 17.10 · Service mesh and the sidecar · how a mesh moves one call
Fig 17.10 · Service mesh and the sidecar · how a mesh moves one call Two pods, orders and login, each holding an app and a sidecar proxy, and above them the mesh control plane, which pushes routes and certificates to every sidecar. The orders app sends a plain request to its own sidecar. The sidecar sends it, encrypted with mutual TLS, to the login pod's sidecar; the first attempt fails and the sidecar retries. The login sidecar checks the caller and hands the plain request to its app. Neither app sees the failure. mesh control plane routes, certificates pod: orders app sidecar proxy pod: login app sidecar proxy encrypted with mutual TLS control plane pushes routes, certificates plain HTTP to its sidecar, which sends it on first try fails the sidecar retries, encrypted retry got through: neither app saw the failure

Each service's traffic goes through its sidecar, so encryption, retries and timeouts happen there, configured centrally: a first attempt that never got through is retried before either app knows (one that may have arrived is retried only if its route is marked safe to repeat).

How it works
  1. Mutual TLS (mTLS). Both sidecars present certificates, so each end knows which service it is talking to; the control plane issues and rotates the certificates.
  2. What else the proxy does. Timeouts and circuit breakers, gradual traffic shifts for new versions, and request metrics and traces for every call.
  3. The cost. An extra hop on each side of every call, and a proxy per pod; newer meshes (Istio's ambient mode, Cilium) move some of this into a shared per-node proxy or the kernel.

Serverless · and the architectural pendulum

The newer wave is serverless: write a function, upload it to a platform such as AWS Lambda, and the platform runs it on demand, scales to whatever number of invocations you receive, meters the bill in milliseconds of execution, and otherwise abstracts away every notion of a server. The trade is sharp: serverless is cheap and elastic for sporadic, bursty workloads such as image processing, webhooks, and infrequent APIs (a function called a thousand times per day costs cents); it is expensive for sustained ones (the same function called a thousand times per second is dramatically cheaper on a dedicated server). The cold-start problem, where the first invocation after a quiet period may take hundreds of milliseconds while the platform spins up a runtime, has eased dramatically with microVMs (Firecracker again) but still exists.

Fig 17.11 · The architectural pendulum · same problems, different costumes
Fig 17.11 · The architectural pendulum · same problems, different costumes A pendulum swinging between one deployable unit and many. It starts at the monolith of the 1990s, swings out through service-oriented architecture around 2005, microservices around 2014 and serverless functions around 2018, then back toward the modular monolith of the 2020s. Notes above the arc give the reasons: swinging out, teams deploy on their own; swinging back, distribution costs too much. ← one deployable many → out: teams deploy on their own back: distribution costs too much 1 2 3 4 5 1 1990smonolith1 1990smonolith 2 ~2005SOA2 ~2005SOA 3 ~2014microservices3 ~2014microservices 4 ~2018serverless4 ~2018serverless 5 2020smodular monolith5 2020smodular monolith

Each generation split systems into smaller deployable pieces, from monoliths through service-oriented architecture (SOA) and microservices to single functions, until the cost of all that distribution pushed some teams back towards fewer, well-divided programs. The trade between independence and simplicity never went away; only its costume changed.

How it works
  1. Why it swings out. Smaller pieces let teams deploy and scale independently; the cost is everything Fig 17.9 shows, paid on every call.
  2. Why it swings back. In 2023 Amazon Prime Video described folding one monitoring service from serverless functions into a single process and cutting its infrastructure cost by over 90%.
  3. The modular monolith. One deployable, with module boundaries kept as strictly as service boundaries, so it can be split later if a team really needs to.

Ask what the abstraction still charges. Every layer hides the one beneath it but not its costs: a VM still pays for each trap to the hypervisor, containers still share one kernel and are walled off only as well as it is, Kubernetes still faces partial failure, and a serverless function still pays its cold start. When something breaks, the work is finding which layer it came from.

SolarWinds, discovered December 2020. Chapter 15 has the attack: a backdoor slipped into Orion while it was being built, so the update the vendor itself signed carried it. What the cloud era adds is the blast radius. An update channel delivers code to every customer machine the way a worm would, except signed and trusted: one poisoned update reached nearly eighteen thousand customers at once, across a trust boundary that sat in no customer's network. Chapter 15's answers (SBOMs, reproducible builds, signed build provenance) are the industry's response.

Knight Capital, 1 August 2012, 9:30 a.m. A high-frequency trading firm pushed a new build to seven of its eight production servers. The new code reused an old feature flag, a setting that switches a piece of behaviour on. When the flag was switched on, the eighth server, never updated and still running the old code, read it as "run Power Peg", a retired order-routing routine whose stop-counting logic had been disabled years before. Every order routed to that one stale machine spawned child orders that nothing counted and nothing stopped. In forty-five minutes Knight Capital racked up four million unintended trades and seven billion dollars in positions; it lost over $460 million, survived the week only through a rescue that cost its shareholders about 73% of the company, and agreed to be acquired by a rival before the year was out. A cluster whose machines run different versions of the code fails like a race condition: what happens depends on which machine a request reaches. Modern deployment systems check that every machine runs the declared version before traffic flows, and send a new build to a few machines first, watch it, then roll it out to the rest.

Closing the chapter · seam to Chapter 18

Chapter 17 closes the substrate-and-scale arc that began with the single transistor in Chapter 1: out from one transistor to one CPU, one operating system, one network, one TLS connection, one database, one cryptographic primitive, then outward again to many cores, many machines, many datacenters, many regions. There is no further "out" to go without reaching the human social systems that computing intersects with, which is where Chapter 18 looks.

Chapter 18 · Epilogue

What You
Now See

Seventeen chapters ago this book drew a single transistor: a switch made of doped silicon, controlled by voltage, doing nothing on its own. Around it we have now built an entire civilization: gates, instructions, kernels, languages, networks, cryptography, security, scale. The computer is now a layered system that runs nearly everywhere, stores nearly all the information we record, and increasingly shapes the decisions of every domain it touches. This chapter adds little new: it looks back at the whole, names what's permanent and what isn't, points at where the field is going, and ends.

TopicsSynthesis · history · futures · intersections · reading list
Era coveredall of it
Clusterone curriculum · five parts · one reader
Chapter 18 hero · What You Now See WEB SERVICE network process kernel instruction processor gate transistor SAND every layer made of the layer below it
01 · The computer as a mathematical object

Every layer is math made physical · made physical again, on top.

The deepest claim this book has made, implicit in every chapter, is that the modern computer is, in a precise sense, a mathematical object: a stack of layers in which each layer is a mathematical structure realised in the layer below, and the next layer up is another mathematical structure realised in this one. Boolean algebra is realised in voltages and gates; arithmetic in adders and registers; the instruction set in the CPU's fetch, decode and execute circuits. The kernel is queueing and scheduling over processes. The relational model is set theory over rows. TLS is number theory over network packets. Distributed consensus is quorum arithmetic over asynchronous nodes.

Fig 18.1 · The full stack · transistor to distributed system · math at every layer
Fig 18.1 · The full stack · transistor to distributed system · math at every layer The book's eleven layers, bottom to top, each with the mathematics it realises: transistors, solid-state physics; logic gates, Boolean algebra; arithmetic, two's complement and IEEE 754; the instruction set, state machines; the kernel, queueing and scheduling; languages, types and pointer arithmetic; networks, information, graphs and control theory; the web, request and response; databases, relational algebra; cryptography, number theory and elliptic curves; distributed systems, consensus and CAP. transistorsolid-state physicstransistorsolid-state physics logic gatesBoolean algebralogic gatesBoolean algebra arithmetictwo's complement, IEEE 754arithmetictwo's complement, IEEE 754 instruction setstate machinesinstruction setstate machines kernelqueueing, schedulingkernelqueueing, scheduling languagestypes, pointer arithmeticlanguagestypes, pointer arithmetic networksinformation, graphs, controlnetworksinformation, graphs, control the webrequest-responsethe webrequest-response databasesrelational algebradatabasesrelational algebra cryptographynumber theory, curvescryptographynumber theory, curves distributedconsensus, CAPdistributedconsensus, CAP

Eleven layers, from a transistor's physics to the consensus of a thousand machines, each beside the mathematics it makes real. The remarkable thing is not that any one layer works; it's that all of them work at once.

How it works
  1. Reading it. Bottom to top is the order of the book: Part I builds the machine, II the software on it, III the network, IV data and secrets, V the machines working as one.
  2. Each boundary is a contract. A layer depends only on what the one below promises: gates (Boolean algebra made physical) on voltages, the kernel on the instruction set, TCP on IP's best effort.
  3. Where it leaks. The bugs the book kept returning to (overflow, races, side channels) are places where a lower layer shows through a promise.
01.5 · The trace

Eighteen stations, one keystroke.

Every senior engineer is supposed to be able to answer one question: "what happens when you type google.com into your browser and press Enter?" The honest answer touches every layer of computing. The book you have just read is the long-form answer. This section is the visual one: one figure, one keystroke, nearly every chapter activated at once.

The trace below takes the nearest case that gives Google's servers work to do, a search typed into Google's own page and sent with Enter, and follows that keystroke from the moment your finger closes a contact on a key (voltage rising on a wire, as Chapter 1 described) through every layer of your machine, out across the network, into Google's server stack, back through the network, and onto pixels on your screen. Eighteen waypoints, drawn from fourteen of the chapters and the Bridge.

Four chapters are not numbered stations. Chapters 6 and 7 (C++ and Python) have none: the code the figure shows is C and JavaScript. C++ is still on the path, since V8 is written in it, but no station shows it. Chapter 15 (security) is woven across the trace rather than located at one point. Each time the journey crosses from less-trusted to more-trusted territory (user input → kernel; client → DNS resolver; client → server's TLS endpoint; service → search index), a trust boundary is crossed and a defence layer applies. The figure marks these transitions with thin red dotted lines. Chapter 18 is not a station because it is this figure: the moment the layers stop being a list and become a system.

Fig 18.1.5 · The trace · what happens when you press Enter on google.com
Fig 18.1.5 · The trace · what happens when you press Enter on google.com Eighteen stations of one search, top to bottom. On your machine: the key's contact closes (Chapter 1), the key's number is decoded (2), the CPU takes the interrupt (3), the trap into the kernel (the Bridge), the event is queued (4), libc carries it to user space (5), the browser's event loop picks it up (12), and JavaScript, run by V8, builds the request (12). In the network: DNS turns the name into an address (11), TCP shakes hands (10), TLS agrees keys (14), HTTP sends GET /search (11), IP routes it by BGP (9), and the wire carries it as light and voltage (8). At Google: orchestrated containers (17), a microservice (17), an index lookup (13), and ranking on many cores and accelerators (16). Four trust boundaries are marked, each with the more trusted side below it: into the kernel, to the resolver, to the server's TLS and into the index. The response climbs back through the network and the browser onto the screen. your machine KEYcontact closesCh 1KEYcontact closesCh 1 GATEkey's number decodedCh 2GATEkey's number decodedCh 2 CPUinterrupt takenCh 3CPUinterrupt takenCh 3 TRAPinto the kernelBridgeTRAPinto the kernelBridge KERNELevent queuedCh 4KERNELevent queuedCh 4 libcto user spaceCh 5libcto user spaceCh 5 LOOPbrowser's event loopCh 12LOOPbrowser's event loopCh 12 V8JS builds the requestCh 12V8JS builds the requestCh 12 the network DNSname → addressCh 11DNSname → addressCh 11 TCPhandshakeCh 10TCPhandshakeCh 10 TLSkeys agreedCh 14TLSkeys agreedCh 14 HTTPGET /searchCh 11HTTPGET /searchCh 11 IProuted (BGP)Ch 9IProuted (BGP)Ch 9 WIREfibre, EthernetCh 8WIREfibre, EthernetCh 8 Google CLOUDcontainers, orchestratedCh 17CLOUDcontainers, orchestratedCh 17 SVCa microserviceCh 17SVCa microserviceCh 17 INDEXindex lookupCh 13INDEXindex lookupCh 13 RANKranked on cores, acceleratorsCh 16RANKranked on cores, acceleratorsCh 16 trust boundary (Ch 15): more trusted below the response climbs back to the screenthe response climbs back to the screen

One search, followed from a key's contact to Google's ranking and back, passes through fourteen chapters and the Bridge, and crosses four trust boundaries that Chapter 15 defends. Each station keeps the promise the next relies on, and the whole round trip takes well under a second.

How it works
  1. Reading it. Top to bottom is the order of the request; the chapter on the right is where each step was taught. The response climbs back up: TLS decrypts, TCP reassembles, the browser parses, the GPU draws.
  2. A cold start. Drawn as if no connection were open. A search from a page already loaded reuses its connection and skips DNS, TCP and TLS; and once a browser has learned Google speaks QUIC, the request rides UDP where TCP is drawn here (Fig 10.9).

That interview question has been answered countless times, in blog posts, lectures and technical books, and most answers are lists. Followed as one path, a single keystroke runs through eighteen distinct mathematical and engineering structures, every one of which had to be invented by specific people in specific decades to be available at this moment.

"The remarkable thing is not that any one layer works; it's that all of them work at once."

from §01 of this chapter, applied here

That sentence has been the through-line of Under the Code from the first transistor to this trace, and every figure that came before was preparation for the figure above. A reader who has finished the book has, in some precise sense, watched a keystroke travel through their entire civilization and come back. Whatever they go on to build or break or maintain, they will see those layers from now on.

02 · From sand to civilization

The chain of decisions, in time, by people in real places.

Every layer in the stack of Fig 18.1 is also a piece of history, and none of it was inevitable. The Boolean algebra in your CPU was invented by a Lincolnshire schoolteacher, who worked it out in full as a professor in Cork and died in 1864. The transistor came out of three physicists at Bell Labs in 1947. The kernels of Linux, macOS, Android and iOS follow the design of UNIX, which two engineers began on a discarded PDP-7, partly so that one of them could play a game called Space Travel. The web came out of a particle physics lab in 1989 because a researcher wanted a better way to organise the lab's tangle of people, projects, and documents. The cloud you deploy your code into is built on virtualization that IBM shipped in 1968 to let many users share one mainframe. Civilization is the long sequence of these decisions, each one made by a real person under real constraints, and accumulated over a century and a half into the substrate of nearly everything modern.

Fig 18.2 · The civilization timeline · seven moments that made the rest possible
Fig 18.2 · The civilization timeline · seven moments that made the rest possible A timeline drawn to scale from 1850 to 2026 with seven moments. 1854: Boole's algebra of logic, written in Cork. 1936: Turing's universal machine, Cambridge. 1947: the transistor, Bell Labs. 1969: ARPANET and UNIX. 1976: public-key cryptography, Diffie and Hellman at Stanford. 1989: the web, Berners-Lee at CERN. 1991: Linux, Torvalds in Helsinki. After Boole the line is empty for eighty years, labelled an 80-year gap; everything else crowds into the fifty-five years from 1936, and the line ends at 2026. 1850 1900 1950 2000 2026 80-year gap 1854 · Boole, the algebraCork, Ireland1854 · Boole, the algebraCork, Ireland 1936 · Turing, the machineCambridge1936 · Turing, the machineCambridge 1947 · the transistorBell Labs, New Jersey1947 · the transistorBell Labs, New Jersey 1969 · ARPANET and UNIXUCLA, Bell Labs1969 · ARPANET and UNIXUCLA, Bell Labs 1976 · public-key cryptographyDiffie, Hellman, Stanford1976 · public-key cryptographyDiffie, Hellman, Stanford 1989 · the webBerners-Lee, CERN1989 · the webBerners-Lee, CERN 1991 · LinuxTorvalds, Helsinki1991 · LinuxTorvalds, Helsinki

Seven moments, a few rooms and a few people: Boole's algebra in 1854, then no entry for eighty years, then Turing's machine, the transistor, the network and the kernel, public keys, the web and Linux in the fifty-five years that followed.

How it works
  1. The scale. Dots sit at their years on a line from 1850 to 2026; the labels are spaced evenly so they can be read.
  2. 1969, twice. ARPANET's first message (UCLA) and the first UNIX (Bell Labs) came within months of each other, years before either met the other.
  3. Not the only seven. Shannon's information theory (1948) and the integrated circuit (1958) have as good a claim; the timeline keeps to seven so each can be read.
03 · What's permanent and what isn't

The substrate is changing again · which parts will survive it?

Three forces are reshaping the substrate as this book is written. Moore's Law is ending: the transistor-density doubling we tracked from Chapter 1 has slowed sharply, because the finest parts of a transistor are now only a few nanometres across, a few dozen atoms, and each further shrink leaks more current and costs more, so extra speed now comes from chips built for one task. Quantum computing is moving from laboratory demonstrations toward early useful machines, aimed at the specific problems for which no efficient classical method is known (factoring, the simulation of quantum systems), with optimisation a more speculative third. Neuromorphic and analog AI accelerators (chips that imitate neurons, or do multiplications in the physical currents of memory cells) are being built as an alternative substrate for one specific workload, large neural networks, whose cost on today's chips is mostly the time and energy spent moving the network's weights (the learned numbers each input is multiplied by) between memory and processor, the bottleneck of Chapter 1. None of these will replace the stack this book described; all of them will sit alongside it as additional substrates.

Fig 18.3 · Three things the next decade will change
Fig 18.3 · Three things the next decade will change Classical CPUs and GPUs in the middle, with three new kinds of hardware attaching beside them, not replacing them: specialised chips, as general-purpose speed-ups slow; quantum processors, for a few structured problems; and neuromorphic and analog accelerators, for neural networks. Below, what fades: RSA and elliptic-curve keys, and ever-faster single threads. What stays: Boolean algebra, relational algebra and the layered model. classical CPUs, GPUs specialised chipsas general speed-ups slowspecialised chipsas general speed-ups slow quantuma few structured problemsquantuma few structured problems neuromorphic, analogfor neural networksneuromorphic, analogfor neural networks added beside, not instead fades stays RSA, elliptic curves faster single threads Boolean algebra relational algebra the layered model

Three new kinds of hardware attach beside the classical CPUs and GPUs; below them, what fades and what stays.

How it works
  1. Specialised chips. As shrinking transistors give less each generation, speed comes from silicon built for one task: tensor units for matrix multiplication, video encoders, network processors.
  2. Quantum. Useful for factoring, simulating molecules and a few other structured problems; that is why public keys are moving to post-quantum algorithms now (Fig 14.13).
  3. What stays. Each of these is still programmed through layers with promises at their boundaries, which is the model this book has followed from the transistor up.

Which of this book's ideas will still be useful in fifty years? The mathematical primitives almost always survive: Boolean algebra (Chapter 2), set theory and relational algebra (Chapter 13), information theory (Chapter 8), graph theory (Chapter 9), control theory (Chapter 10), number theory (Chapter 14). They survive because they are independent of the substrate that computes them. The architectural patterns generally survive (the layered model, the kernel-as-mediator, the request-response cycle, the elect, replicate and commit-by-majority shape of consensus) because they answer questions about how computation should be organised, not about what computes it. The specific implementations are the most fragile: x86 will eventually be a curiosity, RSA is already being retired, ahead of the error-corrected quantum computers (the logical qubits of Chapter 14) that could run Shor's algorithm at scale, and particular libraries will become archaic. The reader who has internalised the patterns rather than the products will not be inconvenienced by those losses; the reader who has memorised the products will keep relearning. That is the whole bet of writing a book about perspective rather than facts.

04 · Where computer science intersects with everything else

The substrate of this book is now the substrate of much else.

Computer science began as a branch of mathematics and electrical engineering. It is now the substrate every other discipline runs on, and increasingly the methodological lens other fields adopt. Biology now predicts a protein's shape by computation, markets match orders in microseconds across networks, and public records live in databases; each is an arc in progress now, one this book's reader will be in the middle of.

Fig 18.4 · CS intersections · the disciplines that now run on the substrate
Fig 18.4 · CS intersections · the disciplines that now run on the substrate Computing at the centre, joined to six fields, each with one thing it now does by computation: biology predicts protein structures; economics trades in microseconds; governance runs digital identity; art makes generative images and music; education tutors with language models; physics runs climate models on supercomputers. biology protein shapespredictedprotein shapespredicted economics trades inmicrosecondstrades inmicroseconds governance digitalidentitydigitalidentity art generativeimages, musicgenerativeimages, music education tutoring bylanguage modelstutoring bylanguage models physics climate onsupercomputersclimate onsupercomputers computing beneath

Six fields, each drawn with one task it now does by computation. Every task is a program running on processors, networks and stores like the ones the chapters built.

How it works
  1. Biology. The Human Genome Project finished in 2003; AlphaFold 2 (2020) predicts protein structures well enough to change how biologists work.
  2. Two directions. In some fields computing is a tool for asking questions; in others (markets, identity, media) the software is now the thing itself.
  3. The same parts. Each runs on the stack of Fig 18.1: a model training on GPUs (Chapter 16), a trading system on a low-latency network (Part III), records in databases (Chapter 13).

The vocabulary moves both ways. A computer scientist who understands the substrate of biology can talk to biologists about why a particular algorithm scales; one who understands market microstructure can talk to economists about why a particular distributed-ledger design will or will not work. "Computer science" is becoming less a separate field than a literacy, one other disciplines increasingly assume the way they once assumed statistics or basic mathematics. Within fifty years, the question may not be whether a biologist or an economist or an artist knows computer science, but which parts, to what depth, and whether they can recognise a problem in their own domain as a computational one they are trying to solve without the vocabulary. The reader who has finished this book has, on top of that vocabulary, the discipline that produced it: resolve into layers, follow the math, locate the security boundary, name the historical decision. Those four moves work in any computational domain, whatever surface field it is wearing.

05 · Reading list

The books that go deeper · one shelf per part of the book.

Every chapter of Under the Code compresses what its field spreads across several books. The compression gave the reader the whole arc and the vocabulary to navigate it; the books below give the depth to do real work. The list is curated for durability: nearly every entry has been the reference text of its field for a decade or more.

Fig 18.5 · The reading list · grouped by part of this book
Fig 18.5 · The reading list · grouped by part of this book Five shelves of further reading, one per part. Part I: Petzold's Code and Hennessy and Patterson's Computer Architecture. Part II: Kernighan and Ritchie's The C Programming Language, Tanenbaum and Bos's Modern Operating Systems, and Stroustrup's The C++ Programming Language. Part III: Fall and Stevens's TCP/IP Illustrated and Tanenbaum, Feamster and Wetherall's Computer Networks. Part IV: Silberschatz and others' Database System Concepts and Ferguson, Schneier and Kohno's Cryptography Engineering. Part V: Kleppmann and Riccomini's Designing Data-Intensive Applications. further reading · one shelf per part Part I · the physical world CodeComp. ArchitectureCodeComp. Architecture Part II · the software layer C book (K&R)Modern OSC++ (Stroustrup)C book (K&R)Modern OSC++ (Stroustrup) Part III · the network TCP/IP IllustratedComp. NetworksTCP/IP IllustratedComp. Networks Part IV · data and security DB ConceptsCrypto EngineeringDB ConceptsCrypto Engineering Part V · the modern machine Data-Intensive AppsData-Intensive Apps

One shelf for each part of the book. Titles are shortened to fit; How it works gives each in full with its authors, and adds the companions, two of them free online.

How it works
  1. Parts I and II. Petzold, Code; Hennessy and Patterson, Computer Architecture: A Quantitative Approach; K&R, The C Programming Language; Tanenbaum and Bos, Modern Operating Systems; Stroustrup, The C++ Programming Language. Also Ramalho, Fluent Python, and Kerrisk, The Linux Programming Interface.
  2. Parts III and IV. Fall and Stevens, TCP/IP Illustrated; Tanenbaum, Feamster and Wetherall, Computer Networks; Grigorik, High Performance Browser Networking (free online); Silberschatz, Korth and Sudarshan, Database System Concepts; Ferguson, Schneier and Kohno, Cryptography Engineering; Boneh and Shoup, A Graduate Course in Applied Cryptography (free draft).
  3. Part V. Kleppmann and Riccomini, Designing Data-Intensive Applications (2nd edition, 2026), the modern reference on distributed data systems; for Chapter 16, Herlihy, Shavit, Luchangco and Spear, The Art of Multiprocessor Programming.
06 · Final note

What you now see.

A book about how computers work could be a book about facts. This one tried to be a book about seeing. The discipline of this book has been to introduce nothing without its history, explain nothing without its mathematics, name no specific vulnerability without the structural pattern beneath it, describe no protocol without the human decisions that produced it. The reader who has finished (really finished, having followed the diagrams, the worked examples, the pull quotes, the cross-chapter threads) is now able, in principle, to look at any piece of software, any network, any device, and resolve it into the layers it is made of. To recognise the human decisions and historical pressures behind each of those layers, and the math underneath each one. To see where the security lives, and where it doesn't.

That recognition is not easily acquired and is rarely taught. The book has tried to teach it without hand-waving, without skipping, and without ever assuming that an idea introduced in the third chapter will be remembered three hundred pages later. Every reference back is explicit, every primitive is named, every figure builds on the figures before. If the discipline has worked, that seeing will be portable to new domains the book did not cover.

The substrate is changing. New chips and new paradigms (quantum, neuromorphic, who knows what else) will sit alongside the stack this book described. Some of the specific content will go out of date; most of the structural content won't. The reasoning skills the book asked for are not specific to any year of computing: resolve into layers, follow the math underneath, locate the security boundary, name the historical decision. They will be useful in fifty years for the same reason they are useful now: every layer of every system, whatever it runs on, will still be a mathematical structure realised in the layer below, built by specific people under specific constraints, defended by carefully chosen assumptions. The vocabulary is durable.

Eighteen chapters from a single transistor to a planet-scale distributed service is a long arc. The reader who has walked it now has, in some real sense, the substrate of modern civilization laid out in their head. The book ends. The work the reader does with what they now see is the part the book cannot end.

"The competent programmer is fully aware of the strictly limited size of his own skull; therefore he approaches the programming task in full humility, and among other things he avoids clever tricks like the plague."

Edsger W. Dijkstra, "The Humble Programmer," ACM Turing Award Lecture, 1972
Fig 18.6 · The closing zoom-out · the inverse of Chapter 1's transistor
Fig 18.6 · The closing zoom-out · the inverse of Chapter 1's transistor A single transistor at the centre, as Chapter 1 drew it. Around it, one frame at a time, the layers built on it: a CPU, a kernel, a network, a cloud and, outermost, a civilization, reached in Chapter 18. civilization cloud network kernel CPU transistorChapter 1 Chapter 18

Chapter 1 drew a single transistor. The book's last diagram shows what was built around it: a CPU, a kernel, a network, a cloud, a civilization. The same layered structure that Chapter 1 introduced as a single switch is now visible at every scale, in every direction. You have followed the chain from voltage on a wire to JSON on a screen and back. You have followed it from a thumb-sized transistor on a 1947 lab bench to a billion-transistor CPU running JavaScript inside a virtualised container inside a Kubernetes cluster inside an AWS region. You have followed it from Boole's algebra to elliptic-curve cryptography to the quantum computers that will retire it. The thing you can now see is not any one of these layers. It is the way they are all the same kind of object: a mathematical structure made physical, and re-realised in the layer above. That is what was inside the code.

How it works
  1. Where each frame was built. The transistor and the CPU in Part I, the kernel in Part II, the network in Part III, the cloud in Part V.
  2. Reading it. The figure starts at Chapter 1's transistor and adds one frame at a time, out to Chapter 18.

* * *

end of Under the Code

Colophon
Author
Yki Lähteenmäki

Helsinki-based. Wrote this book to see all the layers at once.

Published by
Atheric

A small studio for tools and writing.
© MMXXVI · YDT Holdings Oy · operating from Helsinki

End of the Book

Under the Code is built.

Eighteen chapters. Five parts. From a single transistor in 1947 to a planet-scale distributed service in 2026. Voltage to gates to instructions to kernels to languages to networks to cryptography to distributed consensus to the closing synthesis. The substrate of modern civilization, named layer by layer.

The discipline of this book is now yours: resolve into layers · follow the math · locate the security boundary · name the historical decision.

Quorum reached · 5 of 5
Back to the cover