Cost Optimization
DynamoDB is serverless, which means you can accidentally spend a fortune if you aren’t careful. A poorly optimized table can cost 10,000/month, while a well-optimized one with the same traffic might cost 500/month.
Here is the definitive guide to slashing your bill.
1. The “Attribute Name” Tax
DynamoDB stores attribute names with every single item.
- Scenario: You have 1 billion items.
- Bad: Attribute name
user_email_address_verified(27 bytes). - Good: Attribute name
ev(2 bytes). - Saving: 25 bytes * 1 billion = 25 GB of storage. At 0.25/GB, that’s 6.25/month just for the name of one attribute. Now imagine you have 20 attributes and you write them millions of times. The WCU cost difference is even more significant.
[!TIP] Aliases: Use short aliases for attribute names in your database (e.g.,
pk,sk,tfor type), but map them to readable names in your application code (DTOs).
2. Eventual Consistency: The 50% Discount
By default, GetItem and Query use Strongly Consistent reads (unless configured otherwise).
- Strong: Returns the absolute latest data. Costs 1 RCU per 4KB.
- Eventual: Might return data from 100ms ago. Costs 0.5 RCU per 4KB.
Strategy: Switch 99% of your reads to Eventual Consistency. Only use Strong Consistency for critical flows (e.g., “Checkout”).
3. Projection Expressions (Save Bandwidth)
If your item has 50 attributes (total 10KB) but you only need the status (50 bytes), fetching the whole item costs 3 RCUs (10KB / 4KB rounded up).
Using a Projection Expression to fetch only status:
- Note: DynamoDB still reads the full 10KB from disk, so you pay 3 RCUs.
- Wait, what? Yes, Projections don’t save RCUs on
GetItem. - However, they DO save RCUs on Global Secondary Indexes (GSIs). If your GSI only projects
KEYS_ONLYorINCLUDE status, the GSI storage and write costs are a fraction of the base table.
4. Interactive: The Bill Shredder
Simulate a high-traffic table (100M reads/month, 1TB storage) and see how optimizations stack up.
5. Code Implementation: Projection Expressions
Don’t fetch what you don’t need.
import software.amazon.awssdk.services.dynamodb.DynamoDbClient;
import software.amazon.awssdk.services.dynamodb.model.*;
import java.util.Map;
public class EfficientRead {
public static void main(String[] args) {
DynamoDbClient client = DynamoDbClient.create();
GetItemResponse response = client.getItem(GetItemRequest.builder()
.tableName("Users")
.key(Map.of("UserId", AttributeValue.builder().s("123").build()))
// Only return the status, save network bandwidth
.projectionExpression("#st")
.expressionAttributeNames(Map.of("#st", "Status"))
// Use Eventual Consistency (50% cheaper)
.consistentRead(false)
.build());
System.out.println(response.item());
}
}
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)
out, err := client.GetItem(context.TODO(), &dynamodb.GetItemInput{
TableName: aws.String("Users"),
Key: map[string]types.AttributeValue{"UserId": &types.AttributeValueMemberS{Value: "123"}},
// Only return status
ProjectionExpression: aws.String("#st"),
ExpressionAttributeNames: map[string]string{
"#st": "Status",
},
// Eventual Consistency
ConsistentRead: aws.Bool(false),
})
if err != nil {
panic(err)
}
fmt.Println(out.Item)
}