DynamoDB Capacity
Understanding and managing DynamoDB throughput, scaling, and cost optimization
DynamoDB capacity determines how much read and write throughput your table can handle. Understanding capacity modes is essential for performance and cost optimization.
Capacity Modes
Two Pricing Models
- On-Demand: Pay per request, automatic scaling
- Provisioned: Pre-allocated capacity, predictable costs
| Feature | On-Demand | Provisioned |
|---|---|---|
| Scaling | Automatic | Manual or Auto Scaling |
| Pricing | Per request | Per hour (capacity units) |
| Minimum | None | 1 RCU, 1 WCU |
| Best for | Variable/unknown traffic | Predictable workloads |
| Reserved capacity | Not available | Up to 77% discount |
Capacity Units
Read Capacity Units (RCU)
| Read Type | Calculation |
|---|---|
| Strongly consistent | 1 RCU = 1 read of up to 4 KB |
| Eventually consistent | 1 RCU = 2 reads of up to 4 KB |
| Transactional | 2 RCUs = 1 read of up to 4 KB |
Formula:
RCUs = (Item Size in KB / 4) × Reads per Second × Consistency Factor
Strong: (8 KB / 4) × 100 reads/sec × 1 = 200 RCUs
Eventually: (8 KB / 4) × 100 reads/sec × 0.5 = 100 RCUsWrite Capacity Units (WCU)
| Write Type | Calculation |
|---|---|
| Standard | 1 WCU = 1 write of up to 1 KB |
| Transactional | 2 WCUs = 1 write of up to 1 KB |
Formula:
WCUs = (Item Size in KB) × Writes per Second
Standard: 2 KB × 50 writes/sec = 100 WCUs
Transactional: 2 KB × 50 writes/sec × 2 = 200 WCUsOn-Demand Mode
aws dynamodb create-table \
--table-name Products \
--attribute-definitions \
AttributeName=productId,AttributeType=S \
--key-schema \
AttributeName=productId,KeyType=HASH \
--billing-mode PAY_PER_REQUESTaws dynamodb update-table \
--table-name Products \
--billing-mode PAY_PER_REQUESTYou can switch between modes once every 24 hours.
When to use on-demand:
- New applications with unknown traffic
- Variable or spiky workloads
- Development and testing environments
- Infrequent, unpredictable access patterns
Cost considerations:
- ~5x more expensive per request than provisioned
- No reserved capacity discounts
- Still cost-effective for variable workloads
Provisioned Mode
aws dynamodb create-table \
--table-name Products \
--attribute-definitions \
AttributeName=productId,AttributeType=S \
--key-schema \
AttributeName=productId,KeyType=HASH \
--billing-mode PROVISIONED \
--provisioned-throughput \
ReadCapacityUnits=100,WriteCapacityUnits=50aws dynamodb update-table \
--table-name Products \
--provisioned-throughput \
ReadCapacityUnits=200,WriteCapacityUnits=100# Read capacity
aws application-autoscaling register-scalable-target \
--service-namespace dynamodb \
--resource-id "table/Products" \
--scalable-dimension dynamodb:table:ReadCapacityUnits \
--min-capacity 10 \
--max-capacity 1000
# Write capacity
aws application-autoscaling register-scalable-target \
--service-namespace dynamodb \
--resource-id "table/Products" \
--scalable-dimension dynamodb:table:WriteCapacityUnits \
--min-capacity 5 \
--max-capacity 500aws application-autoscaling put-scaling-policy \
--service-namespace dynamodb \
--resource-id "table/Products" \
--scalable-dimension dynamodb:table:ReadCapacityUnits \
--policy-name ProductsReadScaling \
--policy-type TargetTrackingScaling \
--target-tracking-scaling-policy-configuration '{
"TargetValue": 70.0,
"PredefinedMetricSpecification": {
"PredefinedMetricType": "DynamoDBReadCapacityUtilization"
},
"ScaleInCooldown": 60,
"ScaleOutCooldown": 0
}'GSI Capacity
GSIs have separate capacity settings:
aws dynamodb update-table \
--table-name Products \
--global-secondary-index-updates '[
{
"Update": {
"IndexName": "CategoryIndex",
"ProvisionedThroughput": {
"ReadCapacityUnits": 50,
"WriteCapacityUnits": 25
}
}
}
]'If a GSI runs out of write capacity, the base table is throttled. Ensure GSI capacity matches write patterns.
Reserved Capacity
Pre-purchase capacity for significant discounts:
| Term | Discount |
|---|---|
| 1 year | ~42% off |
| 3 years | ~77% off |
# Done through AWS Console or AWS Support
# Cannot be purchased via CLIReserved capacity is purchased at the region level and applies across all tables.
Throttling
What is Throttling?
When requests exceed provisioned capacity, DynamoDB throttles (rejects) requests. The SDK automatically retries with exponential backoff.
Detecting Throttles
aws cloudwatch get-metric-statistics \
--namespace AWS/DynamoDB \
--metric-name ThrottledRequests \
--dimensions Name=TableName,Value=Products \
--start-time $(date -d '1 hour ago' +%Y-%m-%dT%H:%M:%SZ) \
--end-time $(date +%Y-%m-%dT%H:%M:%SZ) \
--period 60 \
--statistics SumAvoiding Throttles
Increase Capacity
aws dynamodb update-table \
--table-name Products \
--provisioned-throughput \
ReadCapacityUnits=500,WriteCapacityUnits=200Enable Auto Scaling
Auto scaling reacts to increased load:
aws application-autoscaling register-scalable-target ...Switch to On-Demand
For truly unpredictable workloads:
aws dynamodb update-table \
--table-name Products \
--billing-mode PAY_PER_REQUESTImplement Exponential Backoff
The AWS SDK does this automatically. For custom clients:
const executeWithRetry = async (operation, maxRetries = 5) => {
for (let attempt = 0; attempt < maxRetries; attempt++) {
try {
return await operation();
} catch (error) {
if (error.name === 'ProvisionedThroughputExceededException') {
const delay = Math.pow(2, attempt) * 100 + Math.random() * 100;
await new Promise(r => setTimeout(r, delay));
continue;
}
throw error;
}
}
throw new Error('Max retries exceeded');
};Burst Capacity
Burst Credits
DynamoDB accumulates unused capacity for up to 5 minutes, allowing short bursts above provisioned capacity.
| Scenario | Behavior |
|---|---|
| Steady traffic below capacity | Burst credits accumulate |
| Traffic spike | Burst credits consumed |
| Credits exhausted | Throttling begins |
Best practice: Don't rely on burst capacity for sustained traffic.
Capacity Calculator
Estimate required capacity:
const calculateCapacity = ({
itemSizeKB,
readsPerSecond,
writesPerSecond,
consistencyMode = 'eventual', // 'eventual', 'strong', 'transactional'
writeMode = 'standard' // 'standard', 'transactional'
}) => {
// RCU calculation
const itemReadUnits = Math.ceil(itemSizeKB / 4);
let readMultiplier = 1;
if (consistencyMode === 'eventual') readMultiplier = 0.5;
if (consistencyMode === 'transactional') readMultiplier = 2;
const rcuRequired = Math.ceil(itemReadUnits * readsPerSecond * readMultiplier);
// WCU calculation
const itemWriteUnits = Math.ceil(itemSizeKB);
const writeMultiplier = writeMode === 'transactional' ? 2 : 1;
const wcuRequired = Math.ceil(itemWriteUnits * writesPerSecond * writeMultiplier);
return { rcuRequired, wcuRequired };
};
// Example
const capacity = calculateCapacity({
itemSizeKB: 2,
readsPerSecond: 100,
writesPerSecond: 50,
consistencyMode: 'eventual',
writeMode: 'standard'
});
// { rcuRequired: 50, wcuRequired: 100 }Hot Partitions
Hot Partition Problem
If traffic concentrates on a few partition keys, those partitions can throttle even with available capacity.
Detecting Hot Partitions
aws cloudwatch get-metric-statistics \
--namespace AWS/DynamoDB \
--metric-name ConsumedReadCapacityUnits \
--dimensions \
Name=TableName,Value=Products \
Name=Operation,Value=GetItem \
--start-time $(date -d '1 hour ago' +%Y-%m-%dT%H:%M:%SZ) \
--end-time $(date +%Y-%m-%dT%H:%M:%SZ) \
--period 60 \
--statistics SumSolutions
Distribute writes across multiple partition keys:
const getShardedKey = (key, shardCount = 10) => {
const shard = Math.floor(Math.random() * shardCount);
return `${key}#${shard}`;
};
// Write
await dynamodb.send(new PutCommand({
TableName: 'HotTable',
Item: {
pk: getShardedKey('popular-item'),
data: 'value'
}
}));
// Read (query all shards)
const results = await Promise.all(
Array.from({ length: 10 }, (_, i) =>
dynamodb.send(new QueryCommand({
TableName: 'HotTable',
KeyConditionExpression: 'pk = :pk',
ExpressionAttributeValues: {
':pk': `popular-item#${i}`
}
}))
)
);Use DynamoDB Accelerator (DAX) or ElastiCache:
import { DaxClient } from '@amazon-dax/dax-client';
const dax = new DaxClient({
endpoints: ['my-dax-cluster.dax.us-east-1.amazonaws.com:8111']
});
// Reads hit cache first
const result = await dax.send(new GetCommand({
TableName: 'Products',
Key: { productId: 'hot-product' }
}));Design partition keys for even distribution:
Bad: status (few values)
ACTIVE → All active items (hot)
INACTIVE → All inactive itemsGood: userId (many values)
user-001 → User's items
user-002 → User's items
...distributed across many partitionsAdaptive Capacity
DynamoDB automatically redistributes capacity to hot partitions. This handles most hot partition scenarios without intervention.
Adaptive capacity:
- Activates automatically
- Distributes unused capacity to hot partitions
- Up to 3,000 RCUs or 1,000 WCUs per partition
Monitoring Capacity
CloudWatch Metrics
aws cloudwatch get-metric-statistics \
--namespace AWS/DynamoDB \
--metric-name ConsumedReadCapacityUnits \
--dimensions Name=TableName,Value=Products \
--start-time $(date -d '1 hour ago' +%Y-%m-%dT%H:%M:%SZ) \
--end-time $(date +%Y-%m-%dT%H:%M:%SZ) \
--period 300 \
--statistics Sum AverageKey metrics:
- ConsumedReadCapacityUnits/ConsumedWriteCapacityUnits
- ProvisionedReadCapacityUnits/ProvisionedWriteCapacityUnits
- ReadThrottleEvents/WriteThrottleEvents
- ThrottledRequests
Set Up Alarms
aws cloudwatch put-metric-alarm \
--alarm-name "DynamoDB-Throttles" \
--metric-name ThrottledRequests \
--namespace AWS/DynamoDB \
--dimensions Name=TableName,Value=Products \
--statistic Sum \
--period 60 \
--threshold 1 \
--comparison-operator GreaterThanOrEqualToThreshold \
--evaluation-periods 1 \
--alarm-actions arn:aws:sns:us-east-1:123456789012:alertsCost Optimization
Right-Size Capacity
Monitor actual usage and adjust:
aws cloudwatch get-metric-statistics \
--namespace AWS/DynamoDB \
--metric-name ConsumedReadCapacityUnits \
...Use Reserved Capacity
For steady-state workloads, reserve capacity for up to 77% discount.
Consider On-Demand
For variable workloads, on-demand may be cheaper despite higher per-request cost.
Optimize Item Size
Smaller items = fewer capacity units:
// Before: 3 KB item = 3 WCUs
const item = {
pk: "user-123",
data: largeJsonObject
};
// After: Compress or store large data in S3
const item = {
pk: "user-123",
dataRef: "s3://bucket/key"
};Use Eventually Consistent Reads
Half the cost of strongly consistent:
await dynamodb.send(new GetCommand({
TableName: 'Products',
Key: { productId: 'prod-123' },
ConsistentRead: false // default
}));Best Practices
Capacity Best Practices
- Start with on-demand for new applications
- Monitor before provisioning - understand traffic patterns
- Enable auto scaling for provisioned tables
- Set alarms on throttles - catch issues early
- Design for even distribution - avoid hot partitions
- Use DAX for read-heavy workloads - reduce RCU consumption
- Consider reserved capacity for steady-state production
- Optimize item size - smaller items = lower costs