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 OLAP 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.
Ask whether your access patterns justify the rigidity. When they don't, reach for multiple tables.
Multiple tables on on-demand in us-east-1 do not cost more per operation —
each GetItem still bills 0.5 RCU per 4 KB eventually-consistent regardless
of how many tables you split across. The savings come from avoiding full-table
Scans and cross-entity Querys you never needed, not from a cheaper billing
tier. Compare table counts in the pricing calculator.
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).
Tip
Need a one-off
GROUP BYor aggregate over a bounded result set — not the
whole table — DynoTable's SQL Workbench runs it client-side, within DynamoDB's
access-pattern rules.That's a debugging convenience, not an analytics pipeline.
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:
Tenants
| TenantId | Name | PlanTier |
|---|---|---|
| TNT-8842 | Northwind | pro |
ApiKeys
| 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 optimize, 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:
Vehicles (cold, low write rate)
| VehicleId | Make | Model | Region |
|---|---|---|---|
| VEH-204 | Volvo | FH16 | eu-west |
Telemetry (hot, high write rate, TTL'd)
| 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 catalog. 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.
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.











