DEV Community

Cover image for Day 1/30 AWS System Design Patterns
Joud Awad
Joud Awad

Posted on

Day 1/30 AWS System Design Patterns

A gaming platform stores player leaderboard data in DynamoDB (managed NoSQL database, distributes data across internal partitions — each partition has its own throughput ceiling). The table uses game_id as the partition key and player_id as the sort key. The platform runs 15 active games. One game — game_id = "battle-royale" — accounts for 78% of all read traffic. It is the flagship title that launched 6 months ago and still dominates the player base.

The table is provisioned at 10,000 RCUs. CloudWatch (AWS monitoring service, reports consumed RCUs as a table-level aggregate across all partitions) shows average consumed RCUs at 4,300 — well under the provisioned capacity. But ThrottlingException errors are spiking on leaderboard reads for battle-royale during peak hours, and P99 read latency has climbed to 820 ms.

The on-call engineer opens a ticket to increase provisioned capacity to 20,000 RCUs. The support team approves the change. It goes live.

The throttling does not stop. CloudWatch still shows consumed RCUs well below the new limit.

You have 10,000 RCUs of headroom sitting idle and a table that is still throttling. What is actually happening?

A) Yes — doubling provisioned RCUs gives the table enough capacity headroom to absorb the battle-royale traffic spike

B) No — the table needs to migrate to DynamoDB on-demand capacity mode (no pre-provisioned capacity, adapts per-partition throughput dynamically), which removes per-partition limits entirely

C) No — battle-royale is a hot partition; per-partition throughput limits apply regardless of total table RCU provisioning; traffic concentrated on one partition key hits that partition's ceiling even when the table has headroom

D) No — the issue is a missing GSI (Global Secondary Index — a secondary index for alternate access patterns) on game_id causing full partition scans on leaderboard reads

Top comments (4)

Collapse
 
thejoud1997 profile image
Joud Awad

The answer is C.

DynamoDB (managed NoSQL database) does not store all your data in one place. It distributes data across internal partitions based on the partition key hash. Each partition has its own throughput ceiling: 3,000 RCUs and 1,000 WCUs. These limits are per-partition — not per-table.

When battle-royale receives 78% of all read traffic, that traffic concentrates on the partition(s) storing game_id = "battle-royale" data. When that partition's 3,000 RCU ceiling is hit, DynamoDB throttles reads on it. The other partitions — the ones holding the other 14 games — still have capacity available. CloudWatch's (AWS monitoring service) consumed RCU metric is a table-level aggregate across all partitions. It shows 4,300 consumed out of 10,000 because the 14 other games are barely loaded. The hot partition is buried in that average.

Doubling total provisioned RCUs to 20,000 does not move the ceiling on the hot partition. The same battle-royale requests still hit the same partition. The per-partition ceiling still applies.

Two correct fixes:

Option 1 — Migrate to DynamoDB on-demand mode (no pre-provisioned capacity, adapts per-partition throughput dynamically). On-demand adapts throughput per partition dynamically and handles burst traffic better than provisioned mode. It does not remove per-partition limits entirely, but it responds to sudden spikes faster than provisioned auto-scaling.

Option 2 — Add a random suffix to the partition key: battle-royale#0 through battle-royale#9. Scatter leaderboard writes across 10 logical partitions. Use a scatter-gather read (parallel queries on all 10 suffixes, merge and rank in application code) to reconstruct the leaderboard. More complex operationally, but it eliminates the hot partition by design.

Collapse
 
thejoud1997 profile image
Joud Awad

A — The logic feels sound: more provisioned RCUs means more headroom. But CloudWatch's (AWS monitoring service) consumed RCU metric is a table-level aggregate — it sums consumption across all partitions. It shows 4,300 consumed out of 10,000 because the 14 other games are barely loaded. The single partition holding battle-royale data has already hit its 3,000 RCU per-partition ceiling. Doubling total RCUs to 20,000 does not redistribute that partition's load. The same requests still hit the same partition.

Collapse
 
thejoud1997 profile image
Joud Awad

D — game_id is already the partition key. DynamoDB uses the partition key to route every read directly to the correct partition — no scan required. Adding a GSI (Global Secondary Index) on game_id would point back to the same partition key the table already uses, achieving nothing except additional write amplification.

Collapse
 
thejoud1997 profile image
Joud Awad

B — On-demand mode (no pre-provisioned capacity, adapts throughput dynamically) makes per-partition limits more adaptive, not absent. A sustained, extreme hot key can still throttle under on-demand. On-demand reduces the problem; it does not remove the constraint.