DevDocsDev Docs
DynamoDB

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
FeatureOn-DemandProvisioned
ScalingAutomaticManual or Auto Scaling
PricingPer requestPer hour (capacity units)
MinimumNone1 RCU, 1 WCU
Best forVariable/unknown trafficPredictable workloads
Reserved capacityNot availableUp to 77% discount

Capacity Units

Read Capacity Units (RCU)

Read TypeCalculation
Strongly consistent1 RCU = 1 read of up to 4 KB
Eventually consistent1 RCU = 2 reads of up to 4 KB
Transactional2 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 RCUs

Write Capacity Units (WCU)

Write TypeCalculation
Standard1 WCU = 1 write of up to 1 KB
Transactional2 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 WCUs

On-Demand Mode

Create on-demand table
aws dynamodb create-table \
  --table-name Products \
  --attribute-definitions \
    AttributeName=productId,AttributeType=S \
  --key-schema \
    AttributeName=productId,KeyType=HASH \
  --billing-mode PAY_PER_REQUEST
Switch to on-demand
aws dynamodb update-table \
  --table-name Products \
  --billing-mode PAY_PER_REQUEST

You 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

Create provisioned table
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=50
Update provisioned capacity
aws dynamodb update-table \
  --table-name Products \
  --provisioned-throughput \
    ReadCapacityUnits=200,WriteCapacityUnits=100
Register scalable target
# 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 500
Create scaling policy
aws 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:

Update GSI capacity
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:

TermDiscount
1 year~42% off
3 years~77% off
Purchase reserved capacity
# Done through AWS Console or AWS Support
# Cannot be purchased via CLI

Reserved 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

Check throttle metrics
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 Sum

Avoiding Throttles

Increase Capacity

aws dynamodb update-table \
  --table-name Products \
  --provisioned-throughput \
    ReadCapacityUnits=500,WriteCapacityUnits=200

Enable 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_REQUEST

Implement Exponential Backoff

The AWS SDK does this automatically. For custom clients:

Exponential backoff
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.

ScenarioBehavior
Steady traffic below capacityBurst credits accumulate
Traffic spikeBurst credits consumed
Credits exhaustedThrottling begins

Best practice: Don't rely on burst capacity for sustained traffic.

Capacity Calculator

Estimate required capacity:

Capacity calculator
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

Check per-partition metrics
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 Sum

Solutions

Distribute writes across multiple partition keys:

Write sharding
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:

DAX client
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 items

Good: userId (many values)

user-001 → User's items
user-002 → User's items
...distributed across many partitions

Adaptive 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

Get consumed capacity
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 Average

Key metrics:

  • ConsumedReadCapacityUnits/ConsumedWriteCapacityUnits
  • ProvisionedReadCapacityUnits/ProvisionedWriteCapacityUnits
  • ReadThrottleEvents/WriteThrottleEvents
  • ThrottledRequests

Set Up Alarms

Throttle alarm
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:alerts

Cost Optimization

Right-Size Capacity

Monitor actual usage and adjust:

Check utilization
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

  1. Start with on-demand for new applications
  2. Monitor before provisioning - understand traffic patterns
  3. Enable auto scaling for provisioned tables
  4. Set alarms on throttles - catch issues early
  5. Design for even distribution - avoid hot partitions
  6. Use DAX for read-heavy workloads - reduce RCU consumption
  7. Consider reserved capacity for steady-state production
  8. Optimize item size - smaller items = lower costs

Next Steps

On this page