Amazon DynamoDB is a fully managed NoSQL database designed to deliver single-digit millisecond latency at any scale. While traditional relational databases use separate tables for each entity type, high-performance DynamoDB applications utilize Single-Table Design. By placing all entity types in a single table, you can retrieve complex hierarchical structures in a single query request.
This reference sheet covers PK/SK naming conventions, primary access patterns, Global Secondary Indexes (GSIs), transactional operations, and modeling rules.
Before diving into this cheatsheet, check out my previous deep-dive on AWS IAM Policies & Boundaries Cheatsheet: The Complete Reference to see how we structured these patterns in practice.
Designing Keys & Naming Conventions
Single-table design relies on generic key names (such as PK and SK) because different item types share these same attributes.
Table Structure:
--------------------------------------------------------------------------------------
| PK (String) | SK (String) | Type (String) | Attribute Data |
--------------------------------------------------------------------------------------
| USER#usr_101 | METADATA | User | name="Arjun", ... |
| USER#usr_101 | ORDER#ord_5001 | Order | total=150.00, ... |
| USER#usr_101 | ORDER#ord_5002 | Order | total=45.00, ... |
| PRODUCT#prod_800 | METADATA | Product | price=99.99, ... |
--------------------------------------------------------------------------------------
Primary Query Access Patterns
Using the key setup above, you can fetch diverse datasets in a single round-trip.
1. Fetch a User Profile
import boto3
from boto3.dynamodb.conditions import Key
dynamodb = boto3.resource('dynamodb')
table = dynamodb.Table('meshworld-production-ecom')
# Retrieve only the metadata details of a specific user
response = table.query(
KeyConditionExpression=Key('PK').eq('USER#usr_101') & Key('SK').eq('METADATA')
)
user_profile = response.get('Items')
2. Fetch a User and All of Their Orders
This pattern is known as a Pre-Joined Query. It returns one User metadata item and multiple Order items together.
# Retrieve the user profile along with all associated order history
response = table.query(
KeyConditionExpression=Key('PK').eq('USER#usr_101') & Key('SK').begins_with('ORDER#')
)
user_and_orders = response.get('Items')
Using Global Secondary Indexes (GSIs)
Global Secondary Indexes allow you to query your data using alternative partition keys and sort keys.
1. Inverting Keys (GSI1)
To find a user profile by email (where email is normally inside the METADATA attributes), project that attribute into GSI1PK and GSI1SK.
Index Mapping:
--------------------------------------------------------------------------------------
| Item Type | PK | SK | GSI1PK (Email) | GSI1SK (Type) |
--------------------------------------------------------------------------------------
| User | USER#usr_101 | METADATA | EMAIL#arjun@mesh.in | USER |
--------------------------------------------------------------------------------------
2. Querying the GSI
# Query the GSI to find a user by their email address
response = table.query(
IndexName='GSI1',
KeyConditionExpression=Key('GSI1PK').eq('EMAIL#arjun@mesh.in') & Key('GSI1SK').eq('USER')
)
user_by_email = response.get('Items')
Implementing ACID Transactions
DynamoDB supports transactional operations that allow you to modify or read up to 100 items atomically. This is critical for operations like order checkout where balance deductions and inventory updates must succeed or fail together.
client = boto3.client('dynamodb')
# Execute atomic updates across multiple keys
try:
response = client.transact_write_items(
TransactItems=[
# 1. Update stock levels on target product
{
'Update': {
'TableName': 'meshworld-production-ecom',
'Key': {
'PK': {'S': 'PRODUCT#prod_800'},
'SK': {'S': 'METADATA'}
},
'UpdateExpression': 'SET stock = stock - :qty',
'ConditionExpression': 'stock >= :qty',
'ExpressionAttributeValues': {
':qty': {'N': '1'}
}
}
},
# 2. Record the new order receipt
{
'Put': {
'TableName': 'meshworld-production-ecom',
'Item': {
'PK': {'S': 'USER#usr_101'},
'SK': {'S': 'ORDER#ord_5003'},
'Type': {'S': 'Order'},
'total': {'N': '99.99'}
}
}
}
]
)
print("Transaction completed successfully!")
except client.exceptions.TransactionCanceledException as e:
print("Transaction cancelled due to failed check conditions:", e)
Single-Table Design Best Practices
To build scalable DynamoDB single-table models, follow these rules:
- Avoid Hot Partitions: Do not place high-write items under the same Partition Key. Spread operations using write-sharding (e.g. appending a random suffix
#0to#9to partition keys) to avoid throughput bottlenecks. - Keep Items under 400KB: The maximum size of a single DynamoDB item (attributes plus key names) is 400KB. Keep large binary datasets or long logs in Amazon S3 and store the URL reference inside DynamoDB.
- Use Sparse Indexes: If a GSI key is not present on an item, DynamoDB does not write that item to the index. You can leverage this to create a “Sparse Index” that only tracks a small subset of your items, keeping storage costs low.
--- Related Articles
Deepen your understanding with these curated continuations.
uv Cheat Sheet: The Fast Python Package Manager (2026)
Complete uv reference — install packages, manage projects, run scripts, pin Python versions, and migrate from pip, poetry, and pyenv with working commands.
Warp Terminal Cheat Sheet: Shortcuts, Workflows & AI Features
Complete Warp reference — block-based terminal, command palette, workflow commands, AI autocomplete, custom themes, and team features in 2026.
React Hooks Cheat Sheet: All Hooks with React 19
Complete React Hooks reference — useState, useEffect, useMemo, useCallback, custom hooks, and React 19's new use(), useOptimistic, and useActionState.