Skip to main content
emnode / learn
Cost

Purchase DynamoDB Reserved Capacity

If your high-volume DynamoDB tables run in provisioned mode with steady throughput, reserved capacity buys the same RCUs and WCUs for up to ~77% less — but it does nothing for On-Demand tables.

14 min·10 sections·AWS

Last reviewed

DynamoDB Reserved Capacity: the basics

A commitment that only works if your tables are in the right mode

DynamoDB Reserved Capacity is a billing commitment against throughput, not storage. You commit to a block of provisioned Read Capacity Units (RCUs) and Write Capacity Units (WCUs) in a single AWS region for one or three years, and in exchange AWS discounts the hourly rate on that capacity by up to roughly 77% versus standard provisioned pricing. Nothing about a table changes — the discount is applied behind the scenes against the provisioned capacity you're already running in that region.

There is one hard prerequisite that makes or breaks the whole decision: reserved capacity only applies to tables in provisioned capacity mode. It does absolutely nothing for On-Demand (pay-per-request) tables — those bill per request and reserved RCU/WCU blocks never match them. So step 0 is not "how much do I reserve?" but "are my high-volume tables in provisioned mode with stable, predictable throughput?" If they're On-Demand, the right first move is the capacity-mode lesson, not this one.

Reserved capacity is region-scoped and pooled: a block of reserved RCUs/WCUs applies across all provisioned tables in that region and account, not to one named table. That makes it well-suited to a fleet of steady, always-on tables sharing a baseline. It is sold in three payment shapes — like other AWS reservations, more upfront means a deeper discount. And critically, you buy it through the console or from a Cost Explorer recommendation; there is no rich purchase API the way EC2 and RDS expose one.

In this lesson you'll learn why DynamoDB reserved capacity is gated entirely on capacity mode, how it's scoped (region-wide pools of RCUs and WCUs, throughput only, never storage), and the sequence that protects you — confirm provisioned mode, right-size, enable auto-scaling, then reserve the floor. You'll see the CLI calls that actually exist for this workflow: aws ce get-reservation-purchase-recommendation to surface a recommendation and aws dynamodb describe-table to compare consumed versus provisioned throughput — and you'll learn why the purchase itself happens in the console, not a rich CLI API. You'll also see the traps: reserving against On-Demand tables, committing before right-sizing, and forgetting that storage is never covered.

Fun fact

The reservation that covered nothing

DynamoDB reserved capacity is one of the deepest discounts in AWS — up to roughly 77% off provisioned throughput on a three-year commitment. A team saw that number, saw a six-figure annual DynamoDB bill, and bought a large block of reserved WCUs to capture it. The problem: their highest-volume tables had been migrated to On-Demand mode the year before to handle spiky traffic, and reserved capacity does nothing for On-Demand tables. The reservation matched almost none of their actual spend. They paid the full upfront commitment and the full On-Demand bill, side by side, for the entire term — a 77% discount that delivered close to 0% savings because nobody checked the capacity mode first.

Buying DynamoDB reserved capacity in action

Dana runs the platform team at a high-traffic consumer app. The FinOps dashboard shows about $22k/month of DynamoDB spend, and most of it is provisioned-throughput charges on a handful of always-on tables — a sessions table, an events ledger, a user-profile store — that have run the same steady shape for over a year. None of it is committed.

Before reserving a unit, Dana does the prerequisite work. She confirms the big tables are actually in provisioned mode (one borderline table was on On-Demand and stays there because its traffic is genuinely spiky). She right-sizes the provisioned RCUs/WCUs down on two tables that were padded for a launch that never spiked, and enables auto-scaling on all of them so day-to-day peaks are handled above the reserved floor.

Only then does she pull the Cost Explorer recommendation. It suggests a three-year reservation covering the steady baseline of provisioned write capacity, projecting roughly 60% savings on that slice. Dana decides to reserve ~80% of the stable floor — leaving auto-scaling to absorb the variable top On-Demand-style — and completes the purchase in the DynamoDB console, since there's no rich CLI purchase API. She notes the renewal date on the calendar and adds coverage and utilisation to the monthly pack.

First, ask Cost Explorer what it recommends reserving for DynamoDB. Scope it to the DynamoDB service so you don't get EC2 or RDS noise.

$ aws ce get-reservation-purchase-recommendation --service "Amazon DynamoDB" --term-in-years THREE_YEARS --payment-option PARTIAL_UPFRONT --lookback-period-in-days SIXTY_DAYS --query 'Recommendations[0].RecommendationDetails[0]'
{
"InstanceDetails": {
"DynamoDBCapacityDetails": {
"CapacityUnits": "WriteCapacityUnit",
"Region": "us-east-1"
}
},
"RecommendedNumberOfCapacityUnitsToPurchase": "12000",
"EstimatedMonthlySavingsAmount": "4380.00",
"EstimatedSavingsPercentage": "60.4",
"EstimatedMonthlyOnDemandCost": "7250.00",
"MinimumNumberOfCapacityUnitsUsedPerHour": "11800"
}
# Recommendation is region-scoped and per capacity type (WCU here) — and only counts PROVISIONED usage.

The recommendation is a region-wide block of one capacity type — and it only reflects tables already in provisioned mode.

Before reserving, check that the table is in provisioned mode and compare consumed throughput to what's provisioned, so you reserve the real floor and not padded headroom.

$ aws dynamodb describe-table --table-name sessions --query 'Table.{Name:TableName,Mode:BillingModeSummary.BillingMode,RCU:ProvisionedThroughput.ReadCapacityUnits,WCU:ProvisionedThroughput.WriteCapacityUnits}'
{
"Name": "sessions",
"Mode": "PROVISIONED",
"RCU": 8000,
"WCU": 12000,
}
# Mode=PROVISIONED → eligible. If this read "PAY_PER_REQUEST", reserved capacity would do nothing.
# Pull ConsumedWriteCapacityUnits from CloudWatch to confirm 12000 is the real floor, not launch-day padding.

BillingMode is the make-or-break field: PROVISIONED is eligible, PAY_PER_REQUEST (On-Demand) is not.

DynamoDB reserved capacity under the hooddeep dive

DynamoDB bills throughput in capacity units and storage separately. In provisioned mode you set Read Capacity Units (RCUs) and Write Capacity Units (WCUs) and pay an hourly rate per unit — in rough US-East terms about $0.00013 per WCU-hour and $0.00013 per RCU-hour (roughly $0.47/WCU-month and $0.09/RCU-month at standard provisioned rates). In On-Demand mode you instead pay per request (per million read/write request units) with no provisioned floor. Reserved capacity is a commitment against the provisioned hourly rate, so it can only ever match capacity units on tables running in provisioned mode — On-Demand tables bill on a different meter the reservation never touches.

A reservation is matched by just two attributes: the AWS region and the capacity type (RCU or WCU). It is not bound to a named table. AWS pools all provisioned RCUs (and, separately, all WCUs) across every table in that region and account, then applies the reserved block against that pool first, billing only the overflow at standard rates. That's why reserved capacity suits a fleet of steady, always-on tables sharing a baseline: you reserve the floor of total regional throughput, not any single table. Storage — the GB-month charge for the data itself — is on yet another meter and is never covered by reserved capacity.

This is why capacity mode and right-sizing must come before the commitment. Reserved capacity is non-refundable and matched only to provisioned usage, so committing against a table you later flip to On-Demand strands the reservation entirely. Committing before right-sizing locks in padded headroom you didn't need. And unlike EC2 or RDS, there is no rich purchase-reserved-* CLI API for DynamoDB — the buy is done through the DynamoDB console (Reserved capacity) or accepted from a Cost Explorer recommendation. The CLI's role here is analysis: surface the recommendation, confirm the mode, and measure consumed-versus-provisioned before a human commits in the console.

# Confirm the billing mode of every table before reserving anything.
# Reserved capacity ONLY applies to tables whose mode is PROVISIONED.
for t in $(aws dynamodb list-tables --query 'TableNames[]' --output text); do
  mode=$(aws dynamodb describe-table --table-name "$t" \
    --query 'Table.BillingModeSummary.BillingMode' --output text)
  echo "$t -> ${mode:-PROVISIONED}"
done

# Pull the real consumed write floor over the last 14 days (provisioned tables only),
# so you reserve the baseline rather than launch-day padding.
aws cloudwatch get-metric-statistics \
  --namespace AWS/DynamoDB --metric-name ConsumedWriteCapacityUnits \
  --dimensions Name=TableName,Value=sessions \
  --start-time "$(date -u -d '14 days ago' +%FT%TZ)" --end-time "$(date -u +%FT%TZ)" \
  --period 3600 --statistics Minimum Average

# NOTE: there is no rich CLI purchase API for DynamoDB reserved capacity.
# Complete the purchase in the DynamoDB console under "Reserved capacity".

What is the impact of leaving DynamoDB uncommitted?

The direct impact is paying full standard rates on a workload that's usually one of the steadiest in the estate. At roughly $0.47 per WCU-month and $0.09 per RCU-month standard provisioned, a fleet running 12,000 WCUs and 8,000 RCUs is north of $6k/month before storage. A three-year commitment drops the throughput rate by up to ~77% — on a steady baseline that's several thousand dollars a month, tens of thousands a year, of discount that AWS offers and no one is claiming because nobody owns the DynamoDB commitment decision.

The first trap is mode. Reserved capacity does nothing for On-Demand (pay-per-request) tables, and many estates have quietly migrated their highest-volume tables to On-Demand to absorb spiky traffic. A reservation bought without checking mode matches almost nothing — you pay the upfront commitment and the full On-Demand bill side by side. The eligibility question is binary and it comes before any sizing math: a table in PAY_PER_REQUEST mode is invisible to reserved capacity no matter how much you commit.

The second trap is the stranding risk, which runs the opposite direction. Reserved capacity is non-refundable and region-and-type scoped. Commit before right-sizing and you lock in padded throughput. Commit and then flip a table to On-Demand, or move the workload to another region, and the reservation bills against nothing while the new shape pays full rate. Over-buy beyond the steady floor — reserving the peak instead of the baseline — and the unused units sit idle every hour for the full term. The deep discount and the deep trap are two sides of the same rigidity.

Finally, there's a scope surprise: reserved capacity covers throughput only, never storage. A large table with modest throughput but terabytes of data will see its throughput line drop and its storage line stay exactly where it was — so the bill moves less than a naive "77% off DynamoDB" expectation. Setting that expectation correctly matters: the reservation is a throughput instrument, and storage right-sizing (TTL, archiving cold data, table-class choice) is a separate lever entirely.

How do you commit to DynamoDB reserved capacity safely?

Buying DynamoDB reserved capacity is a four-step sequence, and the order is the whole point: confirm the tables are in provisioned mode, right-size and enable auto-scaling, reserve only the steady floor, then review coverage and utilisation every month.

1. Confirm provisioned mode BEFORE anything else

Reserved capacity does nothing for On-Demand (PAY_PER_REQUEST) tables. List every table's BillingMode and separate the provisioned, steady tables (eligible) from the On-Demand ones (not eligible — and often On-Demand for good reason, like genuinely spiky traffic). If your high-volume tables are On-Demand but their traffic is actually stable, the right first move is the capacity-mode lesson — switch them to provisioned with auto-scaling — not a reservation. Reserving against an On-Demand fleet is the single most expensive mistake in this category.

2. Right-size and enable auto-scaling, then find the floor

Reservations lock in a throughput level, so fix the level first. Pull consumed RCU/WCU from CloudWatch over the trailing 14–60 days and right-size provisioned capacity down to the real baseline, removing launch-day padding. Enable application auto-scaling so day-to-day peaks are handled above the reserved floor. The reserved block should cover the always-on baseline; auto-scaling absorbs the variable top at standard rates.

3. Reserve the stable floor, not the peak, and choose payment deliberately

Reserve roughly 80% of the steady provisioned floor in each region, per capacity type (RCU and WCU are separate), leaving the variable top uncovered so a future right-size or mode change doesn't strand units. Choose the payment option as a cash decision — more upfront deepens the discount, less upfront preserves capital — and the term (1 vs 3 years) by how confident you are the shape persists. Remember the purchase happens in the DynamoDB console (or by accepting a Cost Explorer recommendation); there's no rich CLI purchase API.

4. Review coverage and utilisation on every FinOps cadence

Track two numbers monthly: coverage (share of provisioned capacity-hours covered by a reservation on the eligible fleet) and utilisation (share of reserved units actually consumed; must stay near 100%). Watch expirations 60–90 days out so renewals are deliberate. If utilisation drops, a table was right-sized below the floor or flipped to On-Demand and stranded the reservation — investigate before buying anything new. Don't expect the storage line to move; that's a separate lever.

# Step 1: confirm eligibility — which tables are PROVISIONED (reservable) vs On-Demand?
for t in $(aws dynamodb list-tables --query 'TableNames[]' --output text); do
  mode=$(aws dynamodb describe-table --table-name "$t" \
    --query 'Table.BillingModeSummary.BillingMode' --output text)
  printf '%-30s %s\n' "$t" "${mode:-PROVISIONED}"
done

# Step 2/3: pull the Cost Explorer recommendation for the eligible provisioned baseline,
# comparing payment options before a human commits in the console.
for opt in NO_UPFRONT PARTIAL_UPFRONT ALL_UPFRONT; do
  echo "== $opt =="
  aws ce get-reservation-purchase-recommendation \
    --service "Amazon DynamoDB" \
    --term-in-years THREE_YEARS \
    --payment-option $opt \
    --lookback-period-in-days SIXTY_DAYS \
    --query 'Recommendations[0].RecommendationSummary.{Savings:TotalEstimatedMonthlySavingsAmount,Pct:TotalEstimatedMonthlySavingsPercentage}'
done

# Step 3 (purchase): no rich CLI API exists — buy in the DynamoDB console under "Reserved capacity".

Quick quiz

Question 1 of 5

Your highest-volume DynamoDB tables are in On-Demand (pay-per-request) mode to absorb spiky traffic, but analysis shows their traffic is actually steady and predictable. You want to capture the reserved-capacity discount. What's the right move?

Keep learning

Dig deeper into DynamoDB capacity modes, reserved-capacity mechanics, and how it fits the broader commitment strategy.

You've completed Purchase DynamoDB Reserved Capacity. You now know why the discount is gated entirely on capacity mode — provisioned only, never On-Demand — how reserved capacity is scoped as a region-wide pool of RCUs or WCUs that covers throughput but never storage, and the sequence that protects you: confirm provisioned mode, right-size and enable auto-scaling, reserve the stable floor, and review coverage and utilisation. You also know the buy happens in the console, not a rich CLI API. The next time the bill shows a large, uncommitted DynamoDB fleet, you'll have a defensible path from standard rates to a sound multi-year commitment without stranding a unit.

Back to the library