SYSTEM DESIGN · TECHNOLOGIES · 5 OF 12

DynamoDB: Managed Key-Value at Any Scale, If You Know Your Queries

How Amazon DynamoDB stores items by partition key and sort key, how requests are routed to partitions with three replicas, capacity units and the per-partition limits behind hot keys, strong versus eventual reads, global and local secondary indexes, single-table design, transactions, streams, global tables, where it hurts and how to justify it in an interview.

  1. 1
  2. 2
  3. 3
  4. 4
  5. 5
  6. 6
  7. 7
  8. 8
  9. 9
  10. 10
  11. 11
  12. 12
1 / 16

Press play to hear the narration, or step through at your own pace.

Notes

What it is

In one line: a serverless key-value and document database. There are no instances to size, no patching and no shards to manage. You call an HTTPS API with operations like get item, put item and query, and you pay either per request, called on-demand, or for capacity you provision in advance. It descends from the ideas in Amazon's original Dynamo paper, though the internals differ.

DynamoDB in one line
  • fully managed, serverless
  • key-value + document DB
  • no instances, no patching,
  • no sharding to do yourself
  • pay per request (on-demand)
  • or per provisioned capacity
  • API: GetItem, PutItem,
  • Query, Scan, TransactWrite

Data model

Items live in tables, and every item has a partition key and an optional sort key. The partition key, say user id, decides where the item is stored. Items with the same partition key are kept together, ordered by the sort key, say order time. So you can fetch one order exactly, or query all of a user's orders in a time range, newest first. Everything else about the item is free-form attributes, up to four hundred kilobytes.

Keys and items
table: Orders
  PK  user_id     (partition key)
  SK  order_ts    (sort key)
  attrs: total, status, items[]

GetItem  PK=u42, SK=2026-10-24T09:00
Query    PK=u42, SK begins_with 2026-10
         ScanIndexForward=false

item max size: 400 KB

Partitions

Under the hood, a request router hashes the partition key to find the partition. Each partition holds around ten gigabytes and has three replicas in different availability zones, with one leader chosen by Paxos. A write is acknowledged once the leader and one more replica have it. As data or traffic grows, partitions split automatically. You never see any of this, but it explains the limits that follow.

How it is stored
  • hash(partition key) → partition
  • each partition: ~10 GB,
  • replicated 3 times across
  • availability zones
  • one replica leads (Paxos)
  • writes: leader + 1 more ack
  • partitions split as data or
  • traffic grows, automatically

Capacity

Throughput is metered. One write capacity unit is one write a second of up to one kilobyte. One read capacity unit is one strongly consistent read a second of up to four kilobytes, or two eventually consistent ones. The important limit: a single partition serves at most three thousand read units and one thousand write units a second, however large the table. On-demand mode needs no planning; provisioned with autoscaling is cheaper for steady traffic.

Capacity units
  • 1 WCU = one write/s, ≤ 1 KB
  • 1 RCU = one strong read/s,
  • ≤ 4 KB (or 2 eventual)
  • per partition, at most:
  • 3,000 RCU and 1,000 WCU
  • on-demand: no planning,
  • higher price per request
  • provisioned + autoscaling:
  • cheaper for steady traffic

Hot keys

Which makes hot keys the main design risk. If one partition key, say a viral product or a single global counter, gets five thousand writes a second, it is throttled while the rest of the table sits idle. Fixes: choose keys with many values, spread one hot key across suffixes like product id hash zero to nine and merge them on read, and cache hot reads with DAX. Adaptive capacity helps uneven traffic but cannot exceed the per-partition ceiling.

Hot partition fixes
  • problem: 1 key gets most
  • traffic → throttled requests
  • • high-cardinality keys
  • (user_id, not country)
  • • write sharding: key#0..key#9,
  • read all 10 and merge
  • • cache hot reads (DAX)
  • • adaptive capacity helps
  • but cannot beat the limit

Consistency

Reads are eventually consistent by default. They may miss a write from the last moment, and they cost half as much. Ask for a consistent read and the leader replica answers, so you see every acknowledged write, but only on the base table and local indexes, never on a global secondary index. For multi-item atomic changes there are transactions of up to a hundred items, which cost about twice as much as normal writes.

Read consistency
  • default: eventually consistent
  • may miss a write from the
  • last second; half the cost
  • ConsistentRead=true: strong,
  • served by the leader
  • (table + LSIs, not GSIs)
  • transactions: up to 100
  • items, serializable, 2x cost

Indexes

To query by something other than the main key, add an index. A global secondary index is effectively another table, keyed by whatever you choose, such as status and created at, kept up to date asynchronously. It has its own capacity, so a throttled index can slow writes to the base table. A local secondary index keeps the same partition key with a different sort key and supports strong reads, but must be created with the table and caps each key's data at ten gigabytes.

Secondary indexes
  • GSI (global): new partition
  • + sort key, e.g. status +
  • created_at; own capacity;
  • eventually consistent;
  • add any time; 20 per table
  • LSI (local): same partition
  • key, new sort key; strong
  • reads OK; must be created
  • with the table; 10 GB per
  • partition key value

Single-table design

Since there are no joins, experienced teams often use single-table design. Different entity types share one table with generic keys. The user profile and that user's orders all have partition key user forty-two, with sort keys like profile and order plus a date. One query returns the profile and every order in one call. Overloaded indexes cover the other access patterns. It is efficient, but hard to change later.

One table, many entities
PK            SK                attrs
USER#42       PROFILE           name..
USER#42       ORDER#2026-10-01  total..
USER#42       ORDER#2026-10-24  total..
ORDER#981     ITEM#1            sku..

Query PK=USER#42
  → profile + all orders, one call
GSI1: PK=STATUS#open SK=created

Streams + global

Around the table there are useful pieces. DynamoDB Streams emits every change in order per item, kept for twenty-four hours, so a Lambda can update a search index or invalidate a cache. Time to live deletes expired items for free. Global tables replicate a table across regions, all accepting writes, usually about a second apart, with last writer wins on conflicts. A newer multi-region strongly consistent mode exists for stricter cases.

Around the table
  • Streams: ordered change log
  • per item, kept 24 h →
  • Lambda, search index, cache
  • TTL: delete items after a
  • timestamp, free
  • Global tables: multi-region
  • active-active; last writer
  • wins, about a second of lag
  • (strong multi-region mode
  • now offered too)

Great at

DynamoDB shines when you have known key-based access at large or unpredictable scale and do not want to run databases. Latency stays flat as the table grows from gigabytes to hundreds of terabytes. There is nothing to patch or fail over. It suits shopping carts, sessions, user profiles, device state and idempotency keys, and it fits naturally with Lambda-based serverless designs.

Where DynamoDB shines
  • • key lookups at any scale
  • with steady single-digit ms
  • • zero ops: no servers,
  • patches, failover, shards
  • • spiky traffic (on-demand)
  • • serverless stacks (Lambda)
  • • sessions, carts, profiles,
  • device state, idempotency
  • keys

Where it hurts

Where it hurts. No joins and no ad-hoc queries: a question your keys do not answer means a full table scan or a new index. Requirements that change after launch can force a remodel. Hot partitions throttle. Items are capped at four hundred kilobytes. Global indexes are eventually consistent. It locks you into AWS, and at high steady throughput it can cost more than running Cassandra or Postgres yourself.

Pain points
  • • no joins, no ad-hoc queries
  • • Scan reads the whole table
  • • new access pattern = new
  • GSI or a remodel
  • • hot partitions throttle
  • • 400 KB item limit
  • • GSIs are eventually
  • consistent
  • • AWS only; costly at high
  • steady throughput

Use it when

Use DynamoDB when you are on AWS, your access patterns are known, every query is a key lookup or a key plus range, and you want zero operations, especially with spiky or very large traffic. Avoid it for reporting and ad-hoc queries, for relational data with many joins, for very high steady load where cost matters most, or when you must stay cloud-neutral. Export to S3 for analytics instead.

Use it when / avoid it when
  • USE when
  • access patterns are known
  • lookups by key or key+range
  • on AWS, want zero ops
  • traffic is spiky or huge
  • AVOID when
  • reporting or ad-hoc queries
  • many relations and joins
  • steady huge load (cost)
  • must stay cloud-neutral

Numbers

The limits to know. Items up to four hundred kilobytes. A write unit covers one kilobyte, a read unit four kilobytes. Each partition tops out at three thousand read units and one thousand write units a second, and holds about ten gigabytes. A query returns at most one megabyte per page, so you paginate. Transactions hold up to one hundred items. Streams keep twenty-four hours. Typical latency is single-digit milliseconds, microseconds with DAX.

Numbers worth knowing
item size max          400 KB
WCU                    1 KB write/s
RCU                    4 KB strong read/s
per partition          3,000 RCU, 1,000 WCU
partition size         ~10 GB
Query page             1 MB max
transaction            100 items max
stream retention       24 hours
typical latency        single-digit ms

Say it like this

How to say it. Start with access patterns: get a cart by user, list a user's orders newest first, list open orders for operations. Then the keys: partition key user id, sort key order timestamp, plus a global index on status and created at. On-demand capacity for spiky traffic, streams to update the search index, and a sharded key for anything hot. Interviewers want to see the access patterns before the table.

In the interview
  • "Access patterns: get cart by
  • user, list orders by user
  • newest first, list open
  • orders. Table PK=user_id,
  • SK=order_ts. GSI on status +
  • created_at. On-demand mode,
  • Streams to update search.
  • Hot sellers: shard the key."

Interview questions

What are the partition key and sort key in DynamoDB?

The partition key is hashed to choose the partition that stores the item; the optional sort key orders items that share a partition key, enabling range queries such as all orders for a user in a date range.

What causes a hot partition and how do you fix it?

Too much traffic on one partition key, which is capped at about 3,000 read units and 1,000 write units per second. Use high-cardinality keys, spread a hot key with random or computed suffixes and merge on read, or cache hot reads with DAX.

What is the difference between a GSI and an LSI?

A global secondary index can use any partition and sort key, has its own throughput, can be added any time and is eventually consistent. A local secondary index keeps the base partition key with another sort key, supports strong reads, must be created with the table and limits each partition key value to 10 GB.

Are DynamoDB reads strongly consistent?

Not by default; reads are eventually consistent and cost half a read unit per 4 KB. You can request strongly consistent reads on the base table and local indexes, but not on global secondary indexes.

What is single-table design?

Storing several entity types in one table with generic partition and sort keys, so that a single Query returns related items together, such as a user profile and all of that user’s orders, since DynamoDB has no joins.

Why should you avoid Scan?

A Scan reads every item in the table or index and consumes capacity for all of it, so cost and latency grow with table size. Design keys or indexes so that every access pattern is a GetItem or Query.

How do you react to changes in a DynamoDB table?

Enable DynamoDB Streams, which records item-level changes in order for 24 hours, and process them with Lambda or Kinesis to update caches, search indexes or other services.

When would you not choose DynamoDB?

When queries are ad-hoc or analytical, when data is highly relational, when steady high throughput makes per-request pricing expensive, or when the system must not depend on AWS.