Amazon DynamoDB: NoSQL Database Design

Table of Contents

Background

DynamoDB is a fully managed NoSQL database service from AWS that provides fast and predictable performance with seamless scalability.

Key Concepts

Concept Description
Partition Key Primary key for data distribution
Sort Key Optional secondary key for range queries
GSI Global Secondary Index for alternate queries
LSI Local Secondary Index (same partition)
RCU/WCU Read/Write Capacity Units for throughput

Creating a Table

import boto3

dynamodb = boto3.resource('dynamodb')

table = dynamodb.create_table(
    TableName='Users',
    KeySchema=[
        {'AttributeName': 'pk', 'KeyType': 'HASH'},   # Partition key
        {'AttributeName': 'sk', 'KeyType': 'RANGE'}   # Sort key
    ],
    AttributeDefinitions=[
        {'AttributeName': 'pk', 'AttributeType': 'S'},
        {'AttributeName': 'sk', 'AttributeType': 'S'},
        {'AttributeName': 'email', 'AttributeType': 'S'}
    ],
    GlobalSecondaryIndexes=[
        {
            'IndexName': 'email-index',
            'KeySchema': [{'AttributeName': 'email', 'KeyType': 'HASH'}],
            'Projection': {'ProjectionType': 'ALL'}
        }
    ],
    BillingMode='PAY_PER_REQUEST'
)

Single-Table Design

Modern DynamoDB design uses a single table with composite keys:

# User record
{
    'pk': 'USER#12345',
    'sk': 'PROFILE',
    'name': 'Alice',
    'email': 'alice@example.com'
}

# User's order
{
    'pk': 'USER#12345',
    'sk': 'ORDER#2024-01-15#001',
    'total': 99.99,
    'status': 'shipped'
}

# Query all orders for a user
response = table.query(
    KeyConditionExpression='pk = :pk AND begins_with(sk, :prefix)',
    ExpressionAttributeValues={
        ':pk': 'USER#12345',
        ':prefix': 'ORDER#'
    }
)

CRUD Operations

# Put item
table.put_item(Item={'pk': 'USER#1', 'sk': 'PROFILE', 'name': 'Bob'})

# Get item
response = table.get_item(Key={'pk': 'USER#1', 'sk': 'PROFILE'})
item = response.get('Item')

# Update item
table.update_item(
    Key={'pk': 'USER#1', 'sk': 'PROFILE'},
    UpdateExpression='SET #n = :name',
    ExpressionAttributeNames={'#n': 'name'},
    ExpressionAttributeValues={':name': 'Robert'}
)

# Delete item
table.delete_item(Key={'pk': 'USER#1', 'sk': 'PROFILE'})

Best Practices

  • Design for access patterns, not entities
  • Use single-table design when possible
  • Avoid hot partitions with key design
  • Use sparse indexes to reduce costs
  • Enable point-in-time recovery for production

Resources

Author: Jason Walsh

j@wal.sh

Last Updated: 2025-12-21 23:02:55

build: 2026-01-11 18:39 | sha: eb805a8