DEV Community

Cover image for When NOT to Use Single-Table Design in DynamoDB
DynoTable
DynoTable

Posted on Originally published at dynotable.com

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 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 BY or 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:

When NOT to Use Single-Table Design in DynamoDB

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.

Top comments (0)