Capacity Modes
One of the first architecture decisions you make when creating a DynamoDB table is choosing the Capacity Mode. This decision fundamentally dictates how your table scales, how it handles traffic spikes, and how you are billed at the end of the month.
1. The Two Modes
DynamoDB offers two distinct capacity models. You can switch between them once every 24 hours, allowing you to adapt as your application evolves.
A. Provisioned Capacity (The “Rent” Model)
In this mode, you reserve a specific amount of throughput for your table.
- Mechanism: You specify exactly how many Read Capacity Units (RCU) and Write Capacity Units (WCU) you need.
- Billing: You pay for what you provision, regardless of whether you use it. It’s like renting a server; you pay for the hardware even if it sits idle.
- Best For: Steady, predictable workloads with gradual traffic changes.
- Cost: Significantly cheaper per-request if you can keep utilization high (e.g., above 60-70%).
B. On-Demand Capacity (The “Taxi” Model)
In this mode, DynamoDB instantly accommodates your workload as it ramps up or down.
- Mechanism: No capacity planning required. DynamoDB automatically allocates resources.
- Billing: You pay strictly for what you use (per request). It’s like taking a taxi; you only pay when you’re moving.
- Best For: Unpredictable traffic, new startups, or “spiky” workloads (e.g., ticket sales).
- Cost: Higher price per request than Provisioned, but cheaper total cost if your traffic is sparse or idle most of the time.
2. Deep Dive: Auto Scaling Internals
Provisioned Capacity is rarely static. You almost always enable Auto Scaling to adjust RCUs and WCUs dynamically.
How Target Tracking Works
DynamoDB uses a Target Tracking algorithm (similar to a PID controller). You set a target utilization (e.g., 70%).
- CloudWatch Alarms: DynamoDB publishes consumption metrics to CloudWatch every minute.
- Evaluation: If utilization stays above 70% for a few minutes, the alarm triggers.
- Scale Out: DynamoDB increases provisioned capacity. This happens relatively quickly (minutes).
- Scale In: If utilization drops, DynamoDB decreases capacity. Crucially, this is slow. DynamoDB will only scale down up to 4 times per day (historically) or more frequently now, but with a conservative dampening factor to prevent “thrashing” (rapidly scaling up and down).
[!WARNING] The “Spike” Problem: Auto Scaling is reactive, not proactive. If your traffic jumps from 100 to 10,000 requests in 1 second, Auto Scaling will be too slow to catch up, and your requests will be throttled. For instant spikes, use On-Demand.
3. The Capacity Formula
To provision correctly, you must understand the “Unit” currency:
- 1 WCU (Write Capacity Unit):
- 1 write per second for an item up to 1 KB.
- Math: Round up item size to nearest KB. (e.g., 1.1 KB item = 2 WCUs).
- 1 RCU (Read Capacity Unit):
- 1 Strongly Consistent read per second for an item up to 4 KB.
- 2 Eventually Consistent reads per second for an item up to 4 KB.
- Math: Transactional reads cost 2x RCU.
[!TIP] Eventually Consistent reads are 50% cheaper. Unless you need “read-your-own-write” consistency immediately, always default to Eventual Consistency in your
GetItemandQuerycalls.
4. Interactive: Capacity Mode Predictor
Analyze your traffic pattern to determine the most cost-effective mode.
5. Code Implementation
Here is how you define capacity modes programmatically when creating a table.
import software.amazon.awssdk.services.dynamodb.DynamoDbClient;
import software.amazon.awssdk.services.dynamodb.model.*;
public class CreateTable {
public static void main(String[] args) {
DynamoDbClient ddb = DynamoDbClient.create();
// 1. Define On-Demand Table
CreateTableRequest onDemandReq = CreateTableRequest.builder()
.tableName("Orders_OnDemand")
.keySchema(KeySchemaElement.builder()
.attributeName("OrderId").keyType(KeyType.HASH).build())
.attributeDefinitions(AttributeDefinition.builder()
.attributeName("OrderId").attributeType(ScalarAttributeType.S).build())
// PAY_PER_REQUEST = On-Demand
.billingMode(BillingMode.PAY_PER_REQUEST)
.build();
// 2. Define Provisioned Table (Read: 10, Write: 5)
CreateTableRequest provisionedReq = CreateTableRequest.builder()
.tableName("Orders_Provisioned")
.keySchema(KeySchemaElement.builder()
.attributeName("OrderId").keyType(KeyType.HASH).build())
.attributeDefinitions(AttributeDefinition.builder()
.attributeName("OrderId").attributeType(ScalarAttributeType.S).build())
// PROVISIONED = Provisioned
.billingMode(BillingMode.PROVISIONED)
.provisionedThroughput(ProvisionedThroughput.builder()
.readCapacityUnits(10L)
.writeCapacityUnits(5L)
.build())
.build();
ddb.createTable(onDemandReq);
System.out.println("Tables created.");
}
}
package main
import (
"context"
"fmt"
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/config"
"github.com/aws/aws-sdk-go-v2/service/dynamodb"
"github.com/aws/aws-sdk-go-v2/service/dynamodb/types"
)
func main() {
cfg, _ := config.LoadDefaultConfig(context.TODO())
client := dynamodb.NewFromConfig(cfg)
// 1. Define On-Demand Table
_, err := client.CreateTable(context.TODO(), &dynamodb.CreateTableInput{
TableName: aws.String("Orders_OnDemand"),
KeySchema: []types.KeySchemaElement{
{AttributeName: aws.String("OrderId"), KeyType: types.KeyTypeHash},
},
AttributeDefinitions: []types.AttributeDefinition{
{AttributeName: aws.String("OrderId"), AttributeType: types.ScalarAttributeTypeS},
},
// PAY_PER_REQUEST = On-Demand
BillingMode: types.BillingModePayPerRequest,
})
// 2. Define Provisioned Table
_, err = client.CreateTable(context.TODO(), &dynamodb.CreateTableInput{
TableName: aws.String("Orders_Provisioned"),
KeySchema: []types.KeySchemaElement{
{AttributeName: aws.String("OrderId"), KeyType: types.KeyTypeHash},
},
AttributeDefinitions: []types.AttributeDefinition{
{AttributeName: aws.String("OrderId"), AttributeType: types.ScalarAttributeTypeS},
},
// PROVISIONED = Provisioned
BillingMode: types.BillingModeProvisioned,
ProvisionedThroughput: &types.ProvisionedThroughput{
ReadCapacityUnits: aws.Int64(10),
WriteCapacityUnits: aws.Int64(5),
},
})
if err != nil {
panic(err)
}
fmt.Println("Tables created.")
}