Tier 1 · Data

Partitioning & Consistent Hashing

Splitting data across machines without reshuffling the world every time you add one

⏱ 20 min datascalingsharding 📕 Ch 5 — Consistent Hashing

Partitioning vs replication

Keep these separate in your head, because interviewers will deliberately blur them:

Real systems do both: each partition is replicated. A "shard" in production usually means one partition plus its replica set. When you say "we shard by user ID," you're describing partitioning; the availability story is a separate answer.

Range partitioning

Assign contiguous key ranges to partitions: A–F here, G–M there.

Good: range scans stay on one node. "All events for user X between two dates" is a single-partition query. This is why HBase and Bigtable use it.

Bad: sequential keys create hot spots. Partition by timestamp and every write today lands on the same node while the rest of the cluster idles. The usual patch is prefixing the key with something high-cardinality — <user_id>:<timestamp> — which restores write spread but means a global time-range scan now hits every partition.

Hash partitioning, and why mod N betrays you

Hash the key, and the hash decides the partition. Uniform distribution, no hot spots from sequential keys, and you lose efficient range scans.

The naive form is hash(key) % N. It works beautifully until N changes. The fraction of keys that keep their home when going from N to N+1 servers is only about 1/(N+1):

Change Keys that move
4 → 5 servers ~80%
10 → 11 servers ~91%
100 → 101 servers ~99%

It gets worse as you grow. That's the opposite of what a scaling strategy should do.

Consistent hashing

Map both servers and keys onto the same circular hash space. A key belongs to the first server it meets walking clockwise. Now adding a server only steals keys from its immediate neighbour: about K/N keys move, not all of them.

Add and remove nodes below and watch the "keys moved" counter. Compare it against the ideal reshuffle — the fraction a perfectly balanced system would have to move.

Loading simulator…
Each dot is a key, coloured by the server that owns it. Add a node and watch how few change colour.

Two things to notice while you play:

With 1 virtual node per server, the balance is terrible. Random points on a circle clump. You'll routinely see one server holding 2–3× its fair share — the "worst node vs fair share" stat. Hashing gave you uniformity of keys, but the server positions are just as random, and few of them.

Drag the virtual-node slider up and the imbalance collapses. Give each server 100–200 positions on the ring instead of 1 and the law of large numbers takes over; the worst node converges toward 1.05× fair share. This is the entire reason virtual nodes exist.

They also make failure recovery smoother. When a physical node with 200 vnodes dies, its load scatters across all remaining servers rather than dumping entirely onto one unlucky clockwise neighbour — which is how a single failure becomes a cascading one.

What consistent hashing does not fix

Hot keys. One key lives on one partition, full stop. If a celebrity account is read a million times a second, no hashing scheme spreads that — the key is the unit of placement. Fixes are application-level:

This distinction — "consistent hashing fixes rebalancing, not skew" — is a reliable way to show depth in an interview.

Rebalancing strategies

Three approaches you should be able to name:

Strategy How it works Used by
Fixed partition count Create ~1000 partitions up front, spread them over N nodes; adding a node moves whole partitions Elasticsearch, Riak
Dynamic partitioning Split a partition when it exceeds a size threshold, merge when it shrinks HBase, MongoDB
Proportional to nodes Fixed partitions per node; adding a node splits some randomly chosen ones Cassandra

The fixed-count approach is the easiest to reason about and the most common choice for a design interview — but say the number out loud, because picking it is the whole trick. Too few and you can never grow past that many nodes; too many and you pay per-partition overhead on every one. "1024 partitions, so we can scale to 1024 nodes and today each node holds 64" is a concrete, defensible answer.

Request routing

Once data moves, who knows where it is? Three designs:

  1. Any node can forward — client hits any node; it proxies to the right one. Simple clients, extra hop. (Cassandra, with gossip spreading membership.)
  2. A routing tier — a proxy that knows the partition map. (Redis Cluster proxies, Vitess.)
  3. Partition-aware clients — client holds the map and connects directly. Fastest, but every client language needs the logic and stale maps cause redirects.

All three need a source of truth for membership: ZooKeeper/etcd (authoritative, one more system to operate) or gossip (no dependency, eventually consistent membership).

Secondary indexes — the part most candidates miss

Partitioning by primary key is easy. Querying by something else is where it gets interesting:

There's no free option. Elasticsearch uses local indexes and eats scatter-gather; DynamoDB GSIs are global and eventually consistent. Naming this tradeoff unprompted is a strong signal.

Worked numbers: resharding without downtime

100 M users, 2 KB profile each = 200 GB. Target 500 GB usable per node with headroom.

The migration itself: double-write to old and new placement, backfill in the background, verify with checksums, flip reads, then decommission. The double-write window is the part worth mentioning — it's where the bugs live.

What to take away

Check yourself

  1. You move a cache from 4 servers to 5 using hash(key) % N. Roughly what fraction of keys change owner?

  2. In consistent hashing, what problem do virtual nodes primarily solve?

  3. A celebrity account is read a million times a second. Does consistent hashing help?

  4. You shard by user_id but need to query by email. What does a local (document-partitioned) secondary index cost you?