System Design (High-Level Design) Module 9 – Partitioning and sharding
Hot partitions, celebrity keys and key salting
Why one viral post or famous account can overload a partition that no hash function can fix, and how salted keys, replicas and batching spread the load.
What you will learn
- Explain why no hash function or partition count spreads the requests for a single key
- Detect hot partitions and hot keys from per-partition and per-key measurements
- Find the top keys of a stream in fixed memory with Space-Saving, and state how far its counts can be off
- Size and spread a write-hot key over salted keys without double counting retries
- Choose read or write fixes for celebrity accounts in feeds, counters and chats
Before you start
What you’ll learn · Before you start
What you will learn
- Explain why no hash function or partition count spreads the requests for a single key
- Detect hot partitions and hot keys from per-partition and per-key measurements
- Find the top keys of a stream in fixed memory with Space-Saving, and state how far its counts can be off
- Size and spread a write-hot key over salted keys without double counting retries
- Choose read or write fixes for celebrity accounts in feeds, counters and chats
Before you start
On this page
One viral post draws 8 % of all likes. At a peak of 20,000 likes a second, the partition that owns its counter takes 2,202 likes a second against a limit of 1,000, while the average partition takes 625. Partitions like that one are hot partitions: they receive far more requests than the rest of a cluster, so they reach their limits while the other partitions are half idle.
They usually come from celebrity keys: single keys, such as one viral post or one famous account, that draw a large share of all reads or writes. Hashing spreads keys evenly, but every request for one key still goes to the one partition that owns it, so no better hash can help. Key salting splits a hot key into several keys by adding a suffix, which spreads its writes over several partitions. The fix depends on whether the heat comes from reads or from writes.
A hash spreads keys, not the requests for one key
Hashing spreads keys, not requests. If one post draws 8 % of all likes, 8 % of the like writes reach the partition that owns its counter, however many partitions there are. Each partition has a ceiling: DynamoDB designs every partition for at most 3,000 read units and 1,000 write units a second, and its A DynamoDB feature that lets a busy partition use throughput that quieter partitions of the table leave unused, and that can move frequently accessed items onto partitions of their own. can move a hot item onto a partition of its own, but no further than that ceiling. This run spreads a sample of likes over 32 partitions by hash and scales it to a peak of 20,000 likes a second:
import random
import zlib
POSTS = 100_000
LIKES = 200_000 # a sample of the day's likes
PARTITIONS = 32
PEAK = 20_000 # likes a second at the peak, spread like the sample
LIMIT = 1_000 # writes a second one partition takes (the per-partition limit DynamoDB documents)
rng = random.Random(7) # fixed seed: the same likes on every run
# A few posts draw most likes: the post of rank r is liked in proportion to 1 / r.
weights = [1 / (rank + 1) for rank in range(POSTS)]
likes = rng.choices(range(POSTS), weights=weights, k=LIKES)
def partition(key):
"""Hash partitioning: CRC-32 of the key, modulo the partition count."""
return zlib.crc32(key.encode()) % PARTITIONS
load = [0] * PARTITIONS
for post in likes:
load[partition(f"post:{post}")] += 1
top_post = sum(1 for post in likes if post == 0)
busiest = max(range(PARTITIONS), key=lambda p: load[p])
def row(name, count):
share = 100 * count / LIKES
rate = PEAK * count / LIKES
mark = " !" if rate > LIMIT else ""
print(f"{name:<15}{share:>5.1f} %{rate:>11,.0f}{mark}")
print(f"{LIKES:,} likes on {POSTS:,} posts, {PARTITIONS} partitions,")
print(f"{PEAK:,} likes a second at the peak\n")
print(f"{'':<15}{'share':>7}{'a second':>11}")
row("average", LIKES / PARTITIONS)
row(f"busiest (P{busiest})", load[busiest])
row("quietest", min(load))
row(f"top post (P{partition('post:0')})", top_post)
print(f"! over the limit of {LIMIT:,} a second") Output
200,000 likes on 100,000 posts, 32 partitions,
20,000 likes a second at the peak
share a second
average 3.1 % 625
busiest (P21) 11.0 % 2,202 !
quietest 1.9 % 387
top post (P21) 8.3 % 1,651 !
! over the limit of 1,000 a second
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 where_likes_land.py
Runs on this device, in your browser. The first run downloads Python (about 13.5 MB), which is kept for the next runs.
Your run, in this browser
The figure follows the top post’s likes to its partition, then through seven salted keys:
One hot key, before and after key salting
Text description of the diagram
The diagram has two panels, one above the other.
- One key: the likes of post:0 arrive at 1,651 a second. The hash of post:0 modulo 32 is 21, so all of them go to partition P21, which takes 2,202 likes a second in all against a limit of 1,000. The average partition takes 625.
- Key salting: the same 1,651 likes a second go to seven keys, post:0#0 to post:0#6, chosen by a suffix calculated from each like's id, so each key takes about 236 a second. Each key is hashed on its own, so the seven keys land on up to seven different partitions.
An arrow from the first panel to the second is labelled "split the hot key".
The average partition takes 625 likes a second, well inside a limit of 1,000, so hot partitions do not show in averages. The busiest partition takes 2,202, more than twice the limit, because the top post alone sends it 1,651. That post would be throttled even on a partition of its own, so its counter has to become at least 2 keys (1,651 ÷ 1,000, rounded up), or 7 keys if each key may use only a quarter of a partition.
Hot keys get different fixes for reads and for writes:
- Key salting adds a suffix such as
#0to#6, so one hot key becomes several keys on different partitions. - Replicas and small local caches serve a read-hot key from many copies instead of one partition. Replicas fit data that is read far more often than it is written.
- Request coalescing turns many concurrent reads of one key into a single read of the store.
- Batching adds increments up in memory and writes one sum per interval. Batching fits a count that may lag by a moment, such as the likes under a post.
- Fan-out on read handles celebrity keys in feeds: a famous account’s post is stored once and fetched by each follower, instead of being copied into millions of feeds.
Checkpoint
Check: Why one key overloads one partition
Hot keys show in the busiest partition, not in averages Pro: opens with a Pro, Premium or Ultimate pass
In this part:
- Space-Saving: a top-k list in fixed memory, and its guarantee
- Salting a write-hot key, measured
- How many keys a hot key needs, worked
- A suffix from the request id survives retries
- Celebrity accounts in feeds, counters and chats
- What salting breaks, and how to keep it safe
- Practise: split a hot counter
- Split a hot counter into enough keys
- Interview questions
- What is the difference between a hot key and a hot partition?
- A celebrity's post receives 40,000 likes a second at its peak, and the partition holding its like counter throttles at 1,000 writes a second. Design the counter so that writes succeed, retries are not counted twice and the number shown stays close to the truth.
- Your news feed copies every post into each follower's feed when it is posted. An account with 30 million followers posts, and feed writes back up for minutes. What happens on the write path, and how would you change the design for such accounts?
- Throttling alarms fire on one partition of a sharded table, but the table's average utilisation is 20 %. How do you find the key responsible, what do you do in the next ten minutes, and what do you change in the next ten days?
- Key takeaways
3 diagrams · 5 runnable examples (JavaScript, Python) · 1 exercise with sample tests · 3 interview answers · about 52 minutes
Opening this part with your pass…
This part opens with any MySmartCoPilot pass.
- Pro pass · 1 week₹79$2.99
- 30 days₹179$5.99
- 1 year₹1,699$45
One payment; a pass never renews. The price shown is the total you pay: no fees or charges are added at checkout.
Passes can’t be bought right now, so this part can’t be opened at the moment. The free part above stands on its own. If you have a pass, enter your licence key.
It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).
References
- Best practices for designing and using partition keys effectively (Amazon DynamoDB developer guide) (Amazon Web Services)
- Using write sharding to distribute workloads evenly (Amazon DynamoDB developer guide) (Amazon Web Services)
- DynamoDB burst and adaptive capacity (Amazon DynamoDB developer guide) (Amazon Web Services)
- CloudWatch contributor insights for DynamoDB: how it works (Amazon Web Services)
- Redis CLI (Redis documentation) (Redis)
- Efficient Computation of Frequent and Top-k Elements in Data Streams (UC Santa Barbara (Metwally, Agrawal and El Abbadi; report of the ICDT paper))
- Scaling Memcache at Facebook (USENIX NSDI (Nishtala and others))
- Fairness in multi-tenant systems (Amazon Builders' Library) (Amazon Web Services)
Related tools
Report a problem with this lesson
Kept only in this browser. Your Learn progress