DynamoDB Keys and Indexes
Complete the full lesson to earn 25 points
Work through each section, then tap “Mark as Complete” on the last one.
✦ Skip the page breaks and see fewer ads — read each lesson on a single page with Pro
Mastering DynamoDB Keys and Indexes: Building Scalable Data Models
Introduction: Why Keys and Indexes Matter in DynamoDB
When you transition from traditional relational databases (like MySQL or PostgreSQL) to a NoSQL solution like Amazon DynamoDB, the most significant shift in your thinking process revolves around data access patterns. In a relational database, you often define your schema based on the structure of the data itself, assuming you can write complex JOIN queries later to retrieve what you need. DynamoDB, however, requires you to design your tables specifically around the queries you intend to perform. This is where primary keys and secondary indexes become the most vital components of your architecture.
Understanding DynamoDB keys and indexes is not just a technical requirement; it is a fundamental design philosophy. If you get your key structure wrong, you will find yourself unable to perform efficient lookups, leading to expensive "scan" operations that can degrade performance and inflate your monthly AWS bill. Conversely, a well-designed indexing strategy allows your application to handle massive scale while maintaining consistent, single-digit millisecond latency, regardless of how much data resides in your table.
In this lesson, we will dissect the anatomy of DynamoDB keys, explore the differences between various index types, and provide a clear roadmap for designing high-performance data models. Whether you are building a simple user profile service or a complex real-time analytics platform, mastering these concepts will provide you with the foundation needed to build reliable, cost-effective, and fast cloud-native applications.
The Anatomy of the Primary Key
Every DynamoDB table must have a primary key. This key is the unique identifier for every item in your table, and it is the primary way that DynamoDB organizes data internally. There are two distinct types of primary keys you can choose from when creating a table, and your choice here determines how you can query your data for the life of that table.
1. Partition Key (Simple Primary Key)
The partition key, often referred to as the hash key, is a single attribute that serves as the unique identifier for an item. DynamoDB uses the value of this attribute as input to an internal hash function, and the output of that function determines the physical location (the partition) where the item is stored. If you use a simple primary key, you can only retrieve items by providing the exact value of this partition key.
2. Composite Primary Key (Partition Key + Sort Key)
A composite primary key consists of two attributes: the partition key and the sort key (or range key). While the partition key determines the physical location of the data, the sort key allows you to store multiple items with the same partition key together. Within a single partition, DynamoDB stores all items with the same partition key physically adjacent to each other, sorted by the value of the sort key. This design is incredibly powerful because it enables you to perform complex queries like "get all orders for a specific user" or "get the most recent log entries for a specific server."
Callout: Partition Key vs. Sort Key Think of the Partition Key as the "cabinet" where data is stored, and the Sort Key as the "folder" inside that cabinet. If you only have a cabinet, you can only grab the whole cabinet. If you have a folder inside, you can grab a specific set of documents without needing to look at everything else in the cabinet. Always choose a Partition Key with high cardinality to ensure data is spread evenly across your cluster.
Designing Effective Partition Keys
Choosing the right partition key is the single most important decision you will make for a DynamoDB table. If you choose a key that does not have enough unique values, you create "hot partitions." A hot partition occurs when one partition receives significantly more traffic than others, leading to throttling errors.
To avoid this, you should select an attribute that has high cardinality. For example, if you are building an e-commerce platform, do not use Status (e.g., "Pending", "Shipped") as a partition key, because there are very few possible values. Instead, use UserID or OrderID, which have millions of unique possibilities. This ensures that your data is distributed across the entire DynamoDB fleet, allowing the database to scale horizontally as your user base grows.
Practical Example: User Activity Tracking
Imagine you are tracking user activity across your application. Your table might look like this:
- Partition Key:
UserID(String) - Sort Key:
Timestamp(String/Number)
With this structure, you can easily query all activities for a specific user within a specific time range. By using a composite key, you avoid the bottleneck of a single massive table and ensure that queries for a specific user's history are lightning-fast.
Leveraging Sort Keys for Advanced Access Patterns
The sort key is your best friend when it comes to filtering and sorting data. Because DynamoDB stores items with the same partition key in sorted order, you can use operators like begins_with, between, >, <, and = to retrieve subsets of data efficiently.
Using Hierarchical Data
One advanced technique is to use the sort key to represent hierarchies. For instance, if you are storing data about a file system, your partition key could be the FolderID, and your sort key could be the FilePath. If you want to list everything inside a folder, you can query by FolderID and use a begins_with condition on the FilePath.
The "One-to-Many" Pattern
A common mistake is trying to store a one-to-many relationship in a single item using a list or set. While DynamoDB supports these types, they are difficult to query. Instead, use the composite key approach:
- Parent Item: Partition Key =
OrderID, Sort Key =METADATA - Child Item: Partition Key =
OrderID, Sort Key =ITEM_#001 - Child Item: Partition Key =
OrderID, Sort Key =ITEM_#002
By using a prefix in the sort key, you can separate metadata from line items while keeping them linked to the same partition key.
Note: Sort Key Prefixing Using prefixes like
USER#,ORDER#, orMETADATA#in your sort keys is a standard industry practice. It helps you distinguish between different types of items that share the same partition key and makes your code more readable when parsing the results.
Secondary Indexes: Expanding Your Query Capabilities
Sometimes, the primary key design that works for your application's primary function is not sufficient for secondary needs. For example, you might need to look up users by Email even if their UserID is the primary key. This is where Secondary Indexes come into play.
Local Secondary Indexes (LSI)
An LSI allows you to query data using the same partition key as the base table, but a different sort key.
- Constraints: You must define LSIs at table creation time.
- Capacity: They share the provisioned throughput of the base table.
- Use Case: Use an LSI when you need to sort the same partition key data in a different way (e.g., sorting user orders by
Datevs. sorting them byTotalAmount).
Global Secondary Indexes (GSI)
A GSI is much more flexible. It allows you to define an entirely new partition key and (optionally) a new sort key.
- Independence: GSIs are asynchronous; DynamoDB replicates data from the base table to the GSI in the background.
- Flexibility: You can add or remove GSIs at any time after the table is created.
- Use Case: Use a GSI when you need to query the data by a completely different attribute, such as looking up an order by
CustomerEmailinstead ofOrderID.
Warning: GSI Eventual Consistency Because GSIs are updated asynchronously, they are eventually consistent. If you write an item to the base table and immediately query the GSI, you might not see the update for a few milliseconds. If your application requires strict read-after-write consistency, do not rely on a GSI for that specific operation.
Code Example: Querying with Indexes
Let's look at how you would perform these queries using the AWS SDK for JavaScript (v3).
Querying the Base Table
import { DynamoDBClient } from "@aws-sdk/client-dynamodb";
import { DynamoDBDocumentClient, QueryCommand } from "@aws-sdk/lib-dynamodb";
const client = new DynamoDBClient({});
const docClient = DynamoDBDocumentClient.from(client);
// Fetching all orders for a specific user
const command = new QueryCommand({
TableName: "OrdersTable",
KeyConditionExpression: "UserID = :uid AND Timestamp > :start",
ExpressionAttributeValues: {
":uid": "user_123",
":start": "2023-01-01T00:00:00Z"
}
});
const response = await docClient.send(command);
Querying a Global Secondary Index
// Fetching orders by Email via a GSI
const gsiCommand = new QueryCommand({
TableName: "OrdersTable",
IndexName: "EmailIndex",
KeyConditionExpression: "Email = :email",
ExpressionAttributeValues: {
":email": "[email protected]"
}
});
const gsiResponse = await docClient.send(gsiCommand);
Best Practices for Index Management
1. Project Only Necessary Attributes
When you create an index, you have the option to project all attributes, only keys, or a specific subset of attributes. Projecting all attributes into a GSI can be expensive because it effectively doubles the amount of storage and write throughput required. If you only need a few fields for your query, project only those fields.
2. Monitor GSI Throttling
A GSI has its own throughput settings (or uses On-Demand capacity). If your GSI is throttled, it does not just affect the query—it can actually throttle writes to your base table if the GSI cannot keep up with the replication stream. Always monitor the ConsumedWriteCapacity of your GSIs.
3. Avoid "Sparse" Indexes for High-Traffic Queries
A sparse index is an index that only contains items where the index key exists. This is useful for finding "Pending" items in a massive table of "Completed" items. However, be careful not to rely on sparse indexes for critical path operations if your data distribution is uneven, as it can make capacity planning difficult.
4. Use TTL (Time To Live)
If you are using sort keys for time-series data, enable DynamoDB TTL. This allows DynamoDB to automatically delete expired items at no cost, which prevents your tables from growing indefinitely and keeps your indexes lean.
Comparison Table: LSI vs. GSI
| Feature | Local Secondary Index (LSI) | Global Secondary Index (GSI) |
|---|---|---|
| Partition Key | Must be same as base table | Can be different |
| Sort Key | Can be different | Can be different |
| Creation Time | Only at table creation | Any time |
| Consistency | Strong or Eventual | Eventual only |
| Throughput | Shares base table capacity | Independent capacity |
Common Pitfalls and How to Avoid Them
Pitfall 1: The "Scan" Trap
Many developers start by using Scan because it is easy to write. They think, "I'll just scan the whole table and filter in my code." This is the fastest way to hit performance limits and run up costs.
- The Fix: Always design your keys and indexes so that you can retrieve data using
QueryorGetItem. If you find yourself scanning, it is a sign that your data model needs to be refactored.
Pitfall 2: Over-indexing
It is tempting to create a GSI for every possible filter. However, every index you create adds overhead to every PutItem, UpdateItem, and DeleteItem operation.
- The Fix: Only create indexes for queries that are truly necessary for your application's core functionality. If a query is only run once a month by an admin, consider using Amazon Athena to run that query against a S3 export of your data instead of indexing it in DynamoDB.
Pitfall 3: Ignoring Key Cardinality
Choosing a non-unique attribute (like Gender or Country) as a partition key will inevitably lead to hotspots.
- The Fix: If you must partition by a low-cardinality attribute, use "write sharding." Append a random number to the partition key (e.g.,
US_1,US_2,US_3) to distribute the load across multiple partitions, and then query all shards when you need to retrieve the data.
Step-by-Step: Designing Your Access Patterns
To design your DynamoDB schema, follow this iterative process:
- List your queries: Before writing any code, write down every single query your application needs to perform. Be specific (e.g., "Get user by ID", "Get top 10 orders by date", "Get all items in a category").
- Define the Base Table: Identify the most common or most important query. This will become your primary key.
- Identify GSI/LSI Needs: For the remaining queries, determine if they can be satisfied by the base table. If not, map them to a GSI or LSI.
- Refine Attributes: Decide which attributes need to be projected into your indexes.
- Review and Test: Use the AWS NoSQL Workbench to visualize your data model and test your access patterns before deploying any infrastructure.
Quick Reference: Key Concepts
- Partition Key: The "Hash" key. Determines the physical partition. Required for all tables.
- Sort Key: The "Range" key. Allows for sorting and range queries within a partition. Optional.
- Query vs. Scan:
Queryis targeted and efficient;Scanis broad and inefficient. - LSI: Best for sorting the same partition key in different ways.
- GSI: Best for querying by different attributes entirely.
- High Cardinality: A requirement for partition keys to ensure even data distribution.
- Eventual Consistency: The standard model for GSI reads; keep this in mind for real-time app requirements.
Conclusion: Key Takeaways
- Design for Query Patterns: Unlike relational databases, DynamoDB requires you to know your access patterns upfront. Design your keys to match your most frequent queries.
- The Power of Composite Keys: Using a partition key combined with a sort key unlocks the ability to perform range-based queries, which is essential for most modern applications.
- Choose High Cardinality: Always select partition key attributes that are unique and varied to prevent "hot partitions" and ensure your database can scale linearly.
- Use GSIs Strategically: Global Secondary Indexes are powerful tools for enabling flexible lookups, but they come with costs in terms of write throughput and storage. Use them only when necessary.
- Avoid Scans at All Costs: Scans are the enemy of performance and cost efficiency. If you are scanning, rethink your indexing strategy.
- Embrace Eventual Consistency: Understand that GSI updates are asynchronous. Design your application logic to handle the slight delay between a write and the index update.
- Iterate and Optimize: Your data model is not set in stone. Use tools like NoSQL Workbench to model, test, and refine your structure as your application's needs evolve over time.
By keeping these principles in mind, you will move from simply "using" DynamoDB to architecting systems that are truly resilient and performant. Remember that in the world of NoSQL, the structure of your data is the most important code you will ever write. Spend the time upfront to get the keys and indexes right, and your future self will thank you when your application handles millions of requests with ease.
Enjoying the courses?
Everything stays free. Pro shows fewer ads, doubles your daily points limit so you progress twice as fast, and lets you read each lesson on one page.
- ✓ Fewer advertisements
- ✓ 2× daily points limit
- ✓ Distraction-free lessons