DynamoDB Single-Table Design: A Comprehensive Modeling Guide
Master DynamoDB single-table design with practical patterns for modeling relationships, choosing between GSI and LSI, optimizing with DAX, and avoiding common pitfalls in production NoSQL systems.
Abstract
Single-table design represents a fundamental shift in how we model data for DynamoDB. This comprehensive guide explores when to use single-table patterns, how to model one-to-one, one-to-many, and many-to-many relationships, the trade-offs between Global and Local Secondary Indexes, DAX caching integration, and practical query optimization techniques. You'll find working TypeScript examples, real-world cost analyses, and battle-tested patterns for avoiding hot partitions and throttling issues.
Why Single-Table Design Matters
Working with DynamoDB taught me that thinking in terms of relational tables causes more problems than it solves. The typical approach - creating separate tables for Users, Orders, Products - leads to multiple round-trips, complex application logic, and unpredictable costs.
Single-table design stores multiple entity types in one table using generic partition and sort keys. Instead of fetching a customer from one table and their orders from another, you retrieve everything in a single request. This isn't just about performance; it fundamentally changes how you approach data modeling.
Core Principles:
- Access patterns first: Document every query before designing the schema
- Data locality: Store related data together using the same partition key
- Generic keys: Use
PKandSKinstead of entity-specific names - Item collections: Group related items with shared partition keys
- Attribute overloading: Same attributes serve different purposes across entity types
When to Use Single-Table Design
Single-table design excels when you need to retrieve related data together. Here's what I've learned about when it works well:
Good Use Cases:
- E-commerce systems where you fetch customers with their orders
- Social platforms retrieving posts with comments and likes
- Multi-tenant SaaS applications with tenant isolation
- Content management systems with hierarchical data
- IoT platforms collecting sensor readings by device
When to Avoid:
Working with teams has shown me that single-table design isn't always the right choice:
- Team lacks DynamoDB expertise (the learning curve is real)
- Simple CRUD applications with minimal relationships
- Ad-hoc reporting and data warehouse scenarios
- Strong consistency required across all entity types
- Different entities have completely different access patterns
Rick Houlihan's 2024 update emphasizes: "What is accessed together should be stored together." Don't force unrelated data into a single table just to follow a pattern.
Partition Key and Sort Key Strategies
The foundation of single-table design lies in understanding how to structure your keys.
Partition Key Patterns
Anti-Pattern to Avoid:
Sort Key Patterns
Sort keys enable range queries and hierarchical organization:
Best Practices:
- Use high-cardinality partition keys (userId, orderId, productId)
- Design sort keys to support range queries with
begins_withandBETWEEN - Include timestamps for chronological ordering
- Maintain consistent prefixes across entity types
Modeling Relationships
Let me show you how to model each relationship type with working examples.
One-to-One Relationships
Store related data in the same item collection with different sort keys:
One-to-Many Relationships
Item collections make one-to-many relationships straightforward:
Many-to-Many Relationships
Use the adjacency list pattern to model many-to-many relationships:
Trade-off: Writing both directions doubles write operations but enables efficient queries in both directions without Scan operations.
Denormalization Pattern
Sometimes you need frequently accessed data without extra queries:
Trade-off Analysis:
- Faster reads: No need to fetch customer details separately
- More complex writes: Update customer name requires updating all their orders
- Storage overhead: Customer data duplicated across orders
- Eventual consistency: Updates to customer data require background job to update orders
Use denormalization when read frequency significantly exceeds write frequency and data rarely changes.
GSI vs LSI: Making the Right Choice
Understanding the differences between Global Secondary Indexes and Local Secondary Indexes is critical for efficient access patterns.
Local Secondary Index (LSI)
LSI shares the partition key with the base table but uses a different sort key:
LSI Characteristics:
- Must be defined at table creation (cannot add later)
- Shares partition key with base table
- Supports strongly consistent reads
- Shares throughput capacity with base table
- 10GB limit per partition key value
- Maximum 5 LSIs per table
- No additional capacity planning needed
Global Secondary Index (GSI)
GSI uses different partition and sort keys, enabling completely new access patterns:
GSI Characteristics:
- Can be added or removed after table creation
- Different partition and sort keys from base table
- Eventually consistent only (no strong consistency)
- Independent throughput in provisioned mode
- No size limits
- Maximum 20 GSIs per table (increased from 5)
- Enables cross-partition queries
Decision Matrix
When to Use LSI:
- Strong consistency is required
- Querying same partition with alternative sort order
- Small datasets (< 10GB per partition)
- Access patterns known at table creation time
When to Use GSI:
- Need different partition key for access pattern
- Cross-partition queries required
- Adding new access patterns to existing table
- Eventually consistent reads are acceptable
- Large datasets
Sparse Index Pattern
Only items with GSI attributes are included in the index, reducing storage costs:
Cost Savings: If only 10% of users are active, sparse indexing reduces GSI storage by 90%.
DynamoDB Accelerator (DAX) Integration
DAX provides microsecond response times for read-heavy workloads through in-memory caching.
When to Use DAX
When NOT to Use DAX
DAX Implementation
DAX Performance Metrics
Cost Analysis
Query Optimization Techniques
The difference between Query and Scan operations dramatically impacts performance and cost.
Query vs Scan
Real-World Impact
Working on systems with millions of users taught me the cost difference is dramatic:
Projection Optimization
Batch Operations
Composite Sort Key for Filtering
Preventing Hot Partitions
Each DynamoDB partition supports 3,000 RCUs and 1,000 WCUs. Exceeding these limits causes throttling.
Hot Partition Scenarios
Prevention Strategy 1: Write Sharding
Prevention Strategy 2: Composite High-Cardinality Keys
Prevention Strategy 3: Deterministic Sharding
This approach ensures the same entity always goes to the same shard, enabling consistent reads without querying all shards.
Cost Optimization Strategies
Provisioned vs On-Demand
When to Use Provisioned:
- Predictable traffic patterns
- High volume (>1M requests/day)
- 24/7 production applications
- Budget-conscious scenarios
- Can commit to reserved capacity (54% 1-year or 77% 3-year savings)
When to Use On-Demand:
- Unpredictable traffic
- New applications with unknown load
- Development/testing environments
- Spiky workloads (10x variance)
- Small-scale applications (less than 1M requests/day)
Sparse Index Savings
Single-Table Cost Benefits
Common Pitfalls and Solutions
Pitfall 1: Not Documenting Access Patterns First
Experience has shown that designing tables before understanding queries leads to redesigns:
Pitfall 2: Missing Error Handling for Throttling
Pitfall 3: Ignoring Item Size Limits
Pitfall 4: Not Planning LSIs at Table Creation
You cannot add LSIs after table creation. This requires data migration if you need them later:
Pitfall 5: Incorrect Capacity Mode
When NOT to Use Single-Table Design
Single-table design isn't always the right choice. Here's when to avoid it:
Use Separate Tables When:
- Different entity types have vastly different consistency requirements
- Team lacks DynamoDB expertise (learning curve impacts velocity)
- Simple CRUD with minimal relationships (overhead not justified)
- Ad-hoc reporting needs (data warehouse patterns better suited)
- Service boundaries in microservices (separate tables per service)
- Different entities have no access pattern overlap
Rick Houlihan's 2024 guidance: "Don't mix configuration and operational data in single table. Don't maintain single table across service boundaries."
Key Takeaways
Here's what working with DynamoDB single-table design has taught me:
- Access patterns first: Document all queries before designing schema
- Data locality: Store related data together with same partition key
- Query not Scan: Always design for Query operations (100-1000x cheaper)
- GSI vs LSI: GSIs for flexibility, LSIs for strong consistency
- Hot partitions: Use high-cardinality partition keys, implement sharding when needed
- DAX ROI: Break-even around 300 req/sec with 90%+ cache hit rate
- Cost optimization: Provisioned mode for steady traffic (6-7x cheaper)
- Sparse indexes: Save 50%+ on storage by indexing subsets
- Projection optimization: Use INCLUDE or KEYS_ONLY instead of ALL
- Know limits: 400KB per item, 10GB per LSI partition, 3,000 RCU per partition
Type-Safe Implementation Libraries
When implementing single-table design patterns with TypeScript, using type-safe libraries instead of raw AWS SDK increases development velocity and prevents runtime errors. Two popular choices:
DynamoDB Toolbox
DynamoDB Toolbox is a modern TypeScript library compatible with AWS SDK v3:
- Type Safety: Automatic TypeScript types from entity definitions
- Schema Validation: Runtime data validation
- Query Builder: Type-safe query and update expressions
- Single-Table Support: Built-in support for GSI and composite key patterns
- AWS SDK v3: Full compatibility with the latest SDK version
Instead of complex AttributeValue mappings with raw AWS SDK, you can use clean, maintainable entity definitions. Check out the DynamoDB Toolbox guide for detailed implementation examples and production best practices.
OneTable
OneTable is an alternative library specifically designed for single-table design:
- Schema-Driven: Model definition with JSON schema
- Migration Support: Built-in schema migration support
- TypeScript Generation: Automatic type generation from schemas
- Developer Experience: Minimal boilerplate, intuitive API
- Validation: Robust validation with JSON Schema standard
OneTable provides powerful tools for schema evolution and migration needs, especially in large and complex single-table designs.
Which to Choose?
Choose DynamoDB Toolbox if:
- You're migrating to AWS SDK v3 or starting fresh
- You want tighter integration with AWS ecosystem
- You'll use more AWS-native patterns
- Detailed guide available on this site
Choose OneTable if:
- You perform schema migrations frequently
- You prefer JSON Schema standard
- You want more abstraction and convention over configuration
- You're doing rapid prototyping
Both libraries are production-ready and actively maintained. The choice depends on team preference and project requirements.
Related Topics
Working with DynamoDB connects to several other areas worth exploring:
- TypeScript Development with DynamoDB Toolbox - Type-safe entity definitions and schema validation for single-table design
- AWS Lambda Guide 101: Cold Start Optimization - Efficient DynamoDB queries in serverless
- Serverless to CDK Migration Guide - Managing DynamoDB tables with CDK
- Serverless Architecture Patterns - DynamoDB in event-driven systems
Single-table design represents a paradigm shift from relational thinking. The key is understanding your access patterns first, designing keys to support efficient queries, and choosing the right indexes for your workload. Start with simpler patterns, measure performance, and evolve your design based on actual usage.