DynamoDB single-table design is the most counterintuitive database pattern I've ever learned. You take all your entities — users, orders, products, reviews — and jam them into one table with generic partition and sort keys. It sounds like madness until you understand why it works.
Why Single Table?
DynamoDB charges you per table for on-demand capacity and per-request for provisioned capacity. But the real reason for single-table design isn't cost — it's performance. When related entities share a partition key, you can fetch them all in a single query instead of making multiple requests to different tables.
Access Patterns Drive Everything
In relational databases, you design the schema first and figure out queries later. In DynamoDB, you flip that: list every access pattern your application needs, then design the key schema to support them.
Here's an example for an e-commerce app:
| Access Pattern | PK | SK |
|---|---|---|
| Get user by ID | USER#123 | PROFILE |
| List user's orders | USER#123 | ORDER#2026-03-01#abc |
| Get order details | ORDER#abc | DETAIL |
| Get order items | ORDER#abc | ITEM#sku-1 |
GSI Overloading
Global Secondary Indexes (GSIs) are the other key tool. By creating a GSI with a different partition key, you enable entirely different query patterns on the same data.
// GSI1: query orders by status
// GSI1PK: STATUS#shipped
// GSI1SK: 2026-03-01T10:00:00Z
const params = {
TableName: 'MyTable',
IndexName: 'GSI1',
KeyConditionExpression: 'GSI1PK = :status AND GSI1SK > :since',
ExpressionAttributeValues: {
':status': 'STATUS#shipped',
':since': '2026-02-01T00:00:00Z',
},
};
The Trade-Offs
Single-table design isn't free. The downsides are real:
- Steeper learning curve — new team members will be confused by the generic key names
- Harder to evolve — adding a new access pattern sometimes requires a new GSI or even a data migration
- CloudFormation complexity — your table definition becomes a sprawling artifact
For small applications with simple access patterns, separate tables are fine. Single-table design shines when you have complex, interrelated entities and need predictable, single-digit-millisecond latency at any scale.
Lessons from Production
After running a single-table DynamoDB setup serving 50 million requests per day, here's what I wish I'd known:
- Hot partitions are real. If one partition key gets too much traffic, DynamoDB will throttle it. Design your keys to distribute load evenly.
- TTL is your friend. Use DynamoDB's built-in TTL to automatically clean up expired data instead of running batch deletion jobs.
- Always project only the attributes you need in GSIs. Full projections waste storage and increase write costs.
The initial investment in access pattern analysis pays dividends for years. Take the time to get it right.