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, t for 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_ONLY or INCLUDE 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.

Optimization Checklist
Monthly Bill Estimate
$5,000
You save $0 / mo

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)
}