πŸ“š Study Notes / Home / DBMS / Session 16
Session 16 Β· Distributed Databases

When one machine isn't enough: databases that span many computers

This is the final session of the whole DBMS course β€” congratulations on making it here! So far every database we studied lived on a single machine. Today we ask the big question: what if your data is too big, or too important, to trust to one computer? We'll learn how databases spread across many machines, how they stay consistent, and the famous trade-off (the CAP theorem) that no distributed system can escape. As always, every topic starts with a tiny "explain like I'm 5" story, then we go deeper with real examples.

⏱ 19 min readπŸ“– 4 topics

1 Why distribute a database?


Explain like I'm 5

Imagine you run a tiny lemonade stand and keep all your money in one piggy bank. Easy! But what if you grow into a hundred stands across the whole city? One piggy bank can't hold all the coins, and if it breaks, you lose everything. So you get many piggy banks in many places. Now you can hold more money and if one breaks, the others still work. That's what a distributed database does with data instead of coins.

Up to now in this course, every database we built lived on one machine β€” one disk, one buffer pool (recall Session 3), one set of indexes (Session 4). A distributed database is a single logical database whose data is stored on many machines (called nodes) that talk to each other over a network and cooperate to look like one database to the user.

The two big reasons to distribute

There are essentially two motivations, and it's worth keeping them separate in your head.

GoalWhat it meansWhy a single machine fails
ScalabilityHandle more data and more requests than one machine can.A single server has a hard ceiling on disk, RAM, and CPU. Eventually you simply run out.
AvailabilityKeep working even when something breaks.One machine is a single point of failure β€” if it dies, your whole app is down.

Scale up vs scale out

When a database gets overwhelmed, you have two choices:

  • Scaling up (vertical scaling) β€” buy a bigger machine: more RAM, more CPU, faster disk. Simple, but expensive and capped β€” there is no infinitely large computer.
  • Scaling out (horizontal scaling) β€” buy more machines and split the work among them. Cheaper commodity hardware, and (in principle) you can keep adding machines forever. Distributed databases are all about scaling out.
Concrete example

A social network has 2 billion users. Their profile data alone might be hundreds of terabytes β€” far more than any single disk holds. And they get millions of reads per second. No single machine on Earth can serve that. So the data is split across thousands of nodes, each holding a slice and serving a fraction of the traffic. That's scaling out.

The catch: distribution creates brand-new problems

Spreading data across machines sounds great until you realise the machines must talk over a network β€” and networks are slow and unreliable. Here are the new headaches you sign up for:

New problemWhat goes wrong
Network latencyTalking to another machine takes milliseconds β€” thousands of times slower than reading local RAM. Coordination is expensive.
Partial failureOn one machine, things either work or the whole thing crashes. With many machines, some can fail while others keep running β€” and you often can't tell whether a silent node has crashed or is just slow.
Network partitionA network fault can split the cluster into groups that can't talk to each other, even though every node is alive. This is the scenario behind the CAP theorem (Topic 4).
Keeping copies in syncIf the same data lives on several nodes, how do you make sure they all agree? (Topic 2 & 3.)
The eight fallacies

Engineers famously assume the network is reliable, fast, and free β€” it is none of those. The hardest part of distributed databases isn't storing data on many machines; it's reasoning about what happens when the messages between them are delayed, dropped, or duplicated. Almost everything in this session is really about taming partial failure.

Recap We distribute a database to scale out (handle more data/traffic than one machine can) and to stay available (survive failures). The price is a set of hard new problems β€” network latency, partial failure, and partitions β€” that single-machine databases never had to face.

2 Partitioning (sharding) & replication


Explain like I'm 5

Imagine a giant library with too many books for one room. Partitioning is splitting the books across several rooms β€” A–F in room 1, G–M in room 2, and so on β€” so each room holds only a slice. Replication is making photocopies of each room and putting them in different buildings, so if one building floods, the books still survive elsewhere. Real databases do both: split the data into slices, then keep copies of each slice.

These are the two fundamental techniques for spreading data across nodes. They solve different problems and are almost always used together.

Partitioning (a.k.a. sharding)

Partitioning (in many systems called sharding) means splitting one big dataset into smaller disjoint pieces called shards (or partitions), and putting each piece on a different node. Each row lives on exactly one shard. The goal is scalability: 10 nodes can hold 10Γ— the data and serve roughly 10Γ— the traffic.

The key decision is the partition key β€” the column whose value decides which shard a row goes to (e.g. user_id). There are two classic strategies:

StrategyHow it assigns rowsGood atWeak at
Hash partitioningCompute hash(key) % N (or look it up in a hash ring) to pick the shard.Spreading rows evenly β€” avoids hotspots.Range queries (e.g. "all users created in June") β€” they hit every shard.
Range partitioningAssign contiguous ranges of the key to shards (A–F β†’ shard 1, G–M β†’ shard 2…).Range queries β€” they touch just a few adjacent shards.Hotspots β€” a popular range (e.g. recent timestamps) overloads one shard.
Worked example: routing a query

Suppose users are hash-partitioned across 4 shards on user_id. A client asks:

SELECT * FROM users WHERE user_id = 8821;

The router (or smart client) computes which shard owns key 8821:

// pick the shard for this key
shard = hash(8821) % 4   // e.g. β†’ 1
route query to "shard-1" only

Only one node does work β€” fast and cheap. But now ask for everyone created last week:

SELECT * FROM users WHERE created_at > '2026-06-13';

Because hashing scattered those rows everywhere, the router must fan out the query to all 4 shards and merge the results β€” a scatter/gather query. This is exactly the trade-off in the table above: hashing is great for point lookups, painful for ranges.

How queries find the right shard (routing)

Something has to know the map from "partition key β†’ node". Three common designs:

🧭
Smart client
The client library knows the map and contacts the right node directly
β†’
πŸ”€
Routing tier
A proxy/coordinator receives every query and forwards it
β†’
πŸ—ΊοΈ
Any node
Hit any node; it forwards to the owner if needed

Rebalancing

When you add nodes (to grow) or one dies, the data has to be moved so the load stays even β€” this is rebalancing. NaΓ―ve hash % N is terrible here: change N from 4 to 5 and almost every key remaps to a different shard, forcing a massive data shuffle.

Consistent hashing

The standard fix is consistent hashing (and "virtual nodes"): keys and nodes are placed on a conceptual ring, and a key belongs to the next node clockwise. Adding or removing a node only moves the keys in one arc of the ring β€” roughly 1/N of the data β€” instead of remapping everything. Systems like Cassandra and DynamoDB use this.

Replication

Replication means keeping copies of the same data (each shard, or the whole database) on multiple nodes called replicas. Its goal is availability and durability: if one replica dies, another still has the data, so reads and writes can continue and nothing is lost.

The most common pattern is leader–follower (also called primary–replica): one replica is the leader that accepts writes, and it streams those changes to one or more followers that can serve reads.

✍️
Write
Client writes to the leader
β†’
πŸ“‘
Replicate
Leader ships the change log to followers
β†’
πŸ“–
Read
Clients can read from any follower
Sync vs async β€” a real trade-off

If the leader waits for followers to confirm before acknowledging a write (synchronous), you never lose data but writes are slower and stall if a follower is down. If it doesn't wait (asynchronous), writes are fast but a follower can lag behind β€” so a read from a follower might return stale data, and a crash can lose the last few writes. This tension between fresh and fast is the seed of the CAP theorem (Topic 4).

Key takeaway

Partitioning is for scale (split data so more nodes share the load). Replication is for availability (copy data so a failure doesn't lose it or stop service). Big systems combine them: split into shards, and replicate each shard across several nodes.

Recap Partitioning (sharding) splits data into disjoint shards by a partition key β€” hash for even spread, range for range queries. Queries route to the owning shard (or scatter/gather across all). Replication keeps copies for availability, usually leader–follower, with a sync-vs-async trade-off. Consistent hashing keeps rebalancing cheap when nodes come and go.

3 Distributed transactions & Two-Phase Commit


Explain like I'm 5

Three friends want to all jump into a pool together β€” nobody wants to jump if even one is going to chicken out. So one friend becomes the "counter." She shouts "Ready?" and waits for every friend to yell "Ready!". Only when everyone has said yes does she shout "JUMP!" and they all jump at once. If even one says "no", she shouts "Don't jump!" and nobody moves. That careful two-step β€” ask everyone, then tell everyone β€” is exactly how databases make many machines commit a transaction together.

Recall atomicity from our transactions session: a transaction is all-or-nothing. Easy on one machine. But what if a single transaction touches data on several nodes β€” say, move $100 from an account on shard A to an account on shard B? Now atomicity must hold across machines: either both nodes commit, or neither does. This is the atomic commit problem, and it's genuinely hard because any node (or the network) can fail at the worst possible moment.

Two-Phase Commit (2PC)

The classic solution is Two-Phase Commit (2PC). One node acts as the coordinator; the others are participants. As the name says, it runs in two phases.

PhaseCoordinator doesParticipants do
1. Prepare (voting)Sends PREPARE to every participant: "can you commit?"Each does the work, writes it durably to its log, then votes YES (and promises it can commit) or NO.
2. Commit / AbortIf all voted YES β†’ send COMMIT to all. If any voted NO (or timed out) β†’ send ABORT to all.Apply the decision (commit or roll back) and reply ACK.
The crucial promise

When a participant votes YES, it is making a binding promise: it must be able to commit later no matter what β€” even if it crashes and restarts in between. That's why it writes everything to its durable log before voting (recall write-ahead logging from our recovery session). It cannot change its mind. This is what makes the all-or-nothing guarantee possible.

The handshake, as a sequence diagram

Coordinator                Participant A        Participant B
     |                          |                    |
     |  ---- PREPARE ------->    |                    |
     |  ---- PREPARE ------------------------------>  |
     |                          | (log, then vote)   |
     |  <------- YES ----------- |                    |
     |  <------- YES ------------------------------   |
     |                          |                    |
   (all YES β†’ decide COMMIT, log the decision)       |
     |                          |                    |
     |  ---- COMMIT -------->    |                    |
     |  ---- COMMIT ---------------------------->     |
     |  <------- ACK ----------- |                    |
     |  <------- ACK ------------------------------   |
     |                          |                    |
   (transaction is now committed everywhere)
Worked example: the $100 transfer
  • Prepare: Coordinator asks shard A "can you take $100 off Alice?" and shard B "can you add $100 to Bob?" Both check funds/locks, log the change, and vote YES.
  • Commit: Both said YES, so the coordinator logs "COMMIT" and tells both to apply. Money leaves Alice and arrives at Bob β€” atomically across two machines.
  • Abort path: If shard A had said NO (insufficient funds), the coordinator tells both to ABORT; Bob never receives the phantom $100. No half-completed transfer is possible.

The blocking weakness

2PC has one famous, fatal flaw. Suppose participants have all voted YES and are waiting for the decision β€” and right then the coordinator crashes. The participants are now stuck: they've promised to commit, so they can't unilaterally abort, but they also haven't heard "commit", so they can't proceed. They must hold their locks and wait, possibly for a long time, until the coordinator recovers. This is called blocking, and it makes the coordinator a single point of failure.

Why this matters

While participants block, the rows they touched stay locked, so other transactions queue up behind them. A single coordinator crash can stall part of the system. 2PC also adds latency (two network round-trips) to every distributed transaction β€” which is why many large systems try to avoid cross-shard transactions altogether by choosing partition keys cleverly.

Beyond 2PC

Smarter protocols were invented to fix the blocking problem:

  • Three-Phase Commit (3PC) β€” adds an extra "pre-commit" phase so participants can make progress even if the coordinator dies. It's non-blocking in theory, but assumes a more reliable network and is rarely used in practice.
  • Consensus protocols β€” Paxos and Raft β€” instead of one fragile coordinator, a group of nodes agrees on each decision by majority vote. As long as a majority survives, the system keeps making progress with no single point of failure. Raft is the modern, easier-to-understand favourite and powers systems like etcd, CockroachDB, and TiDB. These protocols are also how a cluster reliably elects a new leader for replication (Topic 2) when the old one dies.
Recap A distributed transaction must be atomic across nodes. Two-Phase Commit solves this with a coordinator: a prepare phase where everyone durably votes YES/NO, then a commit/abort phase where the coordinator's decision is applied everywhere. Its weakness is blocking: if the coordinator crashes after votes are in, participants are stuck holding locks. 3PC, Paxos, and Raft were designed to remove that single point of failure.

4 The CAP theorem


Explain like I'm 5

You and your sister each have a walkie-talkie and a shared toy list. You promise: (1) you'll always agree on the list, and (2) you'll always answer when asked. But one day the walkie-talkies stop working β€” you can't reach each other. Now if your sister asks "what's on the list?", you have two choices: answer with what you have (maybe wrong, because you can't check with her), or refuse to answer until the walkie-talkies work again. You can't both always-answer and always-agree when you can't talk. That impossible choice is the CAP theorem.

The CAP theorem (Eric Brewer, 2000) is the single most important idea in distributed databases. It says that during a network partition, a distributed system can guarantee at most two of these three properties:

LetterPropertyPlain English
CConsistencyEvery read sees the most recent write β€” all nodes show the same, up-to-date data. (Note: this is not the same "C" as in ACID.)
AAvailabilityEvery request gets a (non-error) response β€” the system always answers.
PPartition toleranceThe system keeps working even when the network drops/delays messages between nodes.

Why it's really a choice between C and A

Here's the subtle but vital point: in any real distributed system, network partitions will happen β€” cables break, switches fail. You don't get to opt out of P. So the theorem really forces a choice that only matters during a partition: when two halves of your cluster can't talk, do you prioritise C or A?

πŸ”Œ
Partition hits
Network splits the cluster in two
β†’
πŸ›‘
Choose CP
Refuse risky requests to stay consistent
β†’
βœ…
Choose AP
Keep answering, accept possibly stale data
Worked example: the split bank

A bank's database is replicated across two data centres, and the link between them breaks. Alice has $100. Her phone hits centre 1; her laptop hits centre 2. Both try to withdraw $100 at the same time.

  • CP choice: Each centre refuses to act alone (it can't confirm with the other). One or both withdrawals get an error: "try again later." Annoying, but Alice can never overdraw β€” the data stays correct. The system sacrificed availability to keep consistency.
  • AP choice: Each centre happily approves its withdrawal using its local copy. Both succeed β€” Alice pulls out $200 she didn't have! Available, but now inconsistent; the conflict must be reconciled later. The system sacrificed consistency to keep availability.

For a bank, CP is the obvious choice. For a "likes" counter on a photo, AP is fine β€” who cares if the count is briefly off by one? The right answer depends on the application.

CP vs AP databases in the real world

TypeDuring a partition…Real examplesGood for
CP (consistency + partition tolerance)May reject requests to avoid serving stale/conflicting data.HBase, MongoDB (default), Google Spanner, etcd, ZooKeeper, traditional RDBMS clusters.Banking, inventory, anything where wrong data is unacceptable.
AP (availability + partition tolerance)Always answers, may return stale data and reconcile later.Cassandra, DynamoDB, Riak, CouchDB.Shopping carts, social feeds, metrics, sensor data β€” always-on matters more than perfect freshness.
Eventual consistency & the PACELC refinement

AP systems usually offer eventual consistency: if writes stop, all replicas eventually converge to the same value β€” they're just allowed to disagree briefly. CAP is also a bit simplistic: the newer PACELC theorem adds that even when there's no partition (E, "else"), you still trade off latency (L) against consistency (C). In short: consistency almost always costs you either availability or speed.

How this ties back to everything

CAP is the deep reason behind every trade-off in this session. The sync-vs-async replication choice in Topic 2? That's choosing C vs A/latency. The blocking weakness of 2PC in Topic 3? That's a CP system choosing consistency over availability when the coordinator is unreachable. If you've studied the High-Level Design (HLD) subject's CAP session, this is the same theorem viewed from the database side β€” the system-design lens and the database lens meet exactly here.

Recap The CAP theorem says that during a network partition you can keep at most two of Consistency, Availability, and Partition tolerance β€” and since partitions are unavoidable, you're really choosing C vs A. CP systems (Spanner, HBase, MongoDB) refuse rather than serve wrong data; AP systems (Cassandra, DynamoDB) stay up and reconcile later (eventual consistency). The right pick depends on whether your app can tolerate stale data.

β˜… Putting it all together


You've reached the end β€” not just of this session, but of the entire DBMS course. Take a moment: that's a huge accomplishment. πŸŽ‰

The big idea

We distribute a database to scale out and stay available, paying for it with network latency and partial failure. We spread data with partitioning (hash or range sharding, routed to the owning shard, rebalanced cheaply via consistent hashing) and protect it with replication (leader–follower, sync vs async). When a transaction spans nodes, we get atomicity with Two-Phase Commit β€” a prepare vote then a commit/abort decision β€” whose blocking weakness pushed the world toward consensus protocols like Raft. And underneath every one of these choices sits the CAP theorem: when the network splits, you choose consistency or availability, never both.

The journey you just completed

Look back at how far you've come. The whole course was one long climb up the database stack:

LayerWhat you learned
ArchitectureWhat a DBMS is and how its components fit together.
StorageHow rows and pages actually live on disk.
Buffer poolHow the database caches pages in memory and decides what to evict.
IndexesB+ trees and hashing to find data fast instead of scanning everything.
Query parse β†’ execute β†’ optimizeHow SQL becomes a plan, how operators run it, and how the optimizer picks the cheapest plan.
Transactions & concurrencyACID, isolation levels, locking, and serving many users at once without chaos.
RecoveryWrite-ahead logging and how a database survives crashes without losing committed data.
Modern & distributedNew architectures, and today β€” spreading a database across many machines.
Don't forget MiniDB

Along the way you didn't just read β€” you built. The MiniDB capstone tied the whole stack together into a working miniature database engine: storage, buffer management, indexing, query execution, and transactions, all in code you wrote yourself. That hands-on project is your proof that these ideas aren't just theory β€” you can implement them.

Quick self-check

What are the two main reasons to distribute a database?

Scalability (handle more data and traffic than one machine can β€” scaling out) and availability (survive failures so there's no single point of failure).

What's the difference between partitioning and replication?

Partitioning (sharding) splits data into disjoint pieces across nodes for scale β€” each row lives on one shard. Replication keeps copies of data on multiple nodes for availability and durability.

When would you pick range partitioning over hash partitioning?

When you do a lot of range queries (e.g. "all orders between two dates"), because range partitioning keeps nearby keys on the same/adjacent shards. Hashing scatters them, forcing scatter/gather.

What is the blocking problem in Two-Phase Commit?

If the coordinator crashes after participants have voted YES but before they hear the decision, the participants are stuck holding locks β€” they can't abort (they promised to commit) and can't proceed (no commit message). They must wait for the coordinator to recover.

During a network partition, what does a CP system do that an AP system doesn't?

A CP system refuses (errors out) requests it can't safely serve, to avoid returning stale or conflicting data β€” it sacrifices availability for consistency. An AP system keeps answering with whatever it has and reconciles later (eventual consistency).

Why is the CAP theorem really a choice between C and A?

Because partitions (P) are unavoidable in real networks, so you can't drop P. The only real decision is what to do when a partition happens: favour consistency or favour availability.

πŸ“š References & Further Reading


Class material

Papers, docs & deep dives