Skip to main content
emnode / learn
Cost

Switch DynamoDB tables between on-demand and provisioned capacity

DynamoDB has two billing modes priced an order of magnitude apart — 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 utilization 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 6–7× cheaper per million operations when the reserved capacity is well utilized — 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 utilization and choose deliberately. The breakeven heuristic: if sustained utilization of the equivalent provisioned capacity would clear roughly 14–18%, 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 utilization from CloudWatch to decide which mode is cheaper, and how to switch modes safely given the once-per-24-hours limit. 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 utilization — 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 18% 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; a provisioned write-capacity-unit costs about $0.00065 per hour, and one WCU can serve up to 3,600 writes per hour at sustained rate. Run the arithmetic and provisioned beats on-demand once you'd sustain roughly 14–18% of the provisioned capacity you'd need to cover your peak. Below that 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 utilization, watches ConsumedWriteCapacityUnits against the provisioned line for a day to confirm no throttling, and notes that he can't switch the mode back for 24 hours if it goes wrong. 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 versus provisioned at roughly $0.00065 per WCU-hour is where the ~6–7× gap at high utilization comes from, because one provisioned WCU sustained for an hour serves up to 3,600 writes for a fraction of a cent.

Switching modes is a metadata operation on the table — no data movement, no downtime — but AWS allows it at most once every 24 hours per table. That window is the single most important operational constraint: if you switch to provisioned, undersize the capacity, and start throttling, you cannot switch back to on-demand for a day. 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 utilization, 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 6× 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 — and then you're stuck with the bad capacity for up to 24 hours before you can switch back. "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 utilization, decide the mode against the breakeven line, switch with auto-scaling and respect the 24-hour limit, then reserve the steady floor.

1. Read consumed-versus-provisioned utilization 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 utilization of the provisioned capacity you'd need to cover peak would clear roughly 14–18%, 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 24-hour limit

Switching mode is a no-downtime metadata change, but you get one switch per table per 24 hours — so don't switch to provisioned with a hand-guessed capacity. Enable auto-scaling at a ~70% target utilization, set min capacity near the observed floor and max comfortably above observed peak, then watch consumed-versus-provisioned for a day before walking away. If it throttles, you're committed for 24 hours, which is exactly why you size for peak with headroom on the first move.

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 ~6–7× gap between them, how to read consumed-versus-provisioned utilization to choose deliberately, the breakeven line around 14–18% sustained utilization, and the safe switch path that respects the once-per-24-hours limit 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