The problem: one link, one shard, all the traffic
Snip's codes are spread across 256 logical shards by hash, so each shard gets about 1/256 of the links. Then a
celebrity posts a Snip link to forty million followers, and for ten minutes that one code gets 50,000 redirects a
second. Hashing spread the keys evenly; it can't split one key. Every request for x7Kp goes to the same cache node
and, on a miss, the same shard, while the other 255 sit idle.
That's a hot partition, and its cause here is a hot key (sometimes called a celebrity key). Hot partitions come from three directions:
| Cause | Snip example | Elsewhere |
|---|---|---|
| One very popular key | A viral link | A celebrity's profile, a flash-sale product |
| One very big tenant | A customer with 40 million links, sharded by owner | A huge workspace or channel |
| A key that follows time | Shards by creation date: today's shard takes every write | Logs or events keyed by timestamp |
The third is a choice of key, fixed by choosing a better one (05.01 Sharding: range, hash, directory). The first two exist in the data, and you need ways to cope with them.
Hot reads: copy the hot key
Reads are the easier half, because copies of a value can be served from many places.
- Cache it closer. Snip's Redis tier already absorbs most redirects. For the very hottest codes, each app server also keeps a tiny in-process cache of a few thousand entries for a few seconds. 50,000 requests a second spread over 40 app servers become 40 requests a second to Redis.
- Spread one key over several cache nodes. Store copies under
x7Kp#1…x7Kp#8, which hash to different nodes, and have each reader pick one at random. Writes must update every copy, so do it only for keys you've detected as hot. - Coalesce. When a hot key's cache entry expires, a thousand requests can miss at once and all query the database. Let one request fetch it while the others wait for its answer (06.05 Stampedes and request coalescing).
Hot writes: split the key
Writes are harder, because they must end up in one correct value. Every click on the viral link increments
total_clicks on one row (04.12 Denormalisation), and one row can only take so many updates a second before the
transactions queue on its lock.
- Batch. Snip already does this: click events go to a queue, and workers turn thousands of clicks into one
UPDATEevery few seconds (02.06 Async workers and queues). This is the first fix to reach for. - Split the counter. Keep N rows per hot counter,
x7Kp:0…x7Kp:15. Each write picks one at random; a read sums all N. Writes spread over N rows (or N shards, if the sub-keys hash differently); reads get a little more expensive.
-- a split counter: 16 rows for one link; the app picks a random slot, 0 to 15
UPDATE link_clicks SET clicks = clicks + 1
WHERE code = 'x7Kp' AND slot = $1;
SELECT sum(clicks) FROM link_clicks WHERE code = 'x7Kp';Big tenants: give them their own home
When the hot partition is a tenant (all of one customer's data on one shard), the options are to move that tenant to a dedicated shard (easy with a directory, 05.01 Sharding: range, hash, directory), or to shard their data by a finer key, such as the channel or the link instead of the customer. That's what Slack did, below.
Find them before they find you
Hot keys are invisible in averages: 256 shards at 5% CPU and one at 100% averages to fine. Watch per-shard load, not just the total, and sample the busiest keys (a periodic "top 100 codes this minute" from the request log) so you can spot a viral link in seconds and turn on the hot-key handling for it.
Seen in production
Discord
2023Message storage
Discord partitions messages by channel and a time bucket. A server with hundreds of thousands of people sends far more messages than a group of friends, and heavy reads on one channel created hot partitions whose latency spread across the cluster, because quorum reads involved the overloaded nodes. Discord added data services between its API and the database that coalesce identical requests, querying once for many readers, and routed requests by channel ID with consistent hashing (05.03 Consistent hashing) so all requests for a channel reach the same instance. After moving to ScyllaDB in 2022, p99 latency for reading history fell from 40–125 ms to about 15 ms.
What we foundOne busy channel could slow the whole database cluster; routing requests for a channel to one service and coalescing them was part of the fix.
How Discord stores trillions of messagesdiscord.com ↗Slack
2020Message storage on Vitess
With every team's data on one shard, Slack's largest enterprise customers created hot spots that couldn't be spread across the fleet, so most shards were over-provisioned to leave room. Moving messages to be sharded by channel ID instead of workspace spread a big team's traffic across many shards.
What we foundWhen the hot partition is a tenant, shard by something finer than the tenant.
Scaling Datastores at Slack with Vitessslack.engineering ↗A viral link's cache entry is on one Redis node, which is now at 100% CPU. The other cache nodes are idle. What helps most, fastest?
Recap
Interview questions
One celebrity's posts get millions of reads and likes a minute. How do you stop their data overloading one shard?