DynamoDB On-Demand vs Provisioned: Which Is Actually Cheaper?

The crossover is 29% average utilization. Below it on-demand wins, above it provisioned does — here is the arithmetic and how to find your own number.

Published August 12, 2026 · Last updated August 12, 2026

Somebody chose provisioned capacity for your DynamoDB tables. It was probably the right call at the time, made for the right reason — provisioned is cheaper per unit, and a predictable bill beats a variable one when you are trying to forecast anything. Then the traffic changed, the capacity numbers did not, and nobody has looked at them since. That is where most DynamoDB money goes: not to a bad decision, but to a good decision nobody revisited.

The honest version of the on-demand versus provisioned question has a number in it. Here it is, with the five-minute check that tells you which side of it your tables are on.

What does DynamoDB provisioned capacity actually cost per RCU?

Provisioned mode reserves throughput by the hour, whether or not you use it. At standard us-east-1 rates for the Standard table class:

What you pay forRate
Provisioned read capacity (RCU)$0.00013 per RCU-hour
Provisioned write capacity (WCU)$0.00065 per WCU-hour
On-demand reads (RRU)$0.125 per million read units
On-demand writes (WRU)$0.625 per million write units
Table storage$0.25 per GB-month

Two things in that table drive almost every DynamoDB bill.

Writes cost five times what reads cost, in both modes. A table over-provisioned on writes wastes money five times faster than the same mistake on reads, which is why the write column is where you should look first.

Per-hour billing compounds quietly. One RCU costs $0.0949 a month (730 hours), one WCU costs $0.4745. Those are rounding-error numbers individually, which is exactly how a table ends up at 3,000 RCU: nobody ever felt the increment.

And the multiplier most people forget: global secondary indexes carry their own provisioned capacity, billed separately from the base table at the same rates. A table with three GSIs provisioned at the same capacity as the base table costs four times the base table. If the console shows 500 WCU and your bill implies far more, the indexes are the gap.

Is DynamoDB on-demand or provisioned cheaper?

Most write-ups stop at "provisioned is cheaper if your traffic is predictable", which is true and useless — it does not tell you where the line is. The line is computable, and it is the same for reads and writes.

Take one unit of capacity and run it for a month:

Capacity unitProvisioned for a monthSame throughput fully consumed on-demand
1 RCU730 h × $0.00013 = $0.09492,628,000 RRU × $0.125/M = $0.329
1 WCU730 h × $0.00065 = $0.47452,628,000 WRU × $0.625/M = $1.643

A unit of capacity held for a month can serve 2,628,000 requests — 3,600 seconds an hour, 730 hours. If you actually consumed all of them, on-demand would cost 3.46× what provisioned costs. Both rows give the same ratio, because on-demand and provisioned are both priced with writes at exactly 5× reads.

Invert that and you get the number that matters:

Provisioned only wins above roughly 29% average utilization. ($0.4745 ÷ $1.643 = 0.289.) Below that line you are paying more for reserved capacity than you would pay per request, and the further below it you sit, the worse the deal gets. A table averaging 10% utilization is paying about three times what on-demand would cost for identical traffic.

Three caveats before you take 29% as gospel:

  • Reserved capacity moves the line down. One- and three-year DynamoDB reserved capacity applies to provisioned throughput only, and pushes the break-even well below 29% for the portion you cover. It is also a commitment against a baseline — so clean up before you buy, not after.
  • Auto scaling changes what "utilization" means. A table with application auto scaling holds utilization near its target, typically 70%, during the day and then sits at MinCapacity all night. Its daily average can look terrible while the config is broadly right. The waste is usually a floor set too high, not the target.
  • On-demand absorbs spikes that provisioned throttles. An on-demand table scales to double its previous peak automatically; matching that with fixed capacity means paying for the peak all month.

How do I check consumed vs provisioned capacity in CloudWatch?

This is the five-minute version. Two calls, one division.

First, what is provisioned — including every index, which is the part people miss:

aws dynamodb describe-table --table-name orders \
  --query 'Table.{Mode:BillingModeSummary.BillingMode,RCU:ProvisionedThroughput.ReadCapacityUnits,WCU:ProvisionedThroughput.WriteCapacityUnits,Indexes:GlobalSecondaryIndexes[].{Name:IndexName,RCU:ProvisionedThroughput.ReadCapacityUnits,WCU:ProvisionedThroughput.WriteCapacityUnits}}'

Then, what is consumed. The arithmetic here is where the check usually goes wrong:

aws cloudwatch get-metric-statistics --namespace AWS/DynamoDB \
  --metric-name ConsumedWriteCapacityUnits \
  --dimensions Name=TableName,Value=orders \
  --start-time 2026-07-12T00:00:00Z --end-time 2026-08-11T00:00:00Z \
  --period 86400 --statistics Sum Maximum

ConsumedWriteCapacityUnits is reported as a total over the period, not a rate. With --period 86400, divide each daily Sum by 86,400 to get average units per second — the number that is directly comparable to your provisioned figure. Skip that division and you will conclude that a table consuming 30 WCU/s is consuming 2.6 million, which is a fun way to justify a capacity increase.

Run the same query for ConsumedReadCapacityUnits, and add each GSI by appending a second dimension (Name=GlobalSecondaryIndexName,Value=by-customer), because index consumption is reported separately from the base table's. Sum table and indexes on both sides — provisioned and consumed — or the ratio is meaningless.

One more metric while you are here: ThrottledRequests. A table that is under-utilized on average and occasionally throttled is bursty, not over-provisioned, and the fix for burstiness is on-demand or auto scaling — not a lower fixed number.

What does the math look like on a real table?

Take an orders table on provisioned billing: 400 RCU / 200 WCU on the base table, plus one GSI at the same capacity. Total across both: 800 RCU, 400 WCU.

What it costs today:

  • Reads: 800 × 730 × $0.00013 = $75.92
  • Writes: 400 × 730 × $0.00065 = $189.80
  • Throughput total: $265.72/month

What CloudWatch says it uses. Daily Sum for reads averages 5,184,000 units, and 2,592,000 for writes. Divide by 86,400: 60 RCU/s and 30 WCU/s. That is 7.5% utilization on both meters.

What on-demand would cost for exactly that traffic:

  • Reads: 60 × 2,628,000 s = 157.68M RRU × $0.125/M = $19.71
  • Writes: 30 × 2,628,000 s = 78.84M WRU × $0.625/M = $49.28
  • Total: $68.99/month — a 74% cut, with no capacity tuning ever again.

What rightsizing would cost instead. Say the busiest day averages 90 RCU/s and 45 WCU/s. Provision twice the peak — 180 RCU / 90 WCU — and you get $17.08 + $42.71 = $59.79/month, slightly better than on-demand.

That is the real shape of the decision. Rightsized provisioned capacity is the cheapest option if you keep it rightsized, because 60 ÷ 180 = 33% average utilization puts the table back over the 29% line. On-demand costs about 15% more and never needs to be revisited. Storage is $0.25/GB-month either way and does not enter the comparison.

How do I reduce DynamoDB cost?

In rough order of savings per unit of effort:

Switch tables under ~$50/month with very low utilization to on-demand. Below single-digit utilization the arithmetic is not close, and the operational win — no capacity planning, no auto scaling config, no 3 a.m. throttling — is worth more than the last few dollars.

Rightsize the rest to roughly 2× observed peak, using peak-day averages, not means. Reducing capacity is a control-plane update that applies in seconds with no downtime and no data risk.

Audit your GSIs before your tables. Indexes are the most common source of surprise capacity, and an index nobody queries is 100% waste — its capacity and the write amplification on every base-table update that touches a projected attribute.

Lower MinCapacity if auto scaling is already on. For an auto-scaled table the overnight floor is where the money sits, not the daytime target.

One case where all of this is wrong: if you already own reserved capacity, it is paid for regardless. Cutting provisioned units below what you reserved saves nothing and strands the reservation. Check your reserved capacity before you touch a single table.

What else should I check while you are in here?

Over-provisioned DynamoDB is one instance of the most expensive pattern in AWS — capacity sized for a peak that stopped happening. The same shape shows up in relational databases, where the numbers are usually larger and the fear of changing them is greater; right-sizing RDS without downtime covers the metrics that prove an instance is oversized and how Multi-AZ makes the change safe.

While you have the CloudWatch console open for ConsumedReadCapacityUnits, it is worth glancing at what CloudWatch itself is charging you. Teams that publish per-table custom metrics or ship verbose application logs alongside a busy DynamoDB workload frequently find the observability bill rivals the database one — why CloudWatch gets so expensive breaks the four meters apart.

If DynamoDB is the first thing you have looked at, it is probably not the biggest. Our walk-through of where a small team's AWS bill leaks covers the account in order of savings-per-minute, and rightsizing generally sits below deleting things nobody uses.

How do I keep DynamoDB capacity from drifting out of line again?

Doing this by hand for one table is twenty minutes. Doing it for every table and every index in every region — matching provisioned against consumed, dividing sums by 86,400, separating a bursty table from a genuinely idle one, and then repeating it next quarter when traffic has moved again — is the part that never happens twice. That is what a scan is for. Parsivex checks each region, compares 30 days of consumed capacity against what is provisioned across tables and their indexes, and then keeps watching, so a table that drifts back out of line after your cleanup gets flagged instead of quietly running up the same bill again.

For what this finding means once it appears in your report, including the thresholds behind it, see Over-provisioned DynamoDB capacity and how savings estimates are calculated.