DynamoDB Single Table Design

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};