Free — no signup required

Hot Partitions & Write Sharding

2 min read

Why Hot Partitions Happen

A hot partition occurs when a disproportionate amount of traffic — reads or writes — targets a single Partition Key. DynamoDB's per-partition limits are hard ceilings: 3,000 RCUs and 1,000 WCUs per second per partition. Exceed them and DynamoDB throttles requests to that partition, returning ProvisionedThroughputExceededException errors.

Common causes:
- Sequential IDs: If your PK is an auto-incrementing integer, all new writes go to the "highest" partition, creating a perpetual hot spot.
- Popular entities: A celebrity's profile page receiving millions of reads per minute.
- Aggregation keys: Storing all votes, events, or logs under a single key (the "Recent Posts" anti-pattern).
- Time-series data: Using PK = "LOGS#TODAY" concentrates all writes for the day on one partition.

DynamoDB Adaptive Capacity (enabled by default) can temporarily burst a partition beyond its baseline allocation by borrowing unused capacity from other partitions. This helps with occasional spikes but cannot solve sustained hot partitions. Do not design around adaptive capacity — it is a safety net, not a solution.

Solution: Write Sharding

Write sharding distributes traffic across multiple logical partitions that represent the same conceptual entity.

The pattern:

# Instead of writing to:
PK: "LEADERBOARD#GLOBAL"

# Write to one of N shards:
PK: "LEADERBOARD#GLOBAL#<random(1-10)>"

Writing (application code):

import random

shard_count = 10
shard_id = random.randint(1, shard_count)
partition_key = f"LEADERBOARD#GLOBAL#{shard_id}"

table.put_item(Item={
    "PK": partition_key,
    "SK": f"SCORE#{user_id}",
    "Score": score,
    "UserId": user_id
})

Reading (scatter-gather):

import concurrent.futures

def query_shard(shard_id):
    return table.query(
        KeyConditionExpression=Key("PK").eq(f"LEADERBOARD#GLOBAL#{shard_id}"),
        ScanIndexForward=False,  # descending order
        Limit=10
    )["Items"]

with concurrent.futures.ThreadPoolExecutor() as executor:
    results = list(executor.map(query_shard, range(1, 11)))

# Merge and sort all shard results in memory
all_scores = [item for shard in results for item in shard]
top_10 = sorted(all_scores, key=lambda x: x["Score"], reverse=True)[:10]

The trade-off: Write sharding makes writes cheap and scalable but makes reads more expensive — you must query all shards in parallel and aggregate in memory. Choose the shard count based on your write throughput requirement: if you need 5,000 WCUs on one logical key, use at least 5 shards (each handling ~1,000 WCUs).

Choosing shard count: Too few shards and you still get throttling. Too many shards and your scatter-gather reads become expensive. A common starting point is ceil(expected_peak_WCUs / 800) — using 800 instead of 1,000 as a safety margin.

This is one of 18 chapters

Get every chapter — Kubernetes, Terraform, SRE, distributed systems, and more — with fast daily review built in.

See pricing