Library
Learn/Databases/Chapter 16
11 min read

DynamoDB

During Prime Day, Amazon has reported DynamoDB peaks of well over a hundred million requests a second, with single digit millisecond responses throughout. No query planner, no joins, no surprise full table scan at the worst moment. DynamoDB gets there by making a bargain with you: it will be fast and predictable at any scale, as long as you tell it up front exactly how you will read your data.

That bargain is the whole chapter. Understand the partition key and you understand how DynamoDB scales, why it is fast, and why designing for it feels backwards the first time.

Your answer

With DynamoDB you are told to list every query your app will make before you design the table. A relational database never asks for that. Why would DynamoDB need it?

Tables, items and keys#

A table holds items (like rows), each up to 400 KB, made of attributes (like columns, but each item can have different ones). Every item has a primary key, in one of two forms:

  • a partition key alone, such as user_id, when you only ever fetch one item by its id
  • a partition key plus a sort key, such as customer_id and order_date, when you want all of a customer's orders, sorted by date, in one request

The partition key decides where an item is stored. The sort key decides the order of items that share a partition key. Those two facts explain almost everything else.

How it works inside#

Figure 1. The partition key is hashed to pick a partition, and each partition is three copies in three availability zones. Every request touches one partition, which is why speed does not change as the table grows.
  1. Your request reaches a request router, which hashes the partition key.
  2. The hash falls in a range owned by one partition. Each partition holds a slice of the table, up to about 10 GB, and DynamoDB splits partitions automatically as data grows.
  3. A partition is a group of three replicas in three availability zones. One is the leader. A write is acknowledged once it is durable on the leader and at least one other replica, so losing a whole data centre loses nothing.

This is consistent hashing at work, the same idea as in Consistent hashing and Design a key value store. Every request goes to exactly one partition, so a table with a billion items answers as fast as one with a thousand.

The partition key decides your fate#

Each partition can serve a limited amount of traffic, roughly 3,000 reads and 1,000 writes a second. Spread traffic across many partition key values and DynamoDB scales almost without limit. Concentrate it on a few and you hit that ceiling no matter how much capacity the table has. That is a hot partition, and it is the most common DynamoDB problem.

Partition key for an orders table
13
12
13
12
13
12
13
12
0
1
2
3
4
5
6
7
Partition 2 carries 13.0% (even would be 12.5%)
Millions of customers, each ordering occasionally. Traffic spreads evenly over every partition, and "all orders for this customer" is a single Query. The default good choice.

Capacity is measured in units#

DynamoDB bills and throttles in read capacity units and write capacity units. One write unit is one write a second of an item up to 1 KB. One read unit is one strongly consistent read a second of up to 4 KB, or two eventually consistent ones. Bigger items cost more units.

Capacity for a table
5000
800
3 KB
Read units, strongly consistent5,000 x ceil(3 / 4) = 5,000
Read units, eventually consistenthalf of that = 2,500
Write units800 x ceil(3 / 1) = 2,400
Partitions needed for throughput alone3
2,400 write units
and every one of them depends on item size

A 3 KB item costs three write units per write. Trimming items, and storing large blobs in object storage with only a pointer in DynamoDB, cuts cost directly. You can pay per request (on demand) or reserve capacity (provisioned, with auto scaling), which is cheaper for steady traffic.

Query, not scan#

There are two ways to read many items, and the gap between them is the gap between fast and broken.

Query takes a partition key, and optionally a condition on the sort key (equals, between, begins with). It reads one partition's items in sort key order and stops. Fast at any table size.

Scan reads every item in the table and filters afterwards. It is slow, and it is billed for everything it reads, not just what it returns. In production code, a Scan is almost always a sign the table is missing a key or an index for that access pattern.

Design from the access patterns#

This is the answer to the question at the top. DynamoDB has no planner to find a clever way to answer a query you did not prepare for. Every fast read is a Query on a key you designed. So you list the questions first, then shape keys that answer each one with a single Query.

food_orders (one table)DynamoDB, keys shaped for the access patterns
PKpartition keyPKCUSTOMER#17 for a customer's items, ORDER#44821 for an order's items. Different entity types share the table, distinguished by prefix.
SKsort keyPKPROFILE for the customer record, ORDER#2026-10-15#44821 for each order, ITEM#1 and ITEM#2 for an order's lines.
GSI1PK, GSI1SKindex keysIDXRESTAURANT#9 and the order time, so a restaurant can list its orders. A global secondary index with its own keys.
status, total, ...attributesOrdinary attributes. Only key attributes can be queried efficiently.
Sample row
CUSTOMER#17 | ORDER#2026-10-15#44821 | RESTAURANT#9 | 2026-10-15T20:41 | delivered | 380
Get a customer's profile: PK = CUSTOMER#17, SK = PROFILE. A customer's orders, newest first: PK = CUSTOMER#17, SK begins with ORDER#, descending. One order with its items: PK = ORDER#44821. A restaurant's orders tonight: Query the index on GSI1PK = RESTAURANT#9. Four access patterns, four single Queries, no joins.

Putting several entity types in one table with prefixed keys is called single table design. It makes related data come back in one request. It is also harder to read and to change, and many teams reasonably use one table per entity instead. Either way, the access patterns come first.

Secondary indexes#

When you need to query by something other than the primary key, add an index.

Global secondary index (GSI). Any partition and sort key you like, such as restaurant and time. It is a separate copy of the data, updated asynchronously, so reads from it are eventually consistent, and it has its own capacity. You can add one at any time.

Local secondary index (LSI). Same partition key, different sort key, such as a customer's orders sorted by total instead of date. It allows strongly consistent reads, but must be created with the table, and limits each partition key value to 10 GB of data.

Safe writes without locks#

DynamoDB has no long running transactions, but it has precise tools. A condition expression makes a write happen only if something is true: attribute_not_exists(PK) to create an item only if it is new, which is how you enforce uniqueness, or version = 7 for optimistic locking, the same idea as the isolation levels chapter. TransactWriteItems groups up to 100 writes across items and tables into one all or nothing operation, at twice the capacity cost.

How would you serve this?
All orders for a customer, newest first
A restaurant's orders in the last hour
Every order over Rs 5,000 across all customers, monthly
Make sure a username is never taken twice
ChoiceWhat you gainWhat you payPick it when
DynamoDBPredictable single digit millisecond latency at any scale, fully managed, no servers to run.Every query must be planned in advance, no joins, and ad hoc analytics is painful.Known, key based access patterns at high or spiky scale: carts, sessions, orders by customer.
A relational databaseAsk any question later, joins, and rich transactions.Scaling writes beyond one primary takes real work.Access patterns that will keep changing, or heavy relational queries.
On demand capacityNo capacity planning, and it absorbs sudden spikes.Higher price per request than well used provisioned capacity.New tables, unpredictable traffic, and spiky workloads.
Do not pick DynamoDB for a schema you have not settled

Adding a new access pattern later means a new index or reshaping keys and backfilling data. If the product is young and the questions change every sprint, a relational database will let you move faster, and you can revisit when the patterns settle. The SQL vs NoSQL chapter has eight workloads to practise this choice on.

Checkpoint#

Checkpoint

1. Why must you design DynamoDB tables from your access patterns?

2. Every write today is throttled even though the table has plenty of total capacity. The partition key is order_date. Why?

3. How do you guarantee a username is unique in DynamoDB?

Worth memorising
  • Partition key picks where, sort key picks order. Items up to 400 KB.
  • About 3,000 reads and 1,000 writes a second per partition. Spread the key.
  • 1 WCU = one 1 KB write a second. 1 RCU = one 4 KB strong read, or two eventual.
  • Query, never Scan, in production. Design keys from the access patterns.
Say this in 60 seconds

DynamoDB stores items in tables with a primary key that is either a partition key or a partition key plus a sort key. The partition key is hashed to choose a partition, each replicated three ways across availability zones, so every request touches one partition and latency stays flat as the table grows. Each partition has a throughput limit, so I choose a high cardinality partition key to avoid hot partitions. Capacity is measured in read and write units that scale with item size. Because there is no planner or joins, I list the access patterns first and shape keys so each is a single Query, using global secondary indexes for other patterns, condition expressions for uniqueness and optimistic locking, and transactions when several items must change together. If the questions are still changing, I would start relational instead.

Finished this one?Mark it complete to track your progress through Databases.