Vibe Engines
YouTube

Vibe Engines is free, interactive AI engineering & system design — runs in your browser. Pick a learning path

System Design Fundamentals  /  Partitioning & Replication
Fundamental 03~13 min readIntermediate
Deep Dive

Partition the data to scale.
Replicate it to survive.

One machine can't hold the internet, and one disk failure shouldn't erase your product. Partitioning answers "where does this key live?" and replication answers "how many copies survive a failure?" — two different questions you almost always answer together.

01

Two questions, not one

Partitioning (sharding) splits one dataset across many machines so each holds a slice — it scales capacity and throughput. Replication copies the same data onto several machines — it provides fault tolerance and read scaling. They're orthogonal, and production systems combine them: shard the data into N partitions, then keep R replicas of each partition.

Partitioning (sharding)Replication
GoalScale capacity & write throughputFault tolerance & read scaling
Each node holdsA different slice of the dataA full copy of its data
Lose a node →Lose that slice (unless replicated)Lose nothing; a copy survives
Key decisionThe shard keyTopology & sync vs async
02

How to partition

The partitioning strategy decides which node owns a given key.

StrategyHowStrengthWeakness
RangeContiguous key ranges per nodeEfficient range scansHotspots on sequential keys
Hashhash(key) → nodeEven spreadRange scans hit every node
DirectoryA lookup service maps key → nodeFlexible, easy rebalancingThe directory is a dependency

Plain hashing — node = hash(key) % N — has a fatal flaw: change N (add or lose a machine) and almost every key remaps, forcing a full reshuffle. That's what consistent hashing fixes.

03

Consistent hashing

Add a node and move a few keys — not all of them.

Map both keys and nodes onto a circular hash space (a ring). A key is owned by the first node found clockwise from its position. When a node joins, it takes over only the keys between it and its predecessor; everyone else is untouched. When a node leaves, only its keys move to the next node.

Keys land on the ring, owned by the next node clockwise
Node A
→
Node B
→
Node C
↺
Adding Node D between B and C moves only B→C's keys onto D — roughly 1/N of the data, not all of it.

One refinement matters in practice: virtual nodes. Each physical machine is placed at many points on the ring, so load is even and removing a node spreads its keys across all survivors rather than dumping them on one neighbour.

The savings are dramatic and measurable. The naive scheme hash(key) mod N remaps almost every key the instant N changes; consistent hashing moves only the new node's share:

add a node to a 4-cluster (5,000 keys):   naive ~80% of keys move  ·  consistent hashing ~24% (≈ 1/N)

That gap is the difference between a resharding storm and a quiet rebalance. The runnable version below counts the moved keys both ways.

04

Hot partitions & the shard key

Even spread depends entirely on the shard key. A bad key creates a hot partition — one shard drowning in traffic while others idle. A celebrity user on a "shard by user_id" scheme is the canonical example.

Bad shard key

  • Low cardinality (e.g. country)
  • Skewed access (celebrities)
  • Sequential (timestamps)
  • → hotspots, uneven load

Good shard key

  • High cardinality
  • Even access distribution
  • Matches your query pattern
  • → balanced, scalable

When a single key is unavoidably hot, the fixes are: add a random suffix to fan it across sub-partitions, cache it separately, or give it dedicated capacity. The shard key is the one decision that most often makes or breaks the design.

05

Rebalancing without a resharding storm

Data grows, nodes join and fail, load drifts. Rebalancing moves data so no node drowns, while the system keeps serving.

Every workable scheme puts a layer of indirection between keys and machines: keys map to partitions, partitions map to nodes, and rebalancing moves whole partitions. A key never changes partition, so nothing is rehashed. The schemes differ in how partitions are created:

SchemeHow it worksUsed byWatch out for
Fixed partitionsCreate far more partitions than nodes up front; a new node takes whole partitions from the othersRedis Cluster (16,384 hash slots), Elasticsearch (primary shard count set when the index is created), RiakYou pick the count once: too few and each partition grows huge, too many and per-partition overhead dominates
Dynamic split and mergeA partition that passes a size threshold splits in two; one that shrinks merges with a neighborHBase regions, CockroachDB rangesAn empty database starts as one partition on one node; pre-split it if you know the key distribution
Per-node partitions (vnodes)Each node owns a set number of ranges on a hash ring; a joining node splits existing ranges and takes part of eachCassandra, the hash ring aboveRandom token choice balances badly with few tokens per node; Cassandra can allocate tokens to balance load instead

The risky part is deciding when to move. Rate-limit automatic rebalancing or keep a human approving it: an overloaded node answers slowly, a failure detector declares it dead, its partitions land on the others, they slow down too, and one hot node becomes a cascading outage. Every client or router must also agree which node owns a partition right now, so many systems keep that map in ZooKeeper or etcd.

Resharding a live system

Changing the shard key or shard count of an application-sharded database, say from 8 MySQL shards to 64, is a migration rather than a rebalance. The safe order is the same everywhere:

Online resharding: capture first, flip last
Capture
new writes
→
Backfill
old rows
→
Verify
both sides
→
Shift
reads
→
Flip
writes
→
Retire
old shards
Capture starts before the copy, so a write made during the backfill can't be missed.

Capture changes from the old shards' replication log (change data capture) rather than dual-writing from application code, which can fail halfway through a request. Backfill with upserts that never overwrite a newer version, so the copy and the live stream can't clobber each other. Verify with row counts, per-range checksums and shadow reads that compare old and new answers. Shift reads gradually, flip writes in one short pause per key range, and keep replicating back into the old shards until you're sure you won't roll back. Vitess runs this sequence for MySQL as its Reshard workflow. Done by hand, the classic bug is a write that lands on an old shard after the flip. Size the new layout with the sharding planner.

06

Secondary indexes: local or global

Partitioning by primary key makes lookups by that key cheap. Everything else (all orders with status = 'failed', all users in one city) needs a secondary index, and the index has to be partitioned too. There are two ways to do it:

Local index · per partition

  • Each partition indexes only its own rows
  • A write touches one partition
  • A read by the indexed field asks every partition
  • → scatter-gather, as slow as the slowest shard

Global index · partitioned by value

  • The index is sharded by the indexed value
  • A read asks one index partition
  • A write may update several partitions
  • → usually updated asynchronously, so it lags

MongoDB, Cassandra and Elasticsearch index locally: writes stay inside one partition and reads pay for it. A query on the indexed field fans out to every partition and waits for all of them, so the slowest one sets its latency. The arithmetic is unforgiving: if each partition answers within 10 ms 99% of the time, a query that needs all 20 finishes within 10 ms only 0.99^20 ≈ 82% of the time.

A global index flips the trade. Entries for status = 'failed' live on one index partition, so the read touches one place, but one row write may now change index partitions on several machines. Doing that synchronously takes a distributed transaction, so most systems update global indexes asynchronously. DynamoDB offers both: a local secondary index shares the table's partition key and supports strongly consistent reads, while a global secondary index has its own partition key and supports only eventually consistent reads.

Decision rule: if most queries include the partition key, index locally and keep fan-out queries rare and paginated. If a lookup by another field is on the hot path, use a global index and design for the lag, since a user who just wrote a row may not find it through the index yet.

07

Replication topologies

TopologyWrites go toTrade-off
Single-leaderOne leader, streamed to followersSimple, no write conflicts; leader is a bottleneck & SPOF
Multi-leaderSeveral leaders (e.g. per region)Low-latency local writes; must resolve write conflicts
LeaderlessAny replica; client uses quorumsHighly available; app handles read repair & conflicts
Single-leader replication
Client write
→
Leader
→
Follower 1
→
Follower 2
Synchronous = durable but slower (wait for followers). Asynchronous = fast but the un-replicated tail is lost if the leader dies.

Asynchronous replication introduces replication lag: a follower can be behind the leader, so a read from it may be stale. That silently breaks read-your-writes — a user edits their profile, then sees the old value. The fix is to route a user's reads to the leader (or a caught-up follower) for a short window after they write.

08

Failover, split brain and fencing

Single-leader replication is simple until the leader dies. Failover has three steps, and each one has a trap:

StepHowThe trap
DetectNo heartbeat within a timeoutToo short and a GC pause or load spike triggers a needless failover that adds load; too long and every second is write downtime
ElectReplicas vote, or a controller appoints the most up-to-date followerWith async replication the new leader may lack the old one's last writes, which are then usually discarded even though clients saw them acknowledged
RerouteUpdate the routing map, DNS or a virtual IPIf the old leader returns still believing it leads, two nodes accept writes

Discarded writes can do damage outside the database. Designing Data-Intensive Applications recounts a GitHub incident in which an out-of-date MySQL follower was promoted and reused auto-increment keys the old leader had already handed out; because Redis also stored those keys, some private data was shown to the wrong users.

Split brain is worse: the old leader was only partitioned or paused, and both sides keep accepting writes that will conflict. Two defenses work together. Elect leaders by majority, so only one side of a partition can elect anyone (the mechanics are on the consensus page). And fence the old leader: each new leader gets a higher epoch number, replicas and storage reject anything stamped with an older one, and the old leader steps down when it sees the higher number. Cruder fencing, known as STONITH ("shoot the other node in the head"), cuts the old leader's power or network through the infrastructure. A lease alone isn't enough, because a leader paused past its lease doesn't know it paused. The same idea protects distributed locks as fencing tokens.

09

Leaderless replication: quorums, hints and conflicts

Leaderless stores in the Dynamo family, such as Cassandra and Riak, let any replica take a write. With N replicas, a write waits for W acknowledgements and a read asks R replicas. If W + R > N, every read set overlaps every write set, so a read reaches at least one replica holding the latest acknowledged write. That is not linearizability; the quorum math and the five ways it fails in production are on the CAP page. This section covers what keeps replicas converging.

Sloppy quorums and hinted handoff. When a partition cuts a client off from some of a key's N home replicas, a strict quorum returns an error. A sloppy quorum instead accepts the write on whichever reachable nodes it can find, and each stand-in keeps a hint saying where the write belongs, delivering it when the home replica returns. Writes stay available, but reads of the home replicas can miss them until then, so W + R > N no longer guarantees overlap. Riak enables sloppy quorums by default. Cassandra stores hints but never counts them toward a quorum; only its ANY consistency level accepts a hint as the write.

Repair. Replicas still drift, for example when a node stays down longer than hints are kept. Read repair fixes the keys people read: the coordinator spots a stale version among its R replies and writes the newer one back. Anti-entropy fixes the rest: a background process compares replicas range by range using Merkle trees and streams only the ranges that differ.

Concurrent writes. Two clients write one key at nearly the same moment, replicas receive the writes in different orders, and something has to pick the final value:

StrategyHowCost
Last write winsKeep the value with the highest timestamp and drop the rest (Cassandra does this per column)Silently loses acknowledged writes; with clock skew, the later write can lose
Version vectorsOne counter per replica travels with each value, telling "newer than" apart from "concurrent"Concurrent writes survive as siblings that the application, or a merge function, combines
CRDTsData types whose merge is commutative and idempotent: counters, sets, mapsMerges are automatic, but only for the operations the type defines

Version vectors compare counter by counter. [A:2, B:1] is at or ahead of [A:1, B:1] on every counter, so it replaces it. [A:2, B:0] and [A:1, B:1] are each ahead on one, so neither happened first: keep both. A cart that merges siblings by set union never loses an added item, although a removed item can come back unless removals are recorded as tombstones. Try both in the vector clocks lab and the CRDT lab.

→ Key insight

Partitioning and replication are different axes: shard for scale, replicate for survival, and use quorums to choose how consistent each operation must be. When you need one agreed value rather than a tunable one — a single leader, a committed transaction — you've crossed into consensus. The consistency you're tuning here is defined in CAP & consistency models.

RUN IT YOURSELF

Count the keys that move when a node joins

Why bother with a hash ring instead of hash(key) % N? Because when N changes, the modulo scheme reshuffles almost everything. This assigns 5,000 keys both ways, adds a fifth node to a four-node cluster, and counts the moves: the naive scheme relocates ~80% of the keys — a resharding storm — while consistent hashing moves only the new node's ~24% share. It also shows that removing a node leaves every other node's keys untouched. Change the key count, nodes, or virtual-node factor.

CPython · WebAssembly
Frequently asked

Quick answers

Partitioning vs replication?

Partitioning splits a dataset across machines to scale capacity. Replication copies the same data onto several machines for fault tolerance and read scaling. Real systems shard for scale and replicate each shard for durability.

What is consistent hashing?

Keys and nodes map onto a ring; a key is owned by the next node clockwise. Adding/removing a node moves only a small fraction of keys instead of remapping everything. Virtual nodes even out the load.

What is a hot partition?

A shard receiving disproportionate traffic due to a skewed shard key (e.g. a celebrity). Fixes: a higher-cardinality key, a random suffix to spread the hot key, or separate caching.

What is replication lag?

The delay between a write committing on the leader and appearing on a follower. With async replication, reads from a lagging follower can be stale, breaking read-your-writes unless you route reads to a caught-up replica.

How do you reshard a live database?

Capture first, flip last. Stream every new write to the new layout, usually from the old shards' replication log, and backfill existing rows with upserts that never overwrite a newer version. Verify with counts, checksums and shadow reads, shift reads gradually, then flip writes in one short pause per key range. Keep the old shards until you're sure you won't roll back. Vitess automates this for MySQL.

What is split brain?

Split brain is two nodes both acting as leader and accepting writes, usually after a failover where the old leader was only slow or partitioned. The sides diverge, and one side's writes are lost when they reconcile. Prevent it with majority-based elections plus fencing: each new leader gets a higher epoch number, and storage rejects anything stamped with an older one.

▶  Watch it explained

Partitioning vs replication: how databases scale

Finished this one? 0 / 208 Handbooks done
NextConcurrency, Locks & Isolation Levels Next on the “Distributed systems, from first principles” ladder · rung 4 of 7

Explore the topic

See this alongside everything else on the same subject — handbooks, system designs, challenges and tools, in one place.

More Handbooks