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