*Sharding* splits one logical dataset across many machines so the cluster can hold and serve more data than any single node can.
3 figures
sharding-replication
Sharding vs replication: capacity vs availability
Sharding and replication solve different problems and almost always run together. Sharding splits a 10 TB table into disjoint slices across machines, each row on exactly one shard, to scale capacity past one machine. Replication keeps two or three copies of the same data on different machines to survive single-machine failures and serve more reads. Production systems shard then replicate each slice, taking capacity from sharding and availability from replication, as Cassandra, sharded MongoDB clusters, and BigQuery's storage layer do.
Notes ↗sharding-replication · Production Synthesis
In production: shard, then replicate
In production, both at once. Spotify's 10 TB Listens is sharded ten ways for capacity, then each shard is replicated three times on different racks. A user's shard lives on three machines, so a read can hit any of them and a dead machine loses nothing.
Notes ↗sharding-replication · Common Confusions
Why you need both: each mechanism alone leaves a gap
Why both are needed. Sharding without replication (left): when a machine dies its shard goes offline, locking out one in ten users. Replication without sharding (right): every machine must hold the whole 10 TB, which never fits on a 1 TB disk. Only running both closes both gaps.
When one transaction touches several shards, *two-phase commit* makes the commit decision atomic across all of them: a coordinator first asks every shard to *prepare*, then tells them all to *commit* only if every shard voted yes.
1 figure
distributed-commit · Two-Phase Commit
Two-phase commit: prepare, then commit
Two-phase commit turns many shards into one atomic decision. In the prepare phase the coordinator asks each shard whether it can commit; a shard logs its change durably and votes yes, or votes no. The coordinator commits only if every vote is yes, logs that decision, then in the commit phase tells all shards to apply it. One no anywhere forces a global abort, so the transaction is all-or-nothing across machines.
Replicas keep copies of the data on several machines, so it survives a dead disk or a dead node.
4 figures
consensus-leader-election · Option 1: Write to All Replicas
Option 1, write to all replicas: every copy must confirm, so one slow or dead replica stalls the write
Writing to all replicas has no fault tolerance. A write is safe only once every copy acknowledges, so a single slow or crashed replica (amber) stalls every write while the client waits. In a cluster where machines fail routinely, waiting on all of them is unusable.
Notes ↗consensus-leader-election · Option 2: Write to a Leader
Why a majority works: any two majorities overlap
Any two majorities of a five-node cluster must overlap on at least one node. Since each node casts one vote per term, that shared node cannot put two candidates over the top, so at most one leader is ever elected. That same overlap lets a majority both tolerate failures and prevent split-brain.
Notes ↗consensus-leader-election · Option 2: Write to a Leader
Leader election: one leader fails, the cluster elects another
Raft elects a new leader after the old one fails. Each node votes at most once per term, so only one candidate can clear a majority, which rules out split-brain. The timeout that triggers the election is randomized per node, so followers rarely become candidates at the same instant. Production Raft systems like etcd and Consul elect a new leader within about 200 ms of the failure.
Notes ↗consensus-leader-election · Option 2: Write to a Leader
Synchronous vs asynchronous replication
The two modes trade latency against durability. Synchronous replication forwards each write to a follower and waits for its acknowledgement before replying OK, so a failover to that follower loses no committed write, at the cost of a full round trip per write. Asynchronous replication replies OK as soon as the leader has the write and ships it to followers in the background, which is fast but means a leader crash can lose any writes that had not yet shipped. The gap between leader and follower is replication lag.
CAP says a distributed database can guarantee at most two of Consistency, Availability, and Partition tolerance.
1 figure
cap-theorem · Pick Two
CAP: partition tolerance is a given, all three at once is impossible, so your one choice is the consistent side or the available side
Partition tolerance is a given, not a choice. Consistency means every read sees the latest committed write. Availability means every request gets a response even when some nodes are unreachable. Partition tolerance means the system keeps operating when the network drops messages between groups of nodes. Real networks fail, so partition tolerance is mandatory (the green circle you always live inside), and during a partition your one choice is C or A: stay consistent and reject what you cannot verify (CP), or stay available and risk serving stale data until it reconciles (AP).
Distributed sort and hash are the two foundational data-movement primitives in a cluster: *hash partitioning* routes every row with the same key to the same machine (so joins and group-bys can run locally), and *distributed sort*
3 figures
distributed-sort-hash · Distributed Sort: One Total Order Across Machines
Distributed sort in three phases
Distributed sort in three phases. Each node first sorts its own partition locally. The cluster then samples the data to pick split points that divide the value range into equal pieces (here ≤3, 4 to 6, ≥7). Finally rows redistribute so each node owns one contiguous range, and reading the nodes in order gives a single total sort. Sampling is what keeps each node's share balanced even when the data is skewed.
Notes ↗distributed-sort-hash · Hash Partitioning: Foundation of Distribution
Distributed hash join: hash both tables on the join key user_id, so every row for a user_id meets on one machine, then join locally
A distributed hash join. Both tables are hashed on the join key user_id, so every row for one user_id, from either table, is routed to the same machine (the colour is the key value: 7 blue, 42 orange, 88 purple). Each machine joins only the rows it holds, then the partial results union into the full join. No machine compares its rows against another machine's, so throughput scales with the cluster.
Consistent hashing: each key goes to the first node clockwise, and adding a node moves only the small arc behind it
Consistent hashing places both keys and machines on a ring over 0 to 2³²−1; each key belongs to the first node clockwise from it. A new node steals only the keys between it and its predecessor, about 1/N of them, so the cluster grows without the near-total reshuffle hash % N forces. DynamoDB, Cassandra, memcached pools, and CDN load balancers all run on this.
Case Study: How did Google store the entire internet on cheap, dying hard drives?
4 figures
case-study-gfs · How did Google store the entire internet on cheap, dying hard drives?
Cheap hardware at scale: failure becomes routine
Cheap hardware at scale. Google linked thousands of commodity disks instead of a few costly enterprise drives. Across ten thousand disks a failure every day is a certainty, shown by the red disks in the grid, so the software treats disk loss as routine.
Notes ↗case-study-gfs · How did Google store the entire internet on cheap, dying hard drives?
Sharding: capacity beyond one disk
Sharding for capacity. A 100 TB file cannot fit on a 1 TB disk, so GFS splits it into 64 MB chunks, about 1.6 million of them, and scatters them across machines. Total capacity then grows with every machine added to the cluster.
Notes ↗case-study-gfs · How did Google store the entire internet on cheap, dying hard drives?
GFS: a leader directory over cheap chunk servers
The GFS lookup. Data never streams through the Leader: the client asks it only for a chunk's replica locations (steps 1 and 2), then reads the chunk directly from a Chunk Server (step 3). The Leader holds just the directory, small enough to keep in memory. Every chunk is stored as three replicas on different servers, here chunk C7 on all three, so a dead disk costs nothing.
Notes ↗case-study-gfs · How did Google store the entire internet on cheap, dying hard drives?
Replication: self-healing storage
Replication and self-healing. Every chunk is stored as three identical replicas on different racks. When a server dies, two replicas keep serving, and the Leader copies the chunk to a fresh healthy server to restore three copies.
A distributed file system splits each large file into fixed-size chunks, replicates each chunk across many cheap commodity servers, and gives clients a single-namespace view, so the cluster delivers sequential read throughput prop
2 figures
distributed-file-systems · The Pattern, and What Changes Across Generations
Distributed file systems: how metadata coordination evolved
Four generations share one chunks-and-replicas substrate but coordinate metadata differently, and that single choice sets each scale ceiling. GFS (2003) and HDFS (2006) keep all metadata on one Master or NameNode (64 MB / 128 MB chunks, 3× replication, CP), so the single leader caps them near petabyte scale. S3 (2006) drops the leader for distributed metadata (redundant across ≥3 availability zones, designed for 11 nines of durability) and scales effectively without limit; since 2020 it is strongly read-after-write consistent (earlier, eventually consistent). Colossus (2010+) shards metadata across many nodes with Reed-Solomon erasure coding (~50% overhead versus 200% for 3× replication, CP) and reaches exabyte scale.
Notes ↗distributed-file-systems · Colossus (2010+): GFS Without the Bottleneck
Colossus: Reed-Solomon erasure coding
Reed-Solomon erasure coding. Colossus splits data into data blocks and computes parity blocks from them, for example six data plus three parity. Any three of the nine can be lost and rebuilt from the six that survive, so it tolerates three failures at 50% storage overhead, where three full copies cost 200% and survive only two failures.
MapReduce is a programming model where you write two functions, map(record) → (key, value) and reduce(key, listvalue]) → result, and the framework distributes the work across thousands of machines using hash partitioning to shuffl
1 figure
mapreduce · A Worked Example: Top Songs Across 100B Plays
MapReduce: top songs across 100 billion plays
MapReduce finds the top songs across 100 billion plays. 1,000 mappers each read one 3×-replicated chunk and emit (song_id, 1) per play. The shuffle partitions pairs by hash(song_id) % R so the same key always reaches the same reducer. Each reducer sums the 1s for its slice of song IDs and writes the per-song counts. The same code runs in about 10 minutes on 1,000 machines instead of weeks on one.
Spark generalizes MapReduce by representing a dataset as a *Resilient Distributed Dataset* (RDD): a partitioned, lazily-evaluated, lineage-tracked collection.
7 figures
spark · RDDs: The In-Memory Pipeline
An RDD is a partial result
One query, four RDDs. Every step leaves behind the data as it stands so far, and only the last box is the answer anybody asked for. The other three are working state. MapReduce wrote each of them to disk so the next step could read it back; Spark keeps them in memory. Pluto never appears after the grouping, because Pluto has no listens.
Same multi-stage pipeline: MapReduce writes to disk, Spark stays in RAM
The same three-stage analytics pipeline (filter to pop, count by user, sort the top), two engines. MapReduce runs it as three chained jobs and materializes each job's output to HDFS before the next reads it (about 3 minutes); Spark chains the same three stages but keeps intermediate RDDs in memory and evaluates lazily, building a plan it runs once when an action triggers it (about 2 seconds). The ~100× gap comes from data staying in RAM between stages, operator fusion from lazy evaluation, and cheap lineage-based recovery instead of replication.
Notes ↗spark · Resilience: Lineage, Not Replicas
Spark recovers a lost partition by replaying its lineage
Spark recovers lost data by replaying lineage instead of keeping replicas. Each RDD records the transformation that produced it (filter, map, map), so Spark stores the operator graph, not the intermediate data. These are all narrow transformations, so each output partition depends on exactly one upstream partition: when a worker dies and takes RDD3.P1 with it, Spark re-runs filter → map → map on just the still-alive RDD0.P1. Storage drops from 3× to 1×, and the cost of a failure rises from reading a replica to recomputing one lost partition. A wide transformation like groupBy would instead re-read every upstream partition through a shuffle.
Spark SQL: the same SQL text, executed distributed
Spark SQL runs the same SQL as Postgres but executes it distributed. The Spotify genre-by-week analytics query (a join across listens and songs) is identical text; Catalyst compiles it to RDD operations: broadcast the smaller songs table to every node, join it locally against each listens partition, then group and count. PostgreSQL on one machine takes hours at 1 TB, spilling the join and sort to disk on a single node with no cluster to parallelize across, while Spark on 100 machines finishes in minutes and scales linearly with cluster size. No code changes between single-node and distributed: the optimizer picks join strategies and partition pruning the same way a single-node optimizer does.
Notes ↗spark · Spark at Real-World Scale: Netflix Recommendations
One query, two surfaces
One query, two surfaces. Read the highlights across: the table, the join, the grouping and the average are the same four operations either way, and Catalyst compiles both to the same plan, so you can write whichever one you prefer. The one real difference is order. SQL puts the average near the top, because it states the result you want; the chain puts it last, because it states the steps in the order they run. Both are lazy, so an action such as show() asks for the rows.
Notes ↗spark · When the Map Step Is a Model Call
The map slot takes a model
The slot that held upper(title) now holds a model endpoint, and the engine underneath is unchanged: the same four partitions, the same executors, the same lazy plan and the same retries. Only the function moved off the worker and onto an endpoint. At this scale the hard parts are the ones this module already covers: partitioning, retries, stragglers and cost. The model is the easy part.
Notes ↗spark · What Breaks: Lineage Meets Non-Determinism
Replay gets a different answer
The same recovery that makes Spark cheap makes a model pipeline inconsistent. Production pipelines close the gap three ways, and only the last one closes it completely: temperature zero narrows the spread while the token can still move; caching the answer against a hash of the input row makes a replay read rather than ask; and materialising inference output to a table removes recomputation entirely, so real pipelines write predictions down once and read them after that.
BigQuery is a serverless OLAP query engine: you submit SQL, Google's compute layer auto-provisions thousands of ephemeral "slots" against shared columnar storage, and you get an answer in seconds.
2 figures
bigquery · The Architectural Shift: Decoupled Storage and Compute
BigQuery is serverless OLAP: storage and compute scale separately
BigQuery is serverless OLAP: storage and compute scale and bill independently. Each query spins up roughly 2,000 ephemeral slots that run in parallel and vanish when it finishes, billed per byte scanned (about $6.25/TiB). Colossus holds the table as persistent columnar files, one column per file, billed per gigabyte at rest (about $20/TB/month). No cluster to provision: you pay for the bytes you scan, not for idle capacity.
Notes ↗bigquery · A Worked Query: Spotify Top Artists, 100B Plays
Behind a BigQuery API call: five stages, from planning a columnar scan to returning the top 100 in about 30 seconds
Behind one API call, the query runs as five stages. Plan: the optimizer identifies artist and year as the only referenced columns, so the columnar layout (from the Module 3 BigQuery case study) reads only those two columns and skips the rest; over 100 billion rows they compress to ~200 GB, a fraction of the full 10 TB. Schedule: ~2,000 ephemeral slots are split across the stages. Scan: each slot reads a Colossus partition and hash-aggregates by artist into partial counts. Shuffle and merge: a second wave hash-partitions those counts by artist, sums, sorts, and trims to the top 100, the same hash-partitioning as Distributed Sort & Hash. Return: the result lands in ~30 s, slots release, and you pay only for the ~200 GB scanned, about $1.25.
Kafka is a durable, partitioned, replicated commit log: producers append events to per-topic partitions, consumers read them at their own pace, and the log itself is the source of truth, so multiple downstream systems can independ
1 figure
kafka · A Worked Example: One Play, Two Pipelines
Kafka is a partitioned, replicated commit log
Kafka is a partitioned, replicated commit log. The producer hashes each event's key (here user_id) to pick a partition, so one user's events stay in order; the topic's 3 partitions each replicate 3× across brokers, giving parallelism and ordering from partitions and durability from replicas. Two independent consumers, Recommendations and Royalty, track their own offsets and read at their own pace, and because events stay until retention expires, either can rewind and re-read. Typical scale: millions of events per second, under 10 ms end to end, retained for days or longer.