RCU & WCU
In traditional databases, you pay for servers (CPU, RAM). In DynamoDB, you pay for throughput.
This throughput is measured in Capacity Units. Understanding these units is critical for performance tuning and cost estimation.
1. The Currency of Throughput
DynamoDB abstracts hardware away. Instead of “CPU cycles,” you purchase units of work.
1. Read Capacity Unit (RCU)
One RCU represents one strongly consistent read per second for an item up to 4 KB.
- Eventually Consistent Reads: Consumes 0.5 RCU (cheaper).
- Transactional Reads: Consumes 2.0 RCU (expensive).
- Item Size: Rounded up to the nearest 4 KB.
- Read 100 bytes → 1 RCU.
- Read 4.1 KB → 2 RCUs.
2. Write Capacity Unit (WCU)
One WCU represents one standard write per second for an item up to 1 KB.
- Transactional Writes: Consumes 2.0 WCU.
- Item Size: Rounded up to the nearest 1 KB.
- Write 100 bytes → 1 WCU.
- Write 1.1 KB → 2 WCUs.
DynamoDB is optimized for small items. Storing large blobs (e.g., base64 images) in DynamoDB is an anti-pattern because it burns through WCUs quickly. Store large objects in S3 and save the URL in DynamoDB.
2. Capacity Modes
Provisioned Mode
You say: “I need 1000 RCUs and 500 WCUs.”
- Pros: Cheapest per unit (if fully utilized).
- Cons: You pay even if you don’t use it.
- Use Case: Predictable, steady traffic (e.g., e-commerce cart service).
On-Demand Mode
You say: “Handle whatever traffic comes.”
- Pros: Zero capacity planning. Handles spiky traffic instantly.
- Cons: ~5-7x more expensive per request than fully utilized provisioned capacity.
- Use Case: New startups, unpredictable workloads, dev/test tables.
3. Interactive: Throughput Calculator
Visualize the cost of your data model.
Throughput Estimator
4. Handling Throttling
If you consume more capacity than you provisioned, DynamoDB throws ProvisionedThroughputExceededException.
The client must handle this with Exponential Backoff and Jitter.
Java Implementation (Custom Retry)
The AWS SDK v2 has built-in retry logic, but you can customize it for high-throughput scenarios.
import software.amazon.awssdk.core.client.config.ClientOverrideConfiguration;
import software.amazon.awssdk.core.retry.RetryPolicy;
import software.amazon.awssdk.core.retry.backoff.FullJitterBackoffStrategy;
import software.amazon.awssdk.services.dynamodb.DynamoDbClient;
import java.time.Duration;
public class DynamoDbClientFactory {
public static DynamoDbClient createClient() {
// Strategy: Wait 100ms, then 200ms, 400ms... up to 2s
// FullJitter adds randomness to prevent "thundering herd"
FullJitterBackoffStrategy backoff = FullJitterBackoffStrategy.builder()
.baseDelay(Duration.ofMillis(100))
.maxBackoffTime(Duration.ofSeconds(2))
.build();
RetryPolicy retryPolicy = RetryPolicy.builder()
.numRetries(5)
.backoffStrategy(backoff)
.build();
return DynamoDbClient.builder()
.overrideConfiguration(ClientOverrideConfiguration.builder()
.retryPolicy(retryPolicy)
.build())
.build();
}
}
Go Implementation (Custom Retry)
package main
import (
"context"
"log"
"time"
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/aws/retry"
"github.com/aws/aws-sdk-go-v2/config"
"github.com/aws/aws-sdk-go-v2/service/dynamodb"
)
func CreateClient() *dynamodb.Client {
// Custom retryer: Max 5 attempts, Max backoff 2s
customRetryer := retry.NewStandard(func(o *retry.StandardOptions) {
o.MaxAttempts = 5
o.MaxBackoff = 2 * time.Second
})
cfg, err := config.LoadDefaultConfig(context.TODO(),
config.WithRetryer(func() aws.Retryer {
return customRetryer
}),
)
if err != nil {
log.Fatalf("unable to load SDK config, %v", err)
}
return dynamodb.NewFromConfig(cfg)
}