When NOT to Use Single-Table Design in DynamoDB
Single-table design is the default advice for DynamoDB, and it earns it: one
Query hands back a parent and its children, no joins, no N+1.
But it is a trade — you buy read speed with a rigid, opaque schema. Some workloads can't afford that price, and forcing one table on them is its own footgun.
When should you not use single-table design in DynamoDB?
Avoid single-table design when your workload is heavy analytics, plain CRUD over a handful of unrelated entities, or entities that scale and fail independently. In those cases multiple tables read better, cost the same, and stay flexible. Single-table design only wins when access patterns are known, related, and high-volume.
- Heavy analytics? Don't single-table. Overloaded keys are OLAP-hostile — export to a columnar store and query there instead.
- Plain CRUD with a handful of access patterns? One table per entity is fine, readable, and costs you nothing in performance.
- Entities that scale or fail independently? Separate tables let you tune, bill, and blast-radius them on their own.
- Single-table still wins when your patterns are known, related, and high-volume — that's the case it was built for.
Know what single-table actually costs
Single-table design isn't free; it just moves the cost off the read path and onto everything else. You pay in legibility and flexibility.
A table holding five entity types behind PK/SK is hard to read, hard to
onboard onto, and hard to change. A new access pattern can mean a backfill
across every item type in the partition.
So the question isn't "is single-table good?" It's "do my access patterns justify the rigidity?" When they don't, reach for multiple tables.
Don't single-table an analytics workload
DynamoDB is built for OLTP — small, known, point-and-range reads. Analytics is
OLAP: GROUP BY, big aggregates, ad-hoc slicing across the whole dataset. The
two pull in opposite directions.
Single-table design makes OLAP worse, not better. Overloaded keys and mixed entity types mean an analytics job must first untangle which item is which before it can sum anything — the opposite of a clean columnar scan.
Coming from SQL, the reflex is to write the aggregate against the live table.
In DynamoDB that's a full Scan — you pay for and read every item, which is the
Scan footgun at full volume.
The fix isn't a cleverer key. It's a different store. AWS's own guidance is to export DynamoDB to S3 and run analytics with a query engine like Athena.
Keep OLTP and OLAP on separate engines (AWS DynamoDB Developer Guide,
S3DataExport.html).
Don't single-table simple CRUD
If your app reads and writes a few unrelated entities by their own id, and you have maybe three access patterns, single-table buys you nothing.
Take a Tenants table and an ApiKeys table for a small B2B tool:
| TenantId | Name | PlanTier |
|---|---|---|
| TNT-8842 | Northwind | pro |
| KeyId | TenantId | Scope |
|---|---|---|
| KEY-01H9... | TNT-8842 | read-write |
Each query is a GetItem by id, or a Query on a GSI keyed by TenantId.
There's no parent-and-children fetch to optimise, so there's nothing for an
overloaded partition to win. Two clear tables read better than one murky one.
The trap is cargo-culting single-table because it's "best practice." Best practice is access-pattern-first. If the patterns are trivial, the simple shape is the right shape.
Split tables that scale or fail independently
A single table shares one throughput surface, one backup, one blast radius. When two entities have wildly different write rates or durability needs, that shared fate becomes a liability.
Picture a fleet-tracking system. Vehicles change rarely; their telemetry pours in every second:
| VehicleId | Make | Model | Region |
|---|---|---|---|
| VEH-204 | Volvo | FH16 | eu-west |
| DeviceTs | VehicleId | SpeedKph | Fuel |
|---|---|---|---|
| 2026-06-23T10:00:01Z | VEH-204 | 88 | 0.61 |
Two tables let you provision telemetry for a firehose, keep vehicles tiny and cheap, set a TTL on telemetry alone, and stop a telemetry write storm from throttling reads of the vehicle catalogue. One table couples all of that.
Per the 2007 Amazon Dynamo paper, partitioning and availability are first-class concerns — independent tables give you independent control over both.
Map the decision before you commit
Walk the workload through one gate: are the entities related, and are the patterns known and high-volume? If not, multiple tables.
Here's the call as a flow — start at the top and follow the first branch that matches:
Only the bottom-right leaf earns single-table; every other path is better served by more than one table.
Single vs multiple, head to head
| Factor | Single-table | Multiple tables |
|---|---|---|
| Related reads | One Query, no joins | Client-side join or extra round-trips |
| Readability | Opaque overloaded keys | One entity per table, self-documenting |
| New access pattern | Often a backfill | Add a table or GSI in isolation |
| Analytics / OLAP | Hostile — untangle before aggregating | Still export, but cleaner per-entity |
| Independent scaling | Shared throughput + blast radius | Tune, bill, TTL, and back up separately |
| Best when | Known, related, high-volume patterns | Unrelated, evolving, or analytical |
Pitfalls + next steps
The mirror-image mistake is over-correcting — splitting genuinely related, high-volume entities into a table-per-entity and rebuilding SQL-style joins in your app. That's the N+1 trap single-table kills.
Pick the shape the access patterns ask for, not the dogma.
When you do model relationships, lean on the right index type — see GSI vs LSI before you add one.
When you do write a query against a multi-table schema, sketch the
KeyConditionExpression in the
DynamoDB Expression Builder first.
That way you catch a full-Scan shape before it hits production.
Then try DynoTable to browse both shapes against your own tables and see which one your access patterns actually want.