12 August 2026 · 7 min read · recloud engineering
DynamoDB: single-table vs multi-table design, honestly
Single-table design has become DynamoDB orthodoxy: cram every entity into one table, overload the keys, and treat conference-talk heroics as best practice. It is genuinely the right call sometimes — and an expensive habit the rest of the time. Having built subscriber stores, order pipelines and session tables both ways, here is the honest ledger.
What single-table actually buys
One thing, mainly — and it's a good thing: related entities in one item collection, fetched in one request. Put a subscriber's profile and their live session under the same partition key and the hot path reads both with a single Query:
PK SK attributes
SUB#INH0012345 PROFILE plan, status, pool
SUB#INH0012345 SESSION sid, nas, counters, ttl
When that read happens on every subscriber login, saving a round trip is real money and real latency. Secondary benefits are smaller than advertised: fewer tables to alarm on, shared capacity smoothing spiky entities, transactions that co-locate naturally (though TransactWriteItems spans tables anyway, so this one is mostly aesthetic).
What it costs
- Legibility.
PK=SUB#... SK=ORDER#2026-09#...is a bespoke encoding only the design document understands. Every new engineer pays the decoding tax; every debugging session starts with the cheat sheet. - GSis become a shared, finite resource. Twenty overloaded access patterns competing for a handful of indexes leads to key gymnastics that make the schema even less readable.
- Evolution gets scary. Adding an entity means threading it through existing key conventions without colliding. In multi-table land it means… creating a table.
- Analytics hates it. Exports to Athena or ClickHouse arrive as a soup of heterogeneous items that every query must first untangle.
- IAM gets coarse. Table-level permissions can't say "the portal may read profiles but not sessions" when both live in one table.
What multi-table costs
Less than the talks imply. The extra reads are parallel BatchGetItem calls, not sequential waterfalls — single-digit milliseconds side by side. Transactions still work across tables. The genuine loss is the item-collection query: if your hot path truly assembles three entities per request at high volume, multi-table makes you pay for it on every call.
The decision rule that survives production
Ask one question per bounded context: is there a high-volume request that must read multiple entity types together?
- Yes — subscriber + session on every RADIUS authorization, cart + items on every checkout: give that context a small, documented single table. (Our RADIUS REST backend is exactly this shape.)
- No — orders here, invoices there, each read by its own service at its own pace: use a table per entity and enjoy schemas a new hire reads in one sitting.
Note the phrase per bounded context. The mature pattern isn't one mega-table for the company; it's a handful of small single-tables where item collections earn their keep, and plain tables everywhere else. Single-table design is a performance optimisation — apply it where the performance exists to optimise.