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.
- 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.
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.
- 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.
- 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.
- 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.
- 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.
- 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.
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.
- 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.
- • 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.
- • 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 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.
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.
- "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."