Capacity, Billing & Transactions
DynamoDB's operational model ā understanding capacity units prevents surprise bills and throttling. Transactions enable atomic multi-item operations at 2Ć cost.
Table of Contents
Read Capacity Units (RCU)
| Read Type | Cost per 4 KB | Notes |
|---|---|---|
| Eventually consistent | 0.5 RCU | Default, reads from any replica |
| Strongly consistent | 1 RCU | Routes to leader replica |
| Transactional | 2 RCU | Snapshot isolation across items |
Calculation Examples
RCU Calculation: āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā Item Size | Eventually Consistent | Strongly Consistent āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā 1 KB item | ceil(1/4) Ć 0.5 = 0.5 ā 1 RCU | ceil(1/4) Ć 1 = 1 RCU 4 KB item | ceil(4/4) Ć 0.5 = 0.5 ā 1 RCU | ceil(4/4) Ć 1 = 1 RCU 10 KB item | ceil(10/4) Ć 0.5 = 1.5 ā 2 RCU | ceil(10/4) Ć 1 = 3 RCU 50 KB item | ceil(50/4) Ć 0.5 = 6.5 ā 7 RCU | ceil(50/4) Ć 1 = 13 RCU āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā Key insight: item size directly impacts cost. Smaller items = lower cost per read.
ProjectionExpression Does NOT Reduce RCU
Even if you project only 2 attributes from a 50 KB item, you still pay RCU for the full 50 KB. ProjectionExpression reduces network bandwidth, not read cost. To reduce RCU, make items smaller or split large items.
Write Capacity Units (WCU)
| Write Type | Cost per 1 KB | Notes |
|---|---|---|
| Standard write | 1 WCU | PutItem, UpdateItem, DeleteItem |
| Transactional write | 2 WCU | TransactWriteItems |
WCU Calculation: āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā Item Size | Standard Write | Transactional Write āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā 0.5 KB item | ceil(0.5/1) = 1 WCU | 2 WCU 1 KB item | ceil(1/1) = 1 WCU | 2 WCU 3.5 KB item | ceil(3.5/1) = 4 WCU | 8 WCU 10 KB item | ceil(10/1) = 10 WCU | 20 WCU āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā For updates: WCU based on max(old item size, new item size). Deletes: WCU based on the deleted item's size.
Write Amplification from GSIs
Every write to the base table also writes to each GSI that includes the item. With 3 GSIs: effective WCU = base write WCU + (3 Ć GSI write WCU). A 1 KB item write with 3 GSIs costs 4 WCU minimum. Factor this into capacity planning.
Provisioned vs On-Demand
| Feature | Provisioned | On-Demand |
|---|---|---|
| Pricing | Pay for provisioned capacity (used or not) | Pay per request (~7Ć more per request) |
| Scaling | Manual or auto scaling (not instant) | Instant, automatic |
| Throttling | Exceeding capacity ā ProvisionedThroughputExceededException | Hot partition throttling only |
| Best for | Predictable, sustained traffic | Unpredictable, spiky, new tables |
| Cost at scale | Cheaper with reserved capacity | Expensive at sustained high throughput |
| Switching | Can switch once per 24 hours | Can switch once per 24 hours |
Provisioned Mode Details
Provisioned Mode Details
- ā You specify RCU and WCU per table ā DynamoDB guarantees that throughput
- ā Burst capacity: unused capacity banked for up to 5 minutes
- ā Auto scaling: CloudWatch alarms trigger capacity adjustments (not instant ā minutes)
- ā Reserved capacity: commit 1 or 3 years for significant discount (up to 77%)
- ā Throttling: requests exceeding capacity return ProvisionedThroughputExceededException
Decision Framework
Choose Provisioned When
- Traffic is predictable and sustained
- Cost optimization is priority
- Can commit to reserved capacity
- Team can manage auto scaling policies
Choose On-Demand When
- Traffic is unpredictable or spiky
- New table (unknown traffic patterns)
- Dev/staging environments
- Simplicity over cost optimization
Adaptive Capacity & Auto Scaling
Adaptive capacity automatically redistributes provisioned capacity to hot partitions. If one partition needs more throughput, DynamoDB borrows from underutilized partitions.
Adaptive Capacity Characteristics
- ā Reduces isolated partition throttling ā hot partition gets more of table's capacity
- ā Does NOT eliminate hot partition problem entirely ā design is still critical
- ā Works within provisioned capacity ā cannot exceed total table capacity
- ā Automatic ā no configuration needed
- ā Helps with temporary spikes, not sustained hot keys
Auto Scaling (Provisioned Mode)
Target Utilization
Set target (e.g., 70% of provisioned capacity)
CloudWatch Alarm
Consumed capacity exceeds target for sustained period
Scale Up
Application Auto Scaling increases provisioned capacity
Cool Down
Wait period before next scaling action (default 60s up, 60s down)
Auto Scaling is Not Instant
Auto scaling reacts to CloudWatch metrics ā there is a delay of 1-2 minutes between traffic spike and capacity increase. For sudden spikes (flash sales, viral events), auto scaling is too slow. Use on-demand mode or pre-warm with scheduled scaling.
Transactions
DynamoDB transactions provide atomic, consistent, isolated operations across multiple items ā potentially across multiple tables in the same region. They cost 2Ć normal operations.
TransactWriteItems
TransactWriteItems Characteristics
- ā Up to 25 items or 4 MB in one atomic transaction
- ā Operations: Put, Update, Delete, ConditionCheck
- ā All succeed or all fail ā atomicity guaranteed
- ā 2Ć WCU cost per item
- ā Idempotency: ClientRequestToken ā safe to retry without double-applying
- ā Cannot span regions (Global Tables limitation)
// Transfer funds between two accounts atomically const params = { TransactItems: [ { Update: { TableName: "Accounts", Key: { PK: { S: "ACCOUNT#sender" }, SK: { S: "BALANCE" } }, UpdateExpression: "SET #balance = #balance - :amount", ConditionExpression: "#balance >= :amount", // sufficient funds ExpressionAttributeNames: { "#balance": "balance" }, ExpressionAttributeValues: { ":amount": { N: "100" } } } }, { Update: { TableName: "Accounts", Key: { PK: { S: "ACCOUNT#receiver" }, SK: { S: "BALANCE" } }, UpdateExpression: "SET #balance = #balance + :amount", ExpressionAttributeNames: { "#balance": "balance" }, ExpressionAttributeValues: { ":amount": { N: "100" } } } } ] }; // Both updates succeed or both fail ā no partial state
Transaction Conflicts
Transaction Conflicts
- āTransactionCanceledException ā one or more conditions failed or conflict detected
- āTransactionConflict: another transaction modified the same item simultaneously
- āRetry strategy: exponential backoff with jitter
- āTransaction isolation: serializable ā no two transactions interleave
- āCancellation reasons returned per item ā diagnose which item failed
When NOT to Use Transactions
Transactions cost 2Ć and have higher latency. Avoid on high-throughput paths where condition expressions alone suffice. Single-item operations are already atomic without transactions. Use transactions only when you need atomicity ACROSS multiple items.
TTL (Time to Live)
TTL enables automatic item expiration. You specify an attribute that holds a Unix epoch timestamp ā DynamoDB deletes items after that time passes.
TTL Characteristics
- ā TTL attribute must be Number type ā Unix epoch seconds
- ā Deletion is asynchronous ā can be up to 48 hours after expiry
- ā Expired but not yet deleted items still returned by reads ā filter in application
- ā No storage charge after expiry timestamp (even if not yet deleted)
- ā Items without the TTL attribute never expire
- ā TTL deletes appear in DynamoDB Streams ā can trigger Lambda on expiry
TTL Use Cases
| Use Case | TTL Strategy | Benefit |
|---|---|---|
| Session expiry | Set TTL to session timeout | Auto-cleanup without cron jobs |
| Cache entries | Set TTL to cache duration | Self-managing cache layer |
| Temporary locks | Set TTL to lock timeout | Auto-release distributed locks |
| Event deduplication | Set TTL to processing window | Expire seen event IDs |
| Soft-delete cleanup | Set TTL on soft-deleted items | Auto hard-delete after retention |
| Regulatory compliance | Set TTL to retention period | Auto-delete after legal requirement |
TTL + Streams Pattern
TTL deletions appear in DynamoDB Streams with userIdentity.principalId = "dynamodb.amazonaws.com". Use this to trigger Lambda on expiry: audit logging of deleted items, cascade deletes to other systems, or archival to S3.
Cost Optimization
Cost Optimization Strategies
- ā Right-size items: smaller items = lower RCU/WCU cost per operation
- ā Short attribute names: they count toward 400 KB limit and storage cost
- ā Choose capacity mode based on traffic pattern (provisioned for steady, on-demand for spiky)
- ā Reserved capacity for predictable workloads (up to 77% discount)
- ā Standard-IA table class for infrequently accessed data (lower storage, higher read cost)
- ā Compress large attribute values (gzip binary attributes)
- ā Use sparse indexes ā only items needing the query appear in the index
- ā Minimize GSI count ā each GSI adds write amplification
- ā Use KEYS_ONLY projection on GSIs when possible
- ā Eventually consistent reads by default (half the RCU cost)
| Optimization | Savings | Trade-off |
|---|---|---|
| Reserved capacity (3yr) | Up to 77% | Commitment, less flexibility |
| Eventually consistent reads | 50% RCU | May read stale data (< 1s) |
| On-demand ā Provisioned | Up to 85% at steady state | Risk of throttling |
| Standard-IA table class | ~60% storage cost | Higher per-read cost |
| Fewer GSIs | Linear WCU reduction | Fewer access patterns supported |
| Smaller items | Linear cost reduction | May need more queries |
Interview Questions
Q:How do you calculate RCU for a 10 KB item with eventually consistent reads?
A: ceil(10 KB / 4 KB) Ć 0.5 = ceil(2.5) Ć 0.5 = 3 Ć 0.5 = 1.5, rounded up to 2 RCU. For strongly consistent: 3 Ć 1 = 3 RCU. For transactional: 3 Ć 2 = 6 RCU. Item size is the primary cost driver ā keep items small.
Q:When would you choose on-demand over provisioned capacity?
A: On-demand for: new tables with unknown traffic, spiky/unpredictable workloads, dev/staging environments, applications where simplicity matters more than cost. Provisioned for: predictable sustained traffic, cost-sensitive production workloads, when you can commit to reserved capacity. On-demand is ~7Ć more expensive per request at steady state.
Q:What are DynamoDB transactions and when should you use them?
A: TransactWriteItems/TransactGetItems provide atomic operations across up to 25 items (potentially across tables). Use for: fund transfers, booking systems, any operation where partial completion is unacceptable. Cost: 2Ć normal operations. Don't use for: single-item operations (already atomic), high-throughput paths where condition expressions suffice.
Q:How does TTL work and what are its limitations?
A: TTL uses a Number attribute (Unix epoch seconds). DynamoDB's background process deletes expired items, but deletion can be delayed up to 48 hours. Expired items may still appear in reads ā applications must filter them. TTL deletes are free (no WCU charged) and appear in Streams for downstream processing.
Q:What happens when a DynamoDB table is throttled?
A: Requests return ProvisionedThroughputExceededException. AWS SDK retries automatically with exponential backoff. Causes: exceeded provisioned capacity, hot partition (even with sufficient table-level capacity), or under-provisioned GSI throttling base table writes. Solutions: increase capacity, fix hot partition keys, enable auto scaling, or switch to on-demand.
Common Mistakes
Using on-demand mode for predictable high-throughput workloads
On-demand is ~7Ć more expensive per request than provisioned at steady state. For a table doing 10,000 WCU sustained, on-demand costs significantly more. Switch to provisioned with auto scaling once traffic patterns are understood.
ā Switch to provisioned capacity with auto scaling once traffic patterns stabilize, and consider reserved capacity for further savings.
Using transactions for single-item operations
Individual DynamoDB operations (PutItem, UpdateItem) are already atomic per item. Using TransactWriteItems for a single item doubles the cost for no benefit. Transactions are only needed for atomicity ACROSS multiple items.
ā Use standard PutItem/UpdateItem with ConditionExpression for single-item atomic operations instead of transactions.
Not filtering expired TTL items in application code
TTL deletion can be delayed up to 48 hours. If your application reads items and doesn't check the TTL attribute, it may process expired items. Always add a filter: if item.ttl < currentTime, treat as deleted.
ā Add application-level filtering to check the TTL attribute against current time and discard expired items from query results.
Ignoring burst capacity limits
DynamoDB banks unused capacity for up to 5 minutes of burst. But if your traffic consistently exceeds provisioned capacity, burst runs out and throttling begins. Burst is for short spikes, not sustained over-provisioning.
ā Provision capacity for your sustained throughput needs and rely on burst only for brief spikes. Use auto scaling for gradual increases.
Not accounting for GSI capacity separately
GSIs have their own provisioned capacity. If a GSI is under-provisioned, writes to the BASE TABLE get throttled (DynamoDB can't replicate). Monitor and provision GSI capacity independently from the base table.
ā Monitor and provision each GSI's capacity independently. Set up CloudWatch alarms on GSI throttling metrics.