One database
can't take all the traffic.
Every technique on this page answers one question: when a single database server runs out of room, CPU, or write throughput — where does the next request go? Drag, click, and break things below to feel each answer instead of just reading it.
What actually happens when you "save to a database"
A database is a program with one job: store data safely, and hand it back fast. Every request — an app saving an order, a page loading a profile — follows roughly the same path.
One server can do this fast — for a while. It has a fixed amount of CPU, memory, and disk I/O, so it can only process so many requests per second before work starts piling up. Hit Start, then drag the slider and watch it happen live.
Single database server
Vertical vs. horizontal — the first fork in the road
Before picking an algorithm, every team hits this decision. It's cheap to delay, expensive to reverse.
Vertical scaling simple, finite
Give the same single database server more CPU, RAM, and faster disks. No code changes, no new failure modes — but every cloud provider has a biggest instance size, and you eventually hit it. It also means one machine is a single point of failure.
Horizontal scaling complex, unbounded
Spread data and load across many machines: more replicas for reads, more shards for writes and storage. Scales close to linearly, but now you're managing a distributed system — replication lag, partial failures, and routing logic all become your problem.
Replication: scale reads, survive failure
One leader takes writes; one or more followers copy its data and serve reads. Click write and watch how long it actually takes the copies to catch up.
Try this: click "Write", then immediately click "Read from a follower" before the bars finish filling. That gap is why "read-your-own-writes" bugs exist — the classic symptom of leader–follower replication.
Single-leader (leader–follower) common default
All writes go through one leader; followers replicate asynchronously (fast, can lose the last few writes on crash) or synchronously (safe, but writes wait on the slowest follower). Used by Postgres, MySQL, MongoDB replica sets.
Multi-leader write anywhere
Multiple leaders (often one per data center) each accept writes and replicate to each other. Great for multi-region latency, but two leaders can accept conflicting writes to the same row at once — you need a conflict-resolution strategy (last-write-wins, CRDTs, app-level merge).
What this actually looks like on real servers
The "balance = $500" box above is a simplification. Nothing that clean gets sent over the wire. What a leader really ships to its followers is its write-ahead log (WAL) — an append-only file of raw change records — which each follower replays against its own copy of the data files. Click the button and watch a real write travel as log entries, not values.
Each entry is one WAL record — the actual unit shipped over TCP. A replica doesn't receive "balance = $500"; it receives something like "at byte offset X, change these bytes on this page" and replays that against its own on-disk copy. That's why a replica can briefly be a few records behind: it's a receive-and-replay pipeline, not a shared value.
Want to actually set this up? step-by-step guide
A separate walkthrough of the real commands — creating the replication user, editing pg_hba.conf and postgresql.conf, taking a base backup, and verifying it's streaming — plus a Docker Compose version to try locally.
Same idea, different names
"Leader/follower," "primary/replica," and "master/replica" all describe the same role split — which node accepts writes vs. which one copies it. The exact term just depends on which database you're reading about.
| Database | Write node is called | Copy node is called |
|---|---|---|
| PostgreSQL | primary | standby / replica |
| MySQL | source (formerly "master") | replica |
| MongoDB | primary | secondary |
| Redis | master | replica |
| Kafka | leader (per partition) | follower |
Partitioning: when one machine can't hold the data
Replication copies the whole dataset everywhere. Partitioning (sharding) splits the dataset itself across machines, so each one only holds a slice. The question every strategy answers differently: given a key, which shard owns it?
Range-based
Sort keys, split into contiguous ranges (IDs 1–1000 → Shard 0, 1001–2000 → Shard 1…). Great for range queries ("give me orders from last week"), but sequential keys concentrate all current writes on one shard — see the simulation below.
Hash-based
shard = hash(key) % N. Spreads writes evenly — no hot shard. The cost: adding or removing a shard changes the modulus, so almost every key remaps to a new shard at once. Massive, unnecessary data movement.
Consistent hashing
Place shards and keys on the same hash ring; a key belongs to the next shard clockwise. Adding or removing a node only remaps the keys between it and its neighbor — everyone else is untouched. Used by Cassandra, DynamoDB, Riak, CDN request routing.
Directory-based (lookup service)
A separate service keeps an explicit key → shard map. Maximum flexibility (move any single key any time, rebalance by hand) at the cost of an extra hop and a new single point of failure — the directory itself usually needs to be replicated.
Simulation — sequential IDs, range vs. hash
Watch where each new row lands as IDs increment one by one (like an auto-increment primary key, the most common real-world case).
Interactive — consistent hashing ring
Add or remove nodes and watch how few keys actually move. Click any node to remove it. Type a key below to see exactly where it routes.
Federation split by function
Instead of splitting one table across shards, split by function: a Users database, an Orders database, a Payments database — each on its own server. Simple to reason about, but you lose cross-domain joins and transactions; the application has to stitch data back together.
Denormalization + caching trade writes for reads
Duplicate data across tables/shards so reads don't need joins across machines, and put a cache in front of hot reads entirely. Writes get more expensive and consistency gets harder — but most apps are read-heavy, so the trade usually wins.
Real application behavior — a checkout service under load
Users are sharded by user_id across 3 primaries, each with one async replica. Watch what happens when traffic is normal — and when one user suddenly goes viral.
The lesson: sharding by user_id spreads out normal traffic beautifully, but it can't save you from one abnormally hot key — every request for that one user still lands on the same shard. Fixing it needs a different move: cache their reads, add a replica to absorb read load, or split that one user's data further.
The CAP theorem — pick your trade-off
During a network partition, a distributed database can guarantee at most two of Consistency, Availability, and Partition tolerance. Click two corners.
Click any two corners of the triangle
Partition tolerance means the system keeps working even when network links between nodes drop or lag badly. In any real distributed database — deployed across more than one machine — partitions will happen sooner or later. So P usually isn't optional; the real everyday choice is between C and A.
Decision cheat sheet
Print this in your head before your next design interview.
| Technique | Solves | Trade-off | Reach for it when |
|---|---|---|---|
| Vertical scaling | Not enough CPU/RAM/disk | Hard ceiling, single point of failure | Early stage, simplest possible fix |
| Read replicas | Too many reads for one server | Replication lag, stale reads | Read-heavy workloads (most CRUD apps) |
| Leader–follower | Reads + durability | Single write bottleneck remains | Default choice for most systems |
| Multi-leader | Multi-region write latency | Conflict resolution complexity | Users write from multiple regions |
| Range sharding | Storage + range-query performance | Hot shard on sequential keys | Time-series / range-scan heavy data |
| Hash sharding | Even write distribution | Massive remap on resize | Fixed, rarely-resized cluster |
| Consistent hashing | Even distribution + cheap resize | Extra routing layer to build/run | Clusters that grow/shrink often |
| Directory-based | Maximum placement flexibility | Directory is a new dependency | Need per-key control (tenant isolation) |
| Federation | One giant DB doing everything | Loses cross-domain joins/transactions | Clear functional boundaries exist |
| Denormalize + cache | Expensive joins on hot reads | Write cost, cache invalidation | Read-to-write ratio is high |