DynamoDB is a fully managed database in AWS that is incredibly scalable, highly available with simple support for encryption and cross-region replication. In addition it has many quality of life features
- Time-to-live support to remove old data when it’s no longer needed
- Streams and event-driven integration with lambda
- Point-in-time-recovery (PITR) with per-second granularity to restore the database to a specific time
While dynamo db has many great features it comes with many tradeoffs to consider before choosing to use it over alternative options.
- query patterns must be known up front to drive the data model
- relationships between data can be difficult to model
- changing the data model is difficult and time consuming
- ad-hoc queries are slow and expensive
- string consistency not always available (ie in global secondary indexes)
- complex queries are difficult or impossible to implement
If a use-case requires massive scale, DynamoDB is great choice but be prepared for difficult trade offs.
Single Table Design
If DynamoDB is the right choice, single table design is critical to represent relationships in data and provide the most efficient queries.
Partition Key and Sort Key
Understanding the partition key (PK) and sort key (SK) is the first step to learning single table design. Whether a table contains only a PK or both PK and SK is defined when the table is created.
Partition Key (PK): Determines which physical partition the data will be written to. The goal is high cardinality to distribute the data. The PK will be used to group entities or aggregate of related data Sort Key (SK): How data is ordered with the partition. This will allow representing relationships and give query flexibility
PK & SK Format
DynamoDB tables created with both a PK and SK results in a composite key, the combination of both PK and SK, for the primary key to uniquely identify a record in the database.
A common convention to help identify records and simplify understanding queries is to prefix the PK and SK with a short description of the entity being represented followed by a value.
PK
USER#12345
PRODUCT#6789
SK
ORDER2026-07-03
CONTACT#6543
Creating Relationships
Establishing relationships between data can be accomplished through a combination of PK and SK. Imagine there are products and each product has many parts
| PK | SK | Name | Description | Price |
|---|---|---|---|---|
| PROUDCT#12345 | PRODUCT | Coffee Pot | Brew coffee | $50 |
| PK | SK | Name | Supplier | Weight |
|---|---|---|---|---|
| PRODUCT#12345 | PART#1 | Carafe | Glass Manufacturer | 10 oz |
| PRODUCT#12345 | PART#2 | Water basin | Plastics Manufacturer | 4 oz |
| PRODUCT#12345 | PART#3 | Filter | Filter Manufacturer | .25 oz |
| PRODUCT#12345 | PART#4 | Ground Coffee | Farmer | 16 oz |
Querying the relationships
How to query for the product
1import { DynamoDBClient } from "@aws-sdk/client-dynamodb";
2import { DynamoDBDocumentClient, GetCommand } from "@aws-sdk/lib-dynamodb";
3
4const client = new DynamoDBClient({});
5const docClient = DynamoDBDocumentClient.from(client);
6
7export const main = async () => {
8 const command = new GetCommand({
9 TableName: "MyCompany",
10 Key: {
11 PK: "PRODUCT#12345",
12 SK: "PRODUCT"
13 },
14 });
15
16 const response = await docClient.send(command);
17 console.log(response);
18 return response;
19};
How to query for parts
1import { DynamoDBClient } from "@aws-sdk/client-dynamodb";
2import { DynamoDBDocumentClient, GetCommand } from "@aws-sdk/lib-dynamodb";
3
4const client = new DynamoDBClient({});
5const docClient = DynamoDBDocumentClient.from(client);
6
7export const main = async () => {
8 const command = new GetCommand({
9 TableName: "MyCompany",
10 KeyConditionExpression:
11 "#pkName = :pkValue AND begins_with(#skName,:skValue",
12 ExpressionAttributeNames: {
13 ":pkName": "PK",
14 ":skName": "SK",
15 },
16 ExpressionAttributeValues: {
17 ":pkValue": "PRODUCT#12345",
18 ":skValue": "PART#",
19 },
20 });
21
22 const response = await docClient.send(command);
23 console.log(response);
24 return response;
25};