AWS DynamoDB: Advanced Patterns for Production at Scale

AWS DynamoDB: Advanced Patterns for Production at Scale
2 views

DynamoDB mistakes are expensive — not in dollars, but in the hot partitions, full table scans, and impossible access patterns that result from wrong key design. The advanced patterns here — single-table design, GSI overloading, sparse indexes, and conditional writes — are what power the highest-scale production DynamoDB systems. This guide focuses on AWS DynamoDB, with the production trade-offs senior engineers need to make sound decisions.

⚡ TL;DR: Design access patterns first, then choose keys. Single-table: prefix PK/SK for different entity types. GSI for alternate access patterns. Sparse GSI: only items with GSI attribute are indexed. Conditional writes for optimistic locking. DynamoDB Streams trigger Lambda on changes. TTL for automatic expiry.

Single-table design patterns — AWS DynamoDB

// All entities in one table: prefix PK and SK
// Users and orders in same table:
// PK: USER#userId        SK: METADATA          → user entity
// PK: USER#userId        SK: ORDER#orderId      → user's order
// PK: ORDER#orderId      SK: METADATA          → order entity
// PK: ORDER#orderId      SK: ITEM#itemId        → order item

// Query all orders for a user:
const result = await client.send(new QueryCommand({
  TableName: 'main',
  KeyConditionExpression: 'PK = :pk AND begins_with(SK, :prefix)',
  ExpressionAttributeValues: {
    ':pk': 'USER#'+userId,
    ':prefix': 'ORDER#'
  }
}));

// Query single order:
const order = await client.send(new GetItemCommand({
  TableName: 'main',
  Key: { PK: 'ORDER#'+orderId, SK: 'METADATA' }
}));

GSI overloading — multiple access patterns from one GSI

// GSI1: GSI1PK, GSI1SK — overloaded for different entity types

// User entity:
// PK: USER#123   SK: METADATA
// GSI1PK: EMAIL#alice@example.com  GSI1SK: USER#123
// → GSI1 enables: look up user by email

// Order entity:
// PK: ORDER#456  SK: METADATA
// GSI1PK: STATUS#pending            GSI1SK: DATE#2026-04-01
// → GSI1 enables: query all pending orders by date

// Same GSI serves completely different entity lookups!
// Key: different entities use different value formats in GSI1PK

Conditional writes — optimistic locking

// Only update if version matches (prevents lost updates)
const updateResult = await client.send(new UpdateItemCommand({
  TableName: 'inventory',
  Key: { PK: 'PRODUCT#'+productId, SK: 'STOCK' },
  UpdateExpression: 'SET stock = stock - :qty, version = version + :one',
  ConditionExpression: 'stock >= :qty AND version = :v',
  ExpressionAttributeValues: {
    ':qty': { N: '1' }, ':one': { N: '1' }, ':v': { N: String(currentVersion) }
  }
}));
// If stock < qty or version changed: ConditionalCheckFailedException
// Retry with fresh version if optimistic lock fails

DynamoDB Transactions + TTL

// ACID transactions across up to 25 items:
await client.send(new TransactWriteItemsCommand({
  TransactItems: [
    { Update: { // Debit account
      TableName:'accounts', Key:{PK:'ACCT#from'},
      UpdateExpression:'SET balance = balance - :amt',
      ConditionExpression:'balance >= :amt',
      ExpressionAttributeValues:{':amt':{N:'100'}}
    }},
    { Update: { // Credit account
      TableName:'accounts', Key:{PK:'ACCT#to'},
      UpdateExpression:'SET balance = balance + :amt',
      ExpressionAttributeValues:{':amt':{N:'100'}}
    }}
  ]
}));

// TTL: auto-delete expired items (free!)
// Store TTL as Unix timestamp in 'expiresAt' attribute
// Enable TTL: aws dynamodb update-time-to-live --ttl-attribute expiresAt
  • ✅ Single-table design for related entities with multiple access patterns
  • ✅ GSI overloading: one GSI serves multiple entity lookup patterns
  • ✅ Conditional writes for optimistic concurrency control
  • ✅ TTL for automatic session/cache/token expiry
  • ✅ Transactions for atomic multi-item updates
  • ❌ Never start with DynamoDB before identifying access patterns
  • ❌ Never scan large tables — redesign keys instead

External reference: Alex DeBrie Single-Table Design.

Recommended Reading

→ Designing Data-Intensive Applications — The bible of distributed systems and production engineering at scale.

→ The Pragmatic Programmer — Timeless engineering wisdom every senior developer needs.

Affiliate links. We earn a small commission at no extra cost to you.

Related on CheatCoders: DynamoDB single-table design, AWS Lambda best practices, and Bedrock Agents with production guardrails.

Free Weekly Newsletter

🚀 Join 2,000+ Senior Developers

Get expert-level JavaScript, Python, AWS, system design and AI secrets every week. Zero fluff, pure signal.

✓ No spam✓ Unsubscribe anytime✓ Expert-level only

Related: AWS Clickstream Analytics at Massive Scale: Kinesis, Lambda, Firehose, S3, Athena, and OpenSearch in Production · How to Build a Hyper-Scale Serverless API Platform on AWS with API Gateway, Lambda, DynamoDB, and CloudFront · How to Build a Real-Time Fraud Detection and Decisioning Architecture on AWS with EventBridge, Step Functions, and DynamoDB

Related: Production IoT Telemetry on AWS: Kinesis, Lambda, DynamoDB, S3, and Event-Driven Device Command Workflows · How to Build a High-Scale Product Search and Catalog Platform on AWS with OpenSearch, DynamoDB, SQS, and EventBridge · Designing an Active-Active Global Collaboration Platform on AWS with API Gateway, EKS, DynamoDB Global Tables, and EventBridge

Deep-dive PDF

Get the expanded guide for this post — extra diagrams-style checklists, failure modes, and a production walkthrough. Free when you subscribe to CheatCoders.

Already subscribed? or open the subscribe page.


Discover more from CheatCoders

Subscribe to get the latest posts sent to your email.

Comments

No comments yet. Why don’t you start the discussion?

Leave a comment

No account needed. Name and email are optional.