Rohit Kumar
← System design

Stage 03 · Scaling data

Hotspots & Dynamic Resharding

4 min read Core Rohit Kumar

You picked a shard key that preserves locality — range, geography, tenant — because your queries needed it (Sharding and Partitioning). Now some of your shards are on fire and others are idle.

This is not a mistake you made. It’s the bill for locality, and it always arrives.


Why it’s unavoidable

Hash sharding distributes evenly because hash functions destroy any structure in your keys. Any key that keeps structure — so that related things sit together — also keeps whatever skew the real world has.

And the real world is extremely skewed:

Popularity. One celebrity account, one viral product, one enterprise customer who is 40% of your traffic.

Density. A city is 78 km² and does more business than a 90,000 km² rural region.

Sequential keys. Timestamps and auto-increment ids mean every new write goes to the last range. The other shards are archives.

Time. This one is the real problem. A stadium is empty at 3pm and saturated at 10pm. Load doesn’t just sit unevenly — it moves. Any static partitioning is wrong within hours of being right.


The fixes, cheapest first

Vary the granularity

Don’t use one partition size everywhere. Let a shard in a sparse region own a large slice of keyspace, and a shard in a dense region own a tiny one. Shards end up equal in load instead of equal in keyspace, which is what you wanted in the first place.

This is exactly what a quadtree does with depth — subdivide more finely where the data is dense — lifted from the index layer up to the shard layer.

Split and merge at runtime

Keep the live partition set in a small directory table. Splitting is then one transaction:

BEGIN;
  DELETE FROM partitions WHERE id = :hot;
  INSERT INTO partitions SELECT unnest(children_of(:hot));
COMMIT;

The children hash to different machines, so load spreads immediately. Merging is the same statement reversed. This is the mechanism behind HBase region splits and DynamoDB partition splits.

Automate it

A control loop watching per-shard QPS or CPU, splitting above a threshold and merging below one — with hysteresis, so a shard hovering at the boundary doesn’t split and merge every five minutes.

Manual splitting is a perfectly honest starting answer. Automation is the obvious follow-up, and volunteering it is a good look.

Sub-shard one hot key

When a single key exceeds a machine, append a suffix: key#0 through key#n. Reads have to fan out across those sub-shards, which is fine because the fan-out is small and bounded.

Use this last. It breaks locality within that key, which is the thing you were protecting.


The part people forget: moving the data

Splitting the map is a one-line transaction. Moving the data is the hard part — unless you’ve arranged not to have to.

Your data How you migrate
Disposable, TTL’d, regenerated by its source Don’t. Old rows expire, new writes land in the new place. The shard moves by attrition.
Durable and small Copy it, then atomically flip the directory
Durable and large Dual-write, backfill, verify, cut over — a project, not an operation

That first row is worth designing for in advance. If you know a dataset will be resharded often, make its rows expire. You turn the hardest part of resharding into a no-op before you’ve written a line of migration code. → TTL as a Coordination Primitive


Staleness during a split

Your servers cache the directory, because reading it on every request would be absurd. So for one refresh interval after a split, some of them route to the old partition.

Whether that matters depends entirely on which code path is asking. The useful question is not “is this consistent?” but “what breaks if this particular caller reads a stale map?”

Path What a misroute costs So it reads
High-volume disposable writes one row that expires anyway the cached copy
Low-volume correctness-critical reads a wrong or empty answer the leader, or a quorum

The asymmetry is usually what makes the expensive answer affordable: adding a database round-trip to a path that runs 350 times a second costs nothing, while adding it to one that runs a million times a second would be fatal. → Replication and Read Consistency


In an interview

If you choose a locality-preserving key — range, geo, tenant — raise the hotspot yourself before the interviewer does. It shows you understand the cost of the key you picked.

The strong fix to describe is a small directory table of live partitions, so splitting a hot one is a single transaction. Then say how the data moves — and if it’s regenerated or TTL’d data, point out that it doesn’t need to.

The trap: proposing to fix a hotspot by changing the shard key. That trades a solvable balance problem for losing the locality you sharded for.

Seen in

  • 09 - H3 and Geo Sharding — a fixed cell size puts an entire city on one shard
  • 10 - Dynamic Resharding and the Shard Index — splitting as one transaction, with TTL absorbing the migration

Sharding and Partitioning · TTL as a Coordination Primitive · Caching Strategies · Replication and Read Consistency

© Rohit Kumar 2026