Back to Blog
AWSDynamoDBreduce_costsLists

The DynamoDB cost model, demystified

How RCU/WCU rounding, consistency, transactions, and GSI write amplification interact — and why a naive table design can 10x your bill before a single row is written.

JohannaNovember 19, 20255 min read

DynamoDB has a reputation for being either shockingly cheap or shockingly expensive, with not much in between. Both reputations are earned, and which one you get is decided almost entirely at design time — by item size, key choice, index count, and read consistency. The pricing is not complicated once you see the units. It is just unforgiving of designs that ignore them, because the cost model rewards and punishes schema decisions directly. This is how the meter actually turns.

Numbers below are us-east-1 and move over time; AWS cut on-demand and provisioned rates substantially in late 2024, so treat the figures as illustrative and check current pricing. The mechanics are stable.

The two capacity models

On-demand bills per request. You pay per write request unit (WRU) and read request unit (RRU) with no capacity to manage — the table scales itself. It is the right default for spiky, unpredictable, or new workloads where you can't forecast throughput.

Provisioned bills for reserved throughput per hour, measured in write capacity units (WCU) and read capacity units (RCU), whether or not you use them. With Auto Scaling it tracks demand within bounds. It is cheaper per unit than on-demand and, for steady predictable traffic, meaningfully so.

The rough guidance: on-demand wins when average utilization of an equivalent provisioned table would sit below ~15–20%, because you'd be paying for reserved capacity you mostly waste. Steady, high-utilization tables want provisioned, ideally with reserved capacity on top.

The units, and where the rounding hurts

This is the part that determines everything downstream:

  • 1 WCU = one write of an item up to 1KB, per second. Item size rounds up to the next KB. A 1.2KB item costs 2 WCU to write; a 3.1KB item costs 4.
  • 1 RCU = one strongly consistent read of an item up to 4KB, per second — or two eventually consistent reads of up to 4KB. Read size also rounds up to the next 4KB.

Two consequences fall straight out of the rounding. First, item size is a cost multiplier you control. Storing a 5KB item where a 900-byte item would do doesn't cost you 5x storage — it costs you the difference in every read and write for the life of the table, because throughput is billed per KB (writes) and per 4KB (reads). Fat items with rarely-read attributes are a standing tax. Move cold attributes to a separate item or a separate table and your hot path shrinks its unit cost.

Second, eventually consistent reads are half price. If your access pattern tolerates reading data that may be a few hundred milliseconds stale — which most read paths do — you halve the read cost by not asking for strong consistency. Defaulting every GetItem to strongly consistent "to be safe" is one of the most common ways to pay double for nothing.

Transactions double it again

TransactWriteItems and TransactGetItems give you all-or-nothing multi-item operations, and they cost 2x the normal units. A transactional write of a 1KB item is 2 WCU, not 1; a transactional read of a 4KB item is 2 RCU, not 1. Transactions are the correct tool when you genuinely need atomicity across items, and the wrong tool when you reached for them out of habit. Wrapping a single-item write in a transaction because the API was handy doubles its cost for a guarantee you didn't need.

GSI write amplification, the silent 10x

Here is where naive designs explode. Every Global Secondary Index is a separate copy of your data keyed differently, and writes to the base table propagate to every GSI whose projected attributes are affected — each billed separately. One base-table write becomes one base write plus one write per touched index. Five GSIs and you are potentially paying 6 WCU for what looks like a single write.

It compounds with item size, because each GSI write is also rounded up per KB against the projected attributes. Project ALL into five GSIs on a 3KB item and a single logical update can cost on the order of 6 × 4 = 24 WCU. This is the most common way I see a table's bill land at ten times the estimate: the estimate counted base-table writes and quietly ignored that every write fans out across the indexes. Project only the attributes each index query needs (KEYS_ONLY or a tight INCLUDE), and only build the GSIs you actually query.

Hot partitions and adaptive capacity

DynamoDB spreads data across partitions by partition key. Each physical partition has hard ceilings — roughly 3,000 RCU and 1,000 WCU, and 10GB. Adaptive capacity now redistributes throughput toward hot partitions automatically and near-instantly, which papers over uneven access at the table level. What it cannot fix is a single hot item or a single hot key: one partition key taking all the traffic still bumps the per-partition ceiling no matter how much table capacity you provision. A partition key like status = "active" or a monotonic timestamp funnels everything into one partition and throttles. High-cardinality keys that spread writes evenly are a cost and reliability decision, not just a performance one — throttling on provisioned tables means retries and, on on-demand, still means a bad time.

The levers that lower the bill

  • Reserved capacity applies to provisioned tables: commit to a baseline of WCU/RCU for one or three years for a large discount (historically up to ~50–77%) over on-demand-rate provisioned pricing. Only worth it for stable, long-lived baseline throughput.
  • TTL deletes expired items for you, and TTL deletions do not consume write capacity — they are free. For session data, logs, or anything with a natural expiry, a TTL attribute is far cheaper than issuing DeleteItem calls that each burn WCU.
  • DynamoDB Standard-IA table class cuts storage cost by roughly 60% (about $0.10/GB-month vs $0.25) in exchange for ~25% higher throughput cost. It's a clear win for tables where storage dominates and reads/writes are infrequent — audit trails, old orders, cold event logs. It's a loss for hot tables where throughput, not storage, is the bill.
  • Trim item size and consistency as covered above — the two cheapest changes, applied to every request the table serves.

Model it before you deploy

You can compute a DynamoDB bill from a design on paper, and you should, because the expensive mistakes are structural and hard to unwind once data is live. For each access pattern, work out: average item size rounded up to the next KB (writes) or 4KB (reads); reads times consistency factor (0.5 for eventual, 1 for strong, 2 for transactional); writes times (1 + number of GSIs the write touches); peak requests per second. Multiply through and you have your WCU/RCU or on-demand request counts before a single row exists.

Do that arithmetic and the 10x surprises stop being surprises — they show up in the spreadsheet as a fifth GSI projecting ALL, or a strongly-consistent read path that could be eventual, or 4KB items that should be 800 bytes. DynamoDB isn't expensive. Designs that never counted the units are.