Explanation
Why the waste happens and who it affects.
When Time to Live (TTL) is not enabled and no cleanup process exists, these items stay in the table indefinitely, and storage grows every month even though the application never reads the old items again.
Teams that do clean up often do it with scheduled jobs that Scan the table and issue DeleteItem or BatchWriteItem calls. Those deletes consume write capacity or write request units, and the Scan consumes read capacity, so the cleanup itself becomes a recurring cost. TTL removes expired items in the background at no additional cost, which is why the DynamoDB cost optimization guidance lists enabling TTL as a way to trim tables whose older data becomes irrelevant.
Billing model
The pricing dimensions that drive this cost.
DynamoDB bills storage continuously based on table size, and user-issued deletes are billed like any other write.
- Data storage
- Billed per GB-month for all items in the table, based on the table class (Standard or Standard-Infrequent Access)
- User deletes
- DeleteItem and BatchWriteItem deletes consume write capacity units or write request units like other writes
- TTL deletes
- Items past their TTL timestamp are deleted by DynamoDB in the background without consuming write throughput
- Replicated TTL deletes
- In global tables, the TTL delete is replicated to each replica Region and consumes replicated write capacity or replicated write units there
How to detect
5 checks to find it in your estate.
- Call DescribeTimeToLive for each table and list tables where TimeToLiveStatus is DISABLED, prioritizing tables whose names or schemas suggest time-bound data (sessions, events, tokens, logs, cache)
- Track TableSizeBytes and ItemCount from DescribeTable over time (DynamoDB refreshes these values roughly every six hours) and flag tables that grow steadily while read traffic stays flat
- Sample items and compare their creation or event timestamps with the business retention window to confirm that old items are being kept without purpose
- Look for scheduled Lambda functions, Step Functions workflows, or batch jobs that Scan a table and delete old items, and check the Scan and DeleteItem or BatchWriteItem capacity they consume in CloudWatch
- For tables that already have TTL enabled, check the TimeToLiveDeletedItemCount metric; a value of zero over a long period can mean the TTL attribute is missing, misnamed, or not stored as a Number in epoch seconds
How to fix
6 ways to remove the waste.
- Add an expiry attribute that is written on every create or update as a Number in Unix epoch seconds, then enable TTL on the table with that attribute name; items with a non-Number TTL attribute are ignored
- Backfill the expiry attribute on existing items so data written before the change also becomes eligible for deletion; this backfill is a one-time write cost
- Retire Scan-and-delete cleanup jobs once TTL is working, since TTL deletes do not consume write capacity
- If expired data must be retained elsewhere, consume the TTL deletions from DynamoDB Streams (they appear with userIdentity type Service and principalId dynamodb.amazonaws.com) and archive them to lower-cost storage such as S3
- Because TTL deletes typically happen within a few days of expiry rather than immediately, add a filter expression on the TTL attribute to Query and Scan calls when the application must not see expired items; pending items still count toward storage and read costs until deleted
- For global tables, account for the replicated write charge that each TTL delete incurs in every replica Region
Documentation
Vendor references for pricing and configuration.