Skip to main content
emnode
Cost

Switch DynamoDB tables between on-demand and provisioned capacity

DynamoDB has two billing modes priced an order of magnitude apart, and picking the wrong one per table is one of the quietest, most common ways to overpay for a database that's working perfectly.

14 min·10 sections·AWS

Last reviewed

DynamoDB capacity modes: the basics

Two ways to pay for the same table

Every DynamoDB table bills under one of two capacity modes. On-demand charges per request: roughly $0.625 per million write request units and $0.125 per million read request units in US-East, with zero capacity planning. You provision nothing and pay only for the reads and writes that actually happen. Provisioned charges for reserved throughput by the hour: about $0.00065 per write-capacity-unit-hour and $0.00013 per read-capacity-unit-hour, billed whether you use the capacity or not. The table behaves identically either way; only the meter changes.

The trade is utilisation for predictability. On-demand is the right default for spiky, new, dev/test, or unpredictable workloads: you never overpay for idle capacity and you never get throttled by a bad capacity estimate. Provisioned is far cheaper per request at steady, predictable load (roughly 3.5x cheaper per million operations when the reserved capacity is fully utilised, and 6 to 7x once you layer DynamoDB Reserved Capacity on top) because you're buying throughput wholesale instead of retail. The catch is that you pay for that reserved capacity around the clock, used or not.

The check flags tables whose billing mode doesn't match their traffic shape. The overwhelmingly common case is a high-volume, steady-state production table left on on-demand "for simplicity": running 24/7 at predictable load while paying the retail per-request rate. The fix isn't always to switch; it's to read the table's real consumed-vs-provisioned utilisation and choose deliberately. The breakeven heuristic: if sustained utilisation of the equivalent provisioned capacity would clear roughly 30%, provisioned (with auto-scaling) usually wins; below that, on-demand is both cheaper and safer.

In this lesson you'll learn the two DynamoDB billing meters and the math that separates them, how to read a table's real consumed-versus-provisioned utilisation from CloudWatch to decide which mode is cheaper, and how to switch modes safely given AWS's switch limits: you can move provisioned to on-demand up to four times in a 24-hour rolling window, and on-demand to provisioned at any time. You'll see the AWS CLI commands to inspect billing mode and consumed capacity, switch a table to provisioned, and configure auto-scaling to a sensible target utilisation, plus why DynamoDB Reserved Capacity only applies after you've moved to provisioned, which fixes the correct order of operations: choose mode, tune provisioned, then reserve the floor.

Fun fact

The 30% line that flips the answer

There's a clean breakeven hiding in DynamoDB's price sheet. A write request unit on-demand costs about $0.625 per million, so 3,600 writes cost about $0.00225; a provisioned write-capacity-unit costs about $0.00065 per hour and serves up to 3,600 writes in that hour at sustained rate. Divide $0.00065 by $0.00225 and provisioned beats on-demand once you'd sustain roughly 30% of the provisioned capacity you'd need to cover your peak. At full utilisation that's a roughly 3.5x per-unit gap on plain provisioned; layer DynamoDB Reserved Capacity on top and the gap widens to the headline 6 to 7x. Below the breakeven line on-demand is genuinely cheaper; above it provisioned with auto-scaling wins, often by a lot. The trap is that almost nobody computes the line: they pick on-demand on day one when the table is spiky and correct, then never recheck once the workload goes steady and the answer has flipped.

Switching capacity mode in action

Marcus runs the FinOps cadence at a logistics company. The dashboard flags a DynamoDB table, orders-events, at about $2,100 a month on on-demand: the single largest DynamoDB line in the account. The table backs the order-event stream, which has run steadily for eighteen months: high volume, very little day-night variation, no real spikes.

He pulls the billing mode to confirm it's on-demand, then pulls two weeks of CloudWatch ConsumedWriteCapacityUnits. The table sustains around 1,400 write units per second almost flat, with peaks barely touching 1,900. That's exactly the profile on-demand is the wrong answer for: predictable, steady, and high-volume. He sizes provisioned capacity at roughly 2,200 WCU to cover peak with headroom, then checks the math: at provisioned rates plus auto-scaling, the same throughput lands near $340 a month.

Marcus doesn't flip it blind on a Friday. He switches orders-events to provisioned with auto-scaling at a 70% target utilisation, and watches ConsumedWriteCapacityUnits against the provisioned line for a day to confirm no throttling. He knows he has a safety valve (provisioned can be switched back to on-demand up to four times in a rolling 24 hours) but he'd rather not burn throughput re-transitioning the table, so he sizes for peak with headroom and gets it right on the first move. Once it's stable, he hands the table to the commitments review so the steady floor (about 1,200 WCU that never drops) can be covered by DynamoDB Reserved Capacity for a further discount. Sequence: choose mode, tune provisioned, then reserve.

First, confirm the table's billing mode and read the recent consumed-capacity metrics DynamoDB exposes on the table itself.

$ aws dynamodb describe-table --table-name orders-events --query '{Mode:Table.BillingModeSummary.BillingMode,RCU:Table.ProvisionedThroughput.ReadCapacityUnits,WCU:Table.ProvisionedThroughput.WriteCapacityUnits,Items:Table.ItemCount}'
{
"Mode": "PAY_PER_REQUEST",
"RCU": 0,
"WCU": 0,
"Items": 184203117
}
# PAY_PER_REQUEST = on-demand. RCU/WCU read 0 because nothing is reserved.
# 184M items on a steady high-volume table - a prime provisioned candidate.

BillingModeSummary confirms on-demand; the zeroed RCU/WCU are expected for pay-per-request.

Now pull two weeks of consumed write capacity to confirm the traffic is steady: the signal that makes provisioned the cheaper choice.

$ aws cloudwatch get-metric-statistics --namespace AWS/DynamoDB --metric-name ConsumedWriteCapacityUnits --dimensions Name=TableName,Value=orders-events --start-time $(date -u -d '14 days ago' +%FT%TZ) --end-time $(date -u +%FT%TZ) --period 86400 --statistics Average Maximum
{
"Datapoints": [
{ "Timestamp": "2026-05-12T00:00:00Z", "Average": 1411.2, "Maximum": 1903.0, "Unit": "Count" },
{ "Timestamp": "2026-05-13T00:00:00Z", "Average": 1388.7, "Maximum": 1871.4, "Unit": "Count" },
{ "Timestamp": "2026-05-14T00:00:00Z", "Average": 1402.9, "Maximum": 1888.6, "Unit": "Count" }
]
}
# Avg ~1,400 WCU, peak <1,950, flat day over day - steady, predictable, provisionable.

Steady ConsumedWriteCapacityUnits with little day-to-day variance is the textbook provisioned signal.

Capacity modes under the hooddeep dive

DynamoDB measures work in capacity units. One write-capacity-unit (WCU) covers one write per second of an item up to 1 KB; one read-capacity-unit (RCU) covers one strongly-consistent read per second up to 4 KB (or two eventually-consistent reads). On-demand bills the same work as request units after the fact: a write request unit and a read request unit are the on-demand equivalents, charged per million. The physical work is identical; only the accounting differs. On-demand at roughly $0.625 per million WRU costs about $0.00225 for the 3,600 writes one provisioned WCU sustains in an hour, against $0.00065 per WCU-hour provisioned, about a 3.5x gap at full utilisation on plain provisioned. Layer DynamoDB Reserved Capacity on top of provisioned and that gap widens to the headline roughly 6 to 7x.

Switching modes is a metadata operation on the table (no data movement, no downtime) but AWS caps how often you can do it: you can switch provisioned to on-demand up to four times in a 24-hour rolling window, while on-demand to provisioned can be done at any time. The practical constraint runs in the provisioned-to-on-demand direction: if you switch to provisioned, undersize the capacity, and start throttling, you can switch back to on-demand (you have up to four such switches in a rolling 24 hours) but you'd rather not spend those transitions firefighting. So the safe pattern is to size provisioned capacity to comfortably cover observed peak, enable auto-scaling before or with the switch, and watch ConsumedWriteCapacityUnits against the provisioned ceiling before walking away. New tables can also be created on-demand and switched once they have a traffic history to size against.

Provisioned auto-scaling adjusts the reserved RCU/WCU between a min and max to track a target utilisation, typically 70%. It registers the table as a scalable target with Application Auto Scaling and attaches a target-tracking policy on DynamoDBWriteCapacityUtilization / DynamoDBReadCapacityUtilization. Set the target too high (90%+) and brief spikes throttle before scaling reacts; too low (40%) and you pay for idle headroom, eroding the advantage over on-demand. Crucially, DynamoDB Reserved Capacity (a one or three year commitment that further discounts provisioned RCU/WCU) only applies to provisioned mode, never on-demand. That fixes the order of operations: choose the mode, tune the provisioned auto-scaling, then reserve the steady floor.

# Confirm the current mode and any provisioned throughput on the table.
aws dynamodb describe-table --table-name orders-events \
  --query 'Table.{Mode:BillingModeSummary.BillingMode,RCU:ProvisionedThroughput.ReadCapacityUnits,WCU:ProvisionedThroughput.WriteCapacityUnits}'

# Pull two weeks of consumed capacity to size the switch against real peaks.
aws cloudwatch get-metric-statistics \
  --namespace AWS/DynamoDB \
  --metric-name ConsumedReadCapacityUnits \
  --dimensions Name=TableName,Value=orders-events \
  --start-time $(date -u -d '14 days ago' +%FT%TZ) \
  --end-time $(date -u +%FT%TZ) \
  --period 3600 --statistics Average Maximum \
  --query 'Datapoints | sort_by(@,&Timestamp)[-24:]'

What is the impact of running the wrong capacity mode?

The direct cost is the multiple you pay for identical performance. A steady table sustaining 1,400 WCU and 2,000 RCU runs near $2,100 a month on-demand; the same throughput provisioned with sensible auto-scaling lands around $340. That's roughly 6x on a single table, recurring every month, with nothing visibly wrong: the database is fast and reliable the whole time. Across a fleet of mature production tables left on the default, this compounds into tens of thousands of dollars a month of pure pricing-mode overspend.

The cost runs the other way too. Putting a spiky, unpredictable, or low-volume table on provisioned is its own trap: you either oversize the reserved capacity and pay for idle headroom around the clock, or you undersize it and start throttling real traffic. You can switch a struggling table back to on-demand (up to four times in a rolling 24 hours) but each transition takes minutes and burns a re-mode you'd rather save, so the real cost is the firefighting and the throttled requests in between. "Always provisioned" is as wrong as "always on-demand." The whole point is that the right answer is per-table and depends on traffic shape.

There's a commitment interaction that surprises teams. DynamoDB Reserved Capacity discounts provisioned throughput further on a one or three year basis, but it only applies to provisioned mode. A table left on on-demand can't benefit from any reserved-capacity discount at all, so the convenience default doesn't just cost the retail per-request rate, it also locks the table out of the deepest discount tier entirely. Right-mode-first, then reserve, is the only sequence that captures both savings.

Finally, mode mismatches distort cost attribution and forecasting. On-demand cost moves directly with traffic, which looks like clean business-correlated spend, so a steady table on the wrong mode hides in plain sight as "growth." Provisioned cost is flatter and more forecastable. Getting each table onto the mode that matches its traffic shape doesn't just lower the bill; it makes the DynamoDB line predictable enough to budget against and to charge back honestly.

How do you pick and switch capacity mode safely?

Right-moding a table is a four-step loop on the FinOps cadence: read the real utilisation, decide the mode against the breakeven line, switch with auto-scaling and respect the switch-frequency limits, then reserve the steady floor.

1. Read consumed-versus-provisioned utilisation honestly

Pull at least 14 days of ConsumedReadCapacityUnits and ConsumedWriteCapacityUnits from CloudWatch for every top-spend table. What you're looking for is the shape: steady and high (provisioned candidate), spiky or low (leave on-demand), or genuinely unpredictable (on-demand for safety). Average tells you the floor; peak tells you the capacity you must cover. Eyeballing the console cost number isn't enough; the decision lives in the traffic shape, not the dollar total.

2. Decide against the breakeven, not by gut

Apply the heuristic: if sustained utilisation of the provisioned capacity you'd need to cover peak would clear roughly 30%, provisioned with auto-scaling almost always wins; below that, on-demand is both cheaper and safer against spikes. New, low-volume, dev/test, and bursty tables belong on on-demand and should stay there; "always provisioned" is as much a mistake as "always on-demand." The output of this step is a per-table decision with the projected cost under each mode written down.

3. Switch with auto-scaling, and respect the switch-frequency limits

Switching mode is a no-downtime metadata change. On-demand to provisioned can be done at any time; provisioned to on-demand is capped at four switches in a rolling 24 hours, so you have a safety valve if a provisioned switch goes wrong, but it's not unlimited and re-transitioning takes minutes. Don't switch to provisioned with a hand-guessed capacity. Enable auto-scaling at a roughly 70% target utilisation, set min capacity near the observed floor and max comfortably above observed peak, then watch consumed-versus-provisioned for a day before walking away. Sizing for peak with headroom on the first move is exactly how you avoid spending those re-mode switches firefighting throttled traffic.

4. Reserve the steady floor, in that order

Only once a table is on provisioned and stable does DynamoDB Reserved Capacity apply: it discounts provisioned RCU/WCU on a 1 or 3 year term and never touches on-demand. So the sequence is fixed: choose mode, tune the provisioned auto-scaling to find the real floor, then buy reserved capacity to cover the part of that floor that never drops. Reserving before tuning over-commits to the wrong shape; staying on on-demand forfeits the discount entirely.

# Switch the table to provisioned with a peak-covering starting capacity.
aws dynamodb update-table \
  --table-name orders-events \
  --billing-mode PROVISIONED \
  --provisioned-throughput ReadCapacityUnits=3000,WriteCapacityUnits=2200

# Register write capacity as a scalable target, then target-track at 70%.
aws application-autoscaling register-scalable-target \
  --service-namespace dynamodb \
  --resource-id table/orders-events \
  --scalable-dimension dynamodb:table:WriteCapacityUnits \
  --min-capacity 1200 --max-capacity 2400

aws application-autoscaling put-scaling-policy \
  --service-namespace dynamodb \
  --resource-id table/orders-events \
  --scalable-dimension dynamodb:table:WriteCapacityUnits \
  --policy-name orders-events-wcu-70 \
  --policy-type TargetTrackingScaling \
  --target-tracking-scaling-policy-configuration \
    'TargetValue=70.0,PredefinedMetricSpecification={PredefinedMetricType=DynamoDBWriteCapacityUtilization}'

Quick quiz

Question 1 of 5

A production table has run 18 months at a flat ~1,400 WCU with peaks near 1,900, currently on on-demand at about $2,100/month. You also want to apply DynamoDB Reserved Capacity to lower it further. What's the right sequence?

You've completed Switch DynamoDB tables between on-demand and provisioned capacity. You now know the two billing meters and the gap between them (about 3.5x on plain provisioned at full utilisation, widening to roughly 6 to 7x once Reserved Capacity is layered on) how to read consumed-versus-provisioned utilisation to choose deliberately, the breakeven line around 30% sustained utilisation, and the safe switch path that respects the switch-frequency limits (provisioned to on-demand up to four times in a rolling 24 hours, on-demand to provisioned at any time) and auto-scales to a 70% target. Most importantly you know the sequence (choose mode, tune provisioned, then reserve the floor) so the next time the dashboard flags a steady table on on-demand you'll have a defensible path from "flagged" to "right-moded."

Back to the library