Back to Solutions

DynamoDB Single-Table Design

Access patterns first, schema second—and why it still hurts my head every time

I've designed dozens of DynamoDB single-table schemas. I built FluentDynamoDB specifically to enforce best practices and reduce the boilerplate. And honestly? It still makes my head hurt every time I start a new one.

That's not a complaint—it's the nature of the problem. Single-table design requires thinking about your data differently than relational databases. The payoff is massive (single-digit millisecond reads at any scale), but the upfront planning is real work.

Why Single-Table Design Is Hard

Relational databases let you normalize your data and figure out queries later. JOINs handle the complexity at read time. DynamoDB flips this: you denormalize upfront and design your schema around how you'll query it.

The Mental Shift

Stop thinking about entities. Start thinking about access patterns.

  • • "Get user by ID" → partition key design
  • • "Get all orders for a user" → sort key design
  • • "Get all orders in a date range" → GSI design
  • • "Get user by email" → another GSI

Every access pattern you need must be supported by either the primary key or a Global Secondary Index. Miss one during planning, and you're either doing expensive scans or restructuring your table.

The Planning Process

Before writing any code or creating any tables, I work through these steps:

1

List Every Access Pattern

Write down every way your application needs to read or write data. Be specific: "Get user by ID," "List orders for user sorted by date," "Find all users in organization."

2

Identify the Primary Key

Find the access pattern that's most critical or most frequent. That usually drives your partition key (PK) and sort key (SK) design.

3

Design GSIs for Remaining Patterns

Each access pattern that can't be served by the primary key needs a GSI. You get 20 per table—use them strategically.

4

Plan Your Key Prefixes

Single-table means multiple entity types in one table. Prefixes like "USER#123" and "ORDER#456" keep them organized and queryable.

The Spreadsheet Phase

I literally use a spreadsheet to map this out. Columns for PK, SK, GSI1PK, GSI1SK, etc. Rows for each entity type. It's not glamorous, but it catches problems before they're baked into code.

Common Patterns

Hierarchical Data

Parent-child relationships using composite sort keys:

PK: ORG#123 | SK: ORG#123 → Organization record

PK: ORG#123 | SK: USER#456 → User in organization

PK: ORG#123 | SK: USER#456#ORDER#789 → User's order

Query PK = "ORG#123" to get everything in the org. Add SK begins_with "USER#456" to get one user's data.

Inverted Index (GSI)

When you need to query the "other direction":

Table: PK: USER#123 | SK: ORDER#456

GSI1: PK: ORDER#456 | SK: USER#123

Table gives you "orders for user." GSI gives you "user for order."

Sparse Index

GSI that only contains items with a specific attribute:

GSI2PK: Only populated on "active" orders

Query GSI2 → Only returns active orders

Efficient for filtering without scanning. Only items with the GSI attribute appear in the index.

Mistakes I've Made (So You Don't Have To)

Hot Partitions

Using a low-cardinality partition key (like "status" or "type") concentrates all traffic on a few partitions. DynamoDB throttles you even if you have capacity to spare.

Forgetting an Access Pattern

Discovered mid-project that we needed "get all items created in the last 24 hours." No GSI supported it. Options were expensive scan or table restructure.

Over-Indexing

Every GSI duplicates data and consumes write capacity. Adding GSIs "just in case" gets expensive fast. Only index what you actually query.

Large Items in GSIs

GSIs copy the entire item by default. If your items are large, project only the attributes you need for that access pattern.

Why I Built FluentDynamoDB

After implementing single-table patterns repeatedly, I got tired of the boilerplate. The AWS SDK is powerful but verbose. I wanted something that:

  • Enforces key prefix conventions automatically
  • Generates type-safe repository code from entity definitions
  • Works with AOT compilation (no reflection at runtime)
  • Makes the "right way" the easy way

FluentDynamoDB doesn't make the planning easier—you still need to think through your access patterns. But once you have the design, it makes implementation faster and less error-prone.

When Single-Table Isn't Worth It

Single-table design isn't always the right choice. Consider multiple tables when:

Multiple Tables Make Sense

  • • Entities have completely different access patterns
  • • Different capacity/scaling needs per entity
  • • Team boundaries align with entity boundaries
  • • Simpler mental model is worth the trade-off

Single-Table Shines

  • • Related entities queried together
  • • Transactional writes across entity types
  • • Consistent access patterns across entities
  • • Minimizing round trips matters

Technology Stack

DynamoDB .NET FluentDynamoDB Source Generators GSI Single-Table Design

Need Help With Your DynamoDB Design?

Let's work through your access patterns and design a schema that scales.

Get in Touch