11 — DynamoDB: NoSQL Designed Around the Access Pattern
"JSON storage, fast" was my DynamoDB summary — until I tried to port a normalized schema into it and hit a wall. The inversion that registered: *DynamoDB is a key-value and document database that delivers single-digit-millisecond performance at any scale, and the cost of that performance is that I design the schema around the access patterns, not around the data itself* [1]. With a relational database I normalize the data and write queries against it; with DynamoDB I list the questions the app will ask and shape the table so each one is a key lookup. That flip is the whole service, and getting it wrong is the whole failure mode.
Tables, items, attributes
The data model is three nested layers [2]:
- Table — the top-level container. Like a relational table, but schema-less.
- Item — a single record in a table, identified by its primary key. Roughly analogous to a row.
- Attribute — a name-value pair on an item. Roughly analogous to a column, except every item can have different attributes. One item in a table can have email and another can have phone, and that's fine.
Attribute values can be scalar (string, number, binary, boolean, null) or nested (list, map, set). There's no enforced schema beyond the primary key — I can add attributes freely, which is the "NoSQL" part people notice first. The constraint that matters more: an item, including the key, is capped at 400 KB [5]. DynamoDB is not for big blobs; large content goes to S3 and the item holds a pointer.
Primary keys and the partition math
The primary key decides how data is distributed and how it can be queried [3]. Two shapes:
- Partition key only (a "simple" primary key) — one attribute. DynamoDB hashes the key to pick a partition; each item lives on exactly one partition. Lookups are "give me the item with this key." Good for pure key-value.
- Partition key + sort key (a "composite" primary key) — two attributes. Items with the same partition key live together, sorted by the sort key. This unlocks range queries: "all items for user 42, sorted by timestamp, give me the last 10." The pattern for time-series and one-to-many relationships.
The non-obvious fact: the partition key's job is to spread data evenly across partitions. A "hot" partition key (one user whose items dwarf everyone else's) creates a hotspot and throttles. Picking a partition key is an access-pattern decision and a load-distribution decision at the same time.
Secondary indexes: when the key doesn't match the query
If I need to query on something that isn't the primary key, I add a secondary index [3]:
- Global Secondary Index (GSI) — a different partition key and/or sort key from the base table. The most flexible; the index can be queried independently. Has its own capacity settings.
- Local Secondary Index (LSI) — same partition key as the base, different sort key. Defined at table creation; limited in number per table.
Indexes are not free: they store a second copy of the (projected) data and consume capacity on writes. The disciplined approach is to create the minimum set that covers the access patterns, not to index every attribute the way I might in a relational schema.
Data modeling: design for the query
This is the inversion. The DynamoDB design process I follow:
- List the access patterns — "get a user's profile," "list a user's recent orders," "fetch one order by ID." Every query the app will make.
- Shape the table so each is a key lookup. If the access pattern is "all orders for a user, newest first," the table's primary key becomes partition key = USER#42, sort key = ORDER#2024-01-15. One query, no scan.
- Add GSIs only for queries that genuinely need a different key. Each GSI is a documented trade-off.
Relational instinct says "normalize the data and trust the query planner." DynamoDB says "denormalize deliberately, because there is no planner — the schema is the index." Single-table design, where multiple entity types share one table distinguished by key prefixes, is the advanced expression of this, and it's controversial exactly because it inverts normalization so hard.
Capacity: provisioned vs on-demand
Throughput is the other lever [4]:
- Provisioned — I set read and write capacity units per table (and per GSI). Cheaper if the workload is predictable, but I have to guess right or throttle.
- On-demand — DynamoDB scales automatically to whatever the workload asks for. No capacity planning; a small price premium per request. The right default for unpredictable or spiky workloads.
A read capacity unit is one strongly-consistent read per second (or two eventually-consistent) of an item up to 4 KB. A write capacity unit is one write per second of an item up to 1 KB. The math is mechanical once the item sizes are known.
Limits, local, and the operational shape
Worth remembering [5]:
- Item size cap is 400 KB (key included).
- Partition key value up to 2048 bytes; sort key up to 1024 bytes.
- On-demand per-table throughput caps exist (40,000 RCUs / 40,000 WCUs by default), raisable via support.
- DynamoDB Local is a downloadable version that mimics the real service's API for offline development [6] — the same code, no cloud round-trip.
How I use this
DynamoDB is my pick when the workload is key-accessed, the latency target is single-digit milliseconds, the scale is genuinely large, and I can enumerate the access patterns upfront. I do not reach for it for ad-hoc analytics, for data I'll query in ways I can't yet predict, or as a relational substitute — that's RDS. When I do use it, I list the queries before I touch the console, design the key around them, set capacity to on-demand until the usage is predictable, and keep items small (big payloads go to S3). The discipline is honest about the trade: DynamoDB gives me speed and scale; what it takes in return is the freedom to ignore access patterns. I can't ignore them on DynamoDB, and pretending otherwise is where the throttles come from.
References
[1] Amazon Web Services, "What is Amazon DynamoDB?," 2024. [Online]. Available: https://aws.amazon.com/dynamodb/
[2] Amazon Web Services, "Working with items and attributes," DynamoDB Developer Guide, 2024. [Online]. Available: https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/WorkingWithItems.html
[3] Amazon Web Services, "Secondary indexes," DynamoDB Developer Guide, 2024. [Online]. Available: https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/SecondaryIndexes.html
[4] Amazon Web Services, "Capacity modes," DynamoDB Developer Guide, 2024. [Online]. Available: https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/capacity-mode.html
[5] Amazon Web Services, "Service, account, and table quotas," DynamoDB Developer Guide, 2024. [Online]. Available: https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/ServiceQuotas.html
[6] Amazon Web Services, "DynamoDB Local," DynamoDB Developer Guide, 2024. [Online]. Available: https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/DynamoDBLocal.html
Knowledge check · Question 1 of 5
In DynamoDB, the schema should be designed around…
Comments
Leave a Comment
You must be signed in to comment
0 Comments
No comments yet. Be the first to comment!