DynamoDB Data Modeling: Access-Pattern-First Design
Engineers who model DynamoDB entity-first always retrofit key schemas and pay the migration tax — a full table scan and rewrite to change key design after data exists in production. This article covers access-pattern enumeration methodology, generic key overloading, composite sort keys, Adjacency List for many-to-many relationships, overloaded GSIs, and sparse indexes.
AWS Serverless Engineering: Lambda to Production
DynamoDB Data Modeling: Access-Pattern-First Design
Every AWS primitive is a tradeoff surface, not a feature toggle. DynamoDB's partition key is not a primary key in the relational sense — it is a routing decision that determines which storage node handles your request, and changing it after data exists means rewriting every item in the table. Engineers who model entities first and derive access patterns second always discover a query they forgot three months into production — and pay the migration tax: a full table scan, a rewrite script, and a production freeze window to swap key schemas. This article covers the discipline that eliminates that tax: enumerate every query before writing any key schema.
Prerequisite: This article builds on Document & NoSQL Databases: MongoDB & DynamoDB in Production, which covers the embed vs reference decision, basic single-table concepts, and DynamoDB Streams introduction. Those foundations are not repeated here.
1. Access-Pattern Enumeration — Design Entry Point
The correct entry point for DynamoDB schema design is the API contract, not the data model. Before writing a single attribute name, you must enumerate every read operation the system will perform — including the ones that come from reporting, audit, admin tooling, and mobile clients with different query needs.
1.1 The Enumeration Method
1.2 The Broken Pattern — Entity-First Modeling
2. Generic Key Overloading — One Table, Many Entity Types
DynamoDB's single-table design stores multiple entity types in one table by using generic key attribute names (PK, SK) that hold different semantic values for different entity types.
2.1 The Key Schema
2.2 Composite Sort Keys for Hierarchical Queries
3. One-to-Many and One-to-One Patterns
4. Adjacency List — Many-to-Many Relationships
The Adjacency List pattern represents many-to-many relationships by storing each relationship as two items — one in each direction.
5. Sparse Indexes — Entity-Scoped GSIs Without Write Amplification
A sparse index only indexes items that carry the GSI key attributes. Items without the GSI key attributes are not indexed — they are completely invisible to the GSI and pay zero write amplification.
Sparse indexes are ideal for "find all items of type X that are in state Y" queries — where only a small fraction of items match state Y at any given time. Fulfilled orders, resolved support tickets, and expired sessions are all poor sparse index candidates (too many items qualify). Active/pending/open state items are perfect candidates — low cardinality, high query frequency, low write amplification.

6. GSI Overloading — One GSI Serving Multiple Entity Types
An overloaded GSI uses polymorphic GSI1PK/GSI1SK values to serve multiple entity types with different query patterns — reducing the total GSI count and its associated write amplification.
Write amplification scales with GSI count: every item write to the base table triggers one additional write per GSI that indexes that item. At 5 GSIs, an item that appears in all 5 costs 6× WCU per write (1 base + 5 GSI). Model the WCU impact before adding each GSI: WCU_per_write × write_rate × (1 + GSI_count) × item_size_KB.

Summary
| Concept | Rule |
|---|---|
| Design entry point | Enumerate queries first — every unanswered query revises the key design before adding a GSI |
| Composite sort keys | ISO date prefix enables chronological range queries without a GSI |
| One-to-many | Parent PK + child prefix under same partition → single Query for parent + children |
| Adjacency List | Two items per relationship (inverted PK/SK) + GSI for inverse direction |
| Sparse index | Only indexed items carry GSI key attributes — absent key = zero GSI write cost |
| GSI write amplification | (1 + GSI_count) × WCU per write — model before adding each GSI |
What's Next
In Part 8: DynamoDB at Scale — Partition Internals, GSI Backpressure & ACID Transactions, we go below the key schema to the physical storage model: why per-partition hard limits make table-level capacity scaling ineffective for hot partitions, how an under-provisioned GSI throttles base table writes upstream, and when ACID transactions are worth their 2× WCU cost.
This article was developed with AI-assisted deep search, specification cross-referencing, and technical research synthesis.