This chapter covers DynamoDB design patterns critical for the SAA-C03 exam, including table design, index strategies, and performance optimization. DynamoDB appears in approximately 10-15% of exam questions, often testing your ability to choose the right key structure, understand read/write capacity modes, and apply design patterns like single-table design. Mastering these patterns is essential for building scalable, cost-effective applications on AWS.
Jump to a section
Imagine a massive filing cabinet with millions of drawers, each drawer having a unique label (partition key). Inside each drawer, folders are sorted by a secondary label (sort key). To find a specific document, you must know the exact drawer label (partition key) — that gives you direct access to that drawer. If you know the folder label too (sort key), you can instantly grab the document. Without the drawer label, you'd have to search every drawer, which is extremely slow. The cabinet has a limited number of workers (read/write capacity units) that can access drawers per second. If too many requests hit the same drawer, the workers become overwhelmed and throttle requests. To avoid this, you can spread documents across many drawers (distribute partition keys) or add more workers (increase capacity). The cabinet also automatically splits overstuffed drawers into new ones (partition splitting) when they exceed 10GB. If you need to find all documents with a certain color tag (global secondary index), you can create a separate index that maps colors to drawer and folder locations, but this index consumes its own worker pool.
What is DynamoDB and Why Does It Exist?
Amazon DynamoDB is a fully managed NoSQL key-value and document database that delivers single-digit millisecond performance at any scale. It was designed to solve the limitations of traditional relational databases in handling massive, high-velocity workloads. Unlike RDS (which uses SQL and normalized schemas), DynamoDB is schema-less — each item (row) can have different attributes, making it ideal for applications with evolving data models, such as gaming leaderboards, session stores, and IoT telemetry.
How DynamoDB Works Internally
DynamoDB stores data across multiple partitions, each hosted on a separate storage node. The partition key (a hash attribute) is hashed using an internal hash function to determine which partition stores the item. This hash distribution ensures uniform load across partitions if the partition key has high cardinality (many unique values). Each partition can hold up to 10GB of data and support up to 3000 RCU or 1000 WCU (for reads and writes respectively). When a table exceeds these limits, DynamoDB automatically splits partitions.
Items within a partition are sorted by sort key (range attribute) in ascending order. This enables efficient range queries (e.g., all orders for a customer within a date range) using the Query API. The Query operation requires a partition key and optionally a sort key condition — it is the most efficient read pattern in DynamoDB.
Key Components and Defaults
Partition Key (PK): A single attribute that determines item location. Must be unique per item if no sort key is defined.
Sort Key (SK): Optional second attribute that sorts items within a partition. Enables hierarchical relationships and range queries.
Primary Key: Either a simple primary key (partition key only) or a composite primary key (partition key + sort key).
Local Secondary Index (LSI): An index with the same partition key as the table but a different sort key. Created only at table creation time. Shares RCU/WCU with the table.
Global Secondary Index (GSI): An index with a different partition and sort key. Can be created anytime. Has its own provisioned throughput.
Read Capacity Unit (RCU): One RCU allows one strongly consistent read per second for items up to 4KB, or two eventually consistent reads per second.
Write Capacity Unit (WCU): One WCU allows one write per second for items up to 1KB.
DynamoDB Streams: Captures item-level changes (create, update, delete) in near-real-time, with a 24-hour retention window.
Time to Live (TTL): Automatically deletes items after a specified timestamp, typically within 48 hours of expiration.
Configuration and Verification
To create a DynamoDB table with provisioned capacity:
aws dynamodb create-table \
--table-name Orders \
--attribute-definitions AttributeName=CustomerID,AttributeType=S AttributeName=OrderDate,AttributeType=S \
--key-schema AttributeName=CustomerID,KeyType=HASH AttributeName=OrderDate,KeyType=RANGE \
--provisioned-throughput ReadCapacityUnits=5,WriteCapacityUnits=5To query items:
aws dynamodb query \
--table-name Orders \
--key-condition-expression "CustomerID = :cid AND OrderDate BETWEEN :start AND :end" \
--expression-attribute-values '{ ":cid": {"S": "C123"}, ":start": {"S": "2024-01-01"}, ":end": {"S": "2024-12-31"} }'Design Patterns
#### Single-Table Design Instead of multiple normalized tables, store different entity types (e.g., users, orders, products) in one table using composite keys. For example, a user entity might have PK="USER#user123" and SK="PROFILE", while an order for that user might have PK="USER#user123" and SK="ORDER#order456". This allows fetching all data for a user with a single query (PK=USER#user123). This pattern reduces costs and complexity but requires careful access pattern analysis.
#### Adjacency List Pattern For graph-like relationships (e.g., social networks), store edges as items with the source node as PK and a composite SK that encodes the relationship type and target node. For example, PK="USER#user123", SK="FRIEND#user456". This enables querying all friends of a user with a single Query.
#### Sparse Index Pattern Use a GSI on an attribute that exists only for a subset of items (e.g., a 'status' attribute for active orders). This creates a sparse index that is smaller and faster, ideal for finding all active orders without scanning the entire table.
#### Write Sharding (Hot Partition Mitigation) If a single partition key receives too many writes (e.g., a popular product ID), add a random suffix (e.g., productID_1, productID_2) to distribute writes. Reads then need to query all shards and merge results. This pattern should be used sparingly as it complicates reads.
Interaction with Related Services
Lambda: Process DynamoDB Streams for real-time triggers (e.g., send welcome email on new user signup).
DAX (DynamoDB Accelerator): In-memory cache for read-heavy workloads, reducing latency to microseconds.
S3: Store large objects (e.g., images) in S3 and store the S3 URL in DynamoDB.
ElastiCache: Can be used alongside DynamoDB for session stores, but DAX is preferred for direct DynamoDB caching.
Performance and Limits
Maximum item size: 400KB.
Maximum partition size: 10GB.
Maximum number of tables: 256 per region (soft limit, can be increased).
Maximum number of GSIs per table: 20.
Maximum number of LSIs per table: 5.
Query result size limit: 1MB per call (use pagination).
Strongly consistent reads: only available if the replica has received all writes (latency ~1 second).
Eventually consistent reads: default, lower latency, may return stale data for up to 1 second.
Capacity Modes
Provisioned Capacity: Specify RCU and WCU. You pay for provisioned capacity regardless of usage. Suitable for predictable workloads. You can use Auto Scaling to adjust capacity automatically.
On-Demand Capacity: Pay per request. Suitable for unpredictable workloads. Can handle sudden spikes without throttling, but costs more per request than provisioned.
Throttling and Error Handling
When requests exceed provisioned capacity, DynamoDB returns a ProvisionedThroughputExceededException. The SDK automatically retries with exponential backoff. To avoid throttling, use burst capacity (accumulated up to 300 seconds of unused capacity) and implement retry logic. Hot partitions (uneven access) can cause throttling even if overall capacity is sufficient.
Exam Tips
Know the difference between Query and Scan: Query is efficient (uses PK), Scan reads entire table (expensive). Always design for Query patterns.
Understand that GSIs have their own throughput. If you query a GSI, its RCU/WCU consumption counts against the index, not the base table.
Remember that LSIs cannot be added after table creation. GSIs can.
For strongly consistent reads, you cannot use a GSI (GSIs only support eventually consistent reads).
TTL deletes are non-reversible and do not count against WCU.
Identify Access Patterns
Before designing a DynamoDB table, list all application queries (e.g., 'get user by ID', 'get orders for user by date', 'find products by category'). Each access pattern will dictate the primary key and index design. This step is crucial because DynamoDB is not a relational database; you cannot join tables or query on arbitrary attributes efficiently. For each pattern, determine the partition key (high cardinality, even access) and sort key (for ordering and range queries).
Design Composite Primary Key
Choose a partition key that distributes items evenly across partitions. For a user table, use UserID. For an orders table, use CustomerID. Add a sort key for ordering, e.g., OrderDate. Use composite keys for single-table design: e.g., PK='USER#user123', SK='ORDER#order456'. This allows hierarchical queries. Ensure the partition key has high cardinality (many unique values) to avoid hot partitions. Avoid using timestamps as partition keys because they create hot partitions (all writes go to the current time).
Create Indexes for Alternate Access
If you need to query by a different attribute (e.g., find user by email), create a GSI with email as partition key. If you need a different sort order on the same partition key (e.g., sort orders by status instead of date), create an LSI (must be done at table creation). Remember: GSIs consume separate throughput and support eventually consistent reads only. LSIs share throughput with the base table and support strongly consistent reads.
Choose Capacity Mode and Throughput
For predictable workloads, use provisioned capacity with Auto Scaling. Set minimum and maximum RCU/WCU based on peak traffic. For unpredictable workloads, use on-demand (pay per request). Monitor CloudWatch metrics (ConsumedReadCapacityUnits, ConsumedWriteCapacityUnits, ThrottledRequests) to adjust. Use burst capacity for short spikes. For hot partitions, consider write sharding or use on-demand to absorb spikes.
Implement TTL and Streams for Data Lifecycle
Enable TTL to automatically expire items (e.g., session data after 24 hours). TTL expiration is non-reversible and typically happens within 48 hours of the expiry timestamp. Enable DynamoDB Streams to capture changes for downstream processing (e.g., Lambda for real-time analytics). Streams have a 24-hour retention window; process events promptly to avoid data loss.
Enterprise Scenario 1: E-Commerce Order Management
A large retailer uses DynamoDB to store customer orders. The table has PK=CustomerID, SK=OrderDate. This allows quick retrieval of all orders for a customer sorted by date. However, during Black Friday, a single customer (e.g., a reseller) places thousands of orders, creating a hot partition. To mitigate, the team implements write sharding by appending a random suffix to CustomerID (e.g., CustomerID_1, CustomerID_2) for writes, but reads must query all shards and merge. Alternatively, they switch to on-demand capacity during the sale to handle spikes without throttling. They also use a GSI on OrderStatus to find all pending orders for fulfillment. The GSI has its own provisioned throughput, and they monitor to avoid throttling on the index.
Enterprise Scenario 2: Gaming Leaderboard
A gaming company stores player scores with PK=GameID, SK=Score (string padded to allow alphabetical sorting). To get the top 10 scores, they query with ScanIndexForward=false and limit=10. However, writes to the same GameID cause hot partitions. They use a write sharding technique: prepend a random number to the partition key (e.g., 1#GameID, 2#GameID) and then use a GSI with the original GameID as partition key and Score as sort key to read the leaderboard. This distributes writes while enabling efficient reads. They also use DAX to cache the top scores, reducing read load.
Common Pitfalls
Using a monotonically increasing value (e.g., timestamp) as partition key: all writes go to the last partition, causing throttling.
Over-provisioning RCU/WCU: leads to unnecessary costs. Use Auto Scaling or on-demand.
Not planning for GSI throughput: a GSI can throttle even if the base table has sufficient capacity. Monitor GSI throttling metrics.
Ignoring item size: each read/write consumes capacity based on item size (rounded up to 4KB for reads, 1KB for writes). Large items (e.g., 400KB) consume 100 RCU per strongly consistent read.
What SAA-C03 Tests on DynamoDB Design Patterns
The exam focuses on your ability to choose the correct table design and indexing strategy to meet application requirements. Key objective codes: 3.6 (High Performance) and related objectives on database selection (3.1). You will see scenario questions where you must select the most efficient DynamoDB design pattern.
Common Wrong Answers and Why Candidates Choose Them
Choosing a Scan over a Query: Candidates often select a Scan because they think it's simpler. However, Scans read every item and are expensive. The correct answer is to design a table with appropriate keys so that a Query can be used.
Using a Relational Data Model: Candidates new to NoSQL try to normalize data into multiple tables with foreign keys. DynamoDB does not support joins. The correct approach is single-table design with composite keys to store related data together.
Selecting LSIs for All Indexing Needs: LSIs are powerful but must be created at table creation time. Candidates often choose LSIs for new indexes on existing tables, which is impossible. The correct answer is to use GSIs for add-on indexes.
Ignoring Hot Partitions: When a scenario describes a popular item (e.g., a viral product), candidates provision high throughput but don't address the single partition bottleneck. The correct answer involves write sharding or using on-demand capacity.
Specific Numbers and Terms on the Exam
Item size limit: 400KB.
RCU: 1 RCU = 1 strongly consistent read per second for 4KB item; 2 eventually consistent reads.
WCU: 1 WCU = 1 write per second for 1KB item.
Maximum partition size: 10GB.
Maximum GSI per table: 20.
Maximum LSI per table: 5 (and only at creation).
DynamoDB Streams retention: 24 hours.
TTL expiry: typically within 48 hours.
Edge Cases and Exceptions
GSIs only support eventually consistent reads. If the scenario requires strongly consistent reads on an index, you must use an LSI or query the base table.
If a table has no sort key, you cannot create an LSI.
You cannot change the key schema after table creation. You must create a new table and migrate data.
DynamoDB Accelerator (DAX) is for read-heavy workloads and does not help with write throttling.
How to Eliminate Wrong Answers
If the question mentions 'multiple queries on different attributes', consider GSIs.
If the question says 'existing table' and needs a new index, eliminate LSIs.
If the question involves 'real-time processing of changes', look for DynamoDB Streams + Lambda.
If the question mentions 'low latency reads for a global user base', consider DAX or Global Tables.
Always design for Query patterns; avoid Scan whenever possible.
Use single-table design with composite keys to store related entities together.
Partition keys must have high cardinality to avoid hot partitions.
LSIs must be defined at table creation; GSIs can be added later.
GSIs only support eventually consistent reads.
One RCU = 1 strongly consistent read per second for 4KB; one WCU = 1 write per second for 1KB.
DynamoDB item size limit is 400KB.
TTL expiration is non-reversible and typically occurs within 48 hours.
DynamoDB Streams retain data for 24 hours.
Use DAX for read-heavy workloads requiring microsecond latency.
These come up on the exam all the time. Here's how to tell them apart.
Local Secondary Index (LSI)
Same partition key as base table.
Different sort key.
Created only at table creation.
Shares throughput with base table.
Supports strongly consistent reads.
Global Secondary Index (GSI)
Different partition key (and sort key) from base table.
Can be created anytime.
Has its own provisioned throughput.
Only supports eventually consistent reads.
Maximum 20 per table.
Mistake
DynamoDB can join tables like SQL databases.
Correct
DynamoDB is a NoSQL database and does not support joins. You must design tables to store related data together (single-table design) or perform joins in application code.
Mistake
LSIs can be added to any existing table.
Correct
LSIs can only be created at table creation time. After creation, you cannot add or modify LSIs. For new indexes on existing tables, you must use GSIs.
Mistake
Scan is an efficient way to retrieve items.
Correct
Scan reads every item in a table or index, consuming large amounts of throughput. Always prefer Query (which uses partition key) for efficient data retrieval.
Mistake
Provisioned throughput guarantees no throttling.
Correct
Even with sufficient overall throughput, a hot partition (uneven access) can cause throttling. Ensure partition key has high cardinality and even access distribution.
Mistake
GSIs support strongly consistent reads.
Correct
GSIs only support eventually consistent reads. For strongly consistent reads, you must query the base table. LSIs on the base table support strongly consistent reads.
Reveal each answer, then mark whether you got it right. Score 60%+ to unlock the next chapter.
An LSI has the same partition key as the base table but a different sort key, and it must be created at table creation. It shares throughput with the base table and supports strongly consistent reads. A GSI can have a different partition key and sort key, can be created anytime, has its own throughput, and only supports eventually consistent reads. Use LSIs when you need alternative sort orders on the same partition key and strongly consistent reads; use GSIs for querying on different attributes.
To avoid hot partitions, ensure your partition key has high cardinality (many unique values) and that access is evenly distributed. Avoid using timestamps or monotonically increasing values as partition keys. If a single key is inherently hot (e.g., a popular product), consider write sharding by appending a random suffix to the partition key for writes, and then use a GSI for reads. Alternatively, switch to on-demand capacity to absorb spikes.
No, LSIs can only be created when the table is created. If you need a new index on an existing table, you must use a GSI. To add an LSI to an existing table, you would need to create a new table with the LSI and migrate the data.
The maximum item size in DynamoDB is 400KB, including attribute names and values. If you need to store larger objects, store them in Amazon S3 and store the S3 URL in DynamoDB.
TTL (Time to Live) allows you to define a per-item timestamp attribute (in epoch format) after which the item is automatically deleted. Deletion is non-reversible and typically occurs within 48 hours of the expiry time. TTL deletes do not consume write capacity. Use TTL for expiring session data, logs, or temporary records.
DAX is an in-memory cache for DynamoDB that provides microsecond latency for read-heavy workloads. It is fully managed and sits between your application and DynamoDB. Use DAX when you need extremely low latency reads (e.g., gaming leaderboards, real-time bidding) and your application can tolerate eventual consistency. DAX does not help with write performance.
Yes, DynamoDB supports ACID transactions (TransactGetItems and TransactWriteItems) that can span multiple tables within the same AWS region. Transactions provide atomicity, consistency, isolation, and durability. However, they consume twice the throughput (two RCU/WCU per operation) and have a maximum of 25 items or 4MB per transaction.
You've just covered DynamoDB Design Patterns — now see how well it sticks with free SAA-C03 practice questions. Full explanations included, no account needed.
Done with this chapter?