DynamoDB's two capacity modes solve the same problem — how much read/write throughput to provision — with opposite cost curves, and picking the wrong one for a given table's actual traffic pattern is one of the more common, least-noticed sources of DynamoDB overspend, because both modes look reasonable in isolation and the mistake only becomes visible when you compare the bill against what the other mode would have cost for the same traffic.
The Actual Pricing Math
On-demand mode bills per request — no capacity to provision, no throttling risk from under-provisioning, and no cost for idle capacity, at a per-request price that's roughly 5-7x the equivalent provisioned-capacity price at steady utilization. Provisioned mode bills for capacity reserved whether or not it's used, at a much lower per-unit rate, with the tradeoff that under-provisioning throttles requests and over-provisioning wastes money on unused capacity.
The crossover point is traffic predictability, not traffic volume. A table with highly variable, spiky, or unpredictable traffic is often cheaper on-demand even at meaningful volume, because provisioned capacity sized for the peak sits mostly idle the rest of the time. A table with steady, predictable traffic is almost always cheaper provisioned, because the on-demand per-request premium compounds against consistent volume with no offsetting idle-capacity waste.
# Pull consumed capacity trend to evaluate predictability before choosing a mode
aws cloudwatch get-metric-statistics \
--namespace AWS/DynamoDB \
--metric-name ConsumedReadCapacityUnits \
--dimensions Name=TableName,Value=production-orders \
--start-time "$(date -u -d '14 days ago' +%Y-%m-%dT%H:%M:%S)" \
--end-time "$(date -u +%Y-%m-%dT%H:%M:%S)" \
--period 3600 \
--statistics Average Maximum
A large gap between average and maximum consumed capacity over the period indicates spiky, unpredictable traffic — a candidate for on-demand or, if the spikes are on a predictable schedule, provisioned with scheduled scaling. A small gap indicates steady traffic — a strong candidate for provisioned capacity with modest auto-scaling headroom.
Provisioned Mode's Real Trap: Auto-Scaling Lag
Provisioned capacity with application auto-scaling enabled sounds like it should handle variability automatically, but DynamoDB auto-scaling reacts to sustained utilization changes over several minutes — it isn't instantaneous, and a sudden traffic spike can throttle requests before auto-scaling catches up and provisions additional capacity.
# terraform: provisioned table with auto-scaling — headroom matters more than the target
resource "aws_appautoscaling_target" "dynamodb_read" {
max_capacity = 4000
min_capacity = 100
resource_id = "table/production-orders"
scalable_dimension = "dynamodb:table:ReadCapacityUnits"
service_namespace = "dynamodb"
}
resource "aws_appautoscaling_policy" "dynamodb_read_policy" {
name = "dynamodb-read-scaling"
policy_type = "TargetTrackingScaling"
resource_id = aws_appautoscaling_target.dynamodb_read.resource_id
scalable_dimension = aws_appautoscaling_target.dynamodb_read.scalable_dimension
service_namespace = aws_appautoscaling_target.dynamodb_read.service_namespace
target_tracking_scaling_policy_configuration {
target_value = 70.0 # target utilization — headroom below 100% absorbs scaling lag
predefined_metric_specification {
predefined_metric_type = "DynamoDBReadCapacityUtilization"
}
}
}
Targeting 70% utilization rather than 90%+ leaves headroom that absorbs the lag between a traffic increase and auto-scaling's response — tighter targets save marginally more on capacity cost but meaningfully increase throttling risk during real spikes, which is a worse outcome for most applications than the modest extra capacity cost.
Hybrid Tables Are a Legitimate Answer, Not a Compromise
Nothing requires picking one mode for an entire table's lifetime, or even one mode across an entire access pattern. Tables with genuinely mixed traffic — a predictable baseline plus occasional unpredictable spikes (a flash sale, a viral moment, a batch job) — can switch capacity modes (DynamoDB allows switching between modes, with a limit on how often), or split traffic across separate tables by access pattern where the data model supports it, provisioned for the predictable baseline and on-demand for the unpredictable overflow path.
Frequently Asked Questions
How often can we switch a table between on-demand and provisioned mode?
DynamoDB allows switching a table's capacity mode once per 24-hour period, which is enough for a deliberate mode change based on a traffic pattern shift, but not frequent enough to use as a real-time cost-optimization lever — the choice should be based on the workload's overall pattern, not adjusted reactively day to day.
Does on-demand mode eliminate throttling risk entirely?
On-demand mode has much higher default throughput limits than a typical provisioned table and scales automatically, but it isn't literally unlimited — extremely sudden, extreme spikes (well beyond double the previous peak in a short window) can still hit temporary internal limits. For most real-world traffic patterns this isn't a practical concern, but very high-scale or highly bursty applications should understand the documented scaling behavior rather than assume zero limits.
What's a rough rule of thumb for when provisioned capacity becomes cheaper?
For steady, predictable traffic, provisioned capacity is typically cheaper once utilization is consistent enough that capacity isn't sitting significantly idle — as a rough starting heuristic, tables with utilization consistently above 30-40% of any reasonably-sized provisioned capacity tier tend to favor provisioned, but this should always be validated against actual CloudWatch consumption data rather than applied as a fixed rule.
Does Reserved Capacity apply to DynamoDB the same way Reserved Instances apply to EC2?
DynamoDB Reserved Capacity is a separate commitment mechanism that discounts provisioned-mode capacity (not on-demand) in exchange for a term commitment, similar in spirit to EC2 Reserved Instances but specific to DynamoDB's read/write capacity units — it only makes sense once you've already decided provisioned mode is the right fit for the workload.
Can we mix capacity modes across a Global Table's replicas?
Global Tables generally use the same capacity mode across all replicas for consistency, though the specific configuration options have evolved with DynamoDB's Global Tables versions — verify current support against the specific Global Tables version in use before assuming per-replica mode flexibility.
Rutagon's AWS cost audits include DynamoDB capacity mode analysis based on real consumption patterns, alongside compute and storage rightsizing.
Talk to us about a cost optimization audit → rutagon.com/contact | 907-841-8407 | contact@rutagon.com
Related reading: RDS Proxy Connection Pooling Cost Savings · Aurora Serverless v2 Cost Optimization Guide · AWS Cloud Infrastructure Capability
External reference: AWS DynamoDB Pricing Documentation