DynamoDB rewards designing around your access patterns up front and punishes designing it like a relational database — no JOINs, no flexible ad-hoc queries, and a primary key structure that has to be decided correctly before you have much data in the table at all.
4 min read
A relational database schema starts from "what are the entities and their relationships" (users, orders, products) and lets you query them flexibly afterward with JOINs and WHERE clauses. DynamoDB inverts this: you design the table starting from the specific queries your application will actually run, because the key structure you choose determines what's efficiently queryable — and unlike RDS, there's no JOIN to fall back on if you guessed wrong.
Every DynamoDB table has a partition key (required) and optionally a sort key, together forming the primary key:
Table: Orders
Partition key: customerId
Sort key: orderDate
customerId=alice, orderDate=2024-01-15 -> {order details}
customerId=alice, orderDate=2024-03-02 -> {order details}
customerId=bob, orderDate=2024-02-10 -> {order details}
A query for customerId = "alice" efficiently returns all of Alice's orders, sorted by date, without scanning the whole table — because DynamoDB physically partitions data by the partition key, and sorts within each partition by the sort key.
# Query — efficient, uses the partition key directly
response = table.query(
KeyConditionExpression=Key('customerId').eq('alice')
)
# Scan — reads the entire table, then filters; avoid this at any real scale
response = table.scan(
FilterExpression=Attr('customerId').eq('alice')
)A table that works fine in development with 50 items and falls over in production with 5 million is, overwhelmingly often, a table being Scanned instead of Queried — the access pattern that looked fine at small scale was never actually efficient, it just wasn't big enough yet to expose the cost.
A Global Secondary Index (GSI) lets you query by a different attribute than the table's primary partition/sort key, at the cost of additional storage and (for provisioned-capacity tables) its own throughput:
resource "aws_dynamodb_table" "orders" {
name = "Orders"
billing_mode = "PAY_PER_REQUEST"
hash_key = "customerId"
range_key = "orderDate"
attribute {
name = "customerId"
type = "S"
}
attribute {
name = "orderDate"
type = "S"
}
attribute {
name = "status"
type = "S"
}
global_secondary_index {
name = "StatusIndex"
hash_key = "status"
projection_type = "ALL"
}
}This adds an efficient "find all orders with status = X, across all customers" query path, without which that same query would require a full table Scan.
PAY_PER_REQUEST): pay per actual request, scales automatically with no capacity planning — simpler and safer for unpredictable or new workloads, generally more expensive per-request at sustained high volume.This is the same on-demand-vs-reserved trade-off from the Cloud Computing domain's pricing-models lesson, applied specifically to DynamoDB's own capacity model: start on-demand while traffic patterns are unknown, consider provisioned capacity once usage is predictable enough to forecast confidently.
DynamoDB fits well when: access patterns are known and stable, data is naturally key-value or hierarchical (a user's own data, a session store, a shopping cart), and the workload needs to scale to very high request volumes with predictable low latency. RDS fits better when: the data genuinely needs flexible, ad-hoc querying (reporting, analytics with unpredictable filter combinations), relationships between entities are complex and JOIN-shaped, or the team's query patterns are still actively evolving and locking in a key structure prematurely would be costly to redo.
Check your understanding
A quick comprehension check — not tracked, not graded, just for you.
1. How does DynamoDB's design approach differ from a relational database's?
2. Why can a Scan operation cause a DynamoDB table to fail at scale even when it worked fine in development?
3. What does a Global Secondary Index (GSI) allow in DynamoDB?
4. When does RDS remain a better fit than DynamoDB, according to this lesson?
AWS