Skip to content
🎉 Launch sale:50% off everything over $22 — automatically applied at checkout· ends Aug 2Shop the sale →
DevOps AI ToolKit
Newsletter
All guides
AWS with AI By James Joyner IV · · 9 min read Last reviewed Jul 2026

AWS Error Guide: 'ProvisionedThroughputExceededException' — DynamoDB Throttling Fix

Quick answer

Fix DynamoDB ProvisionedThroughputExceededException: diagnose hot partitions, undersized capacity, missing backoff, and scan storms, then rebalance keys or switch capacity mode.

  • #aws
  • #cloud
  • #troubleshooting
  • #errors
Free toolkit

Stuck on this AWS with AI error? Get the free incident triage checklist

A one-page PDF — the exact steps to isolate, fix, and verify a production error like this one. No spam, unsubscribe anytime.

Overview

ProvisionedThroughputExceededException means a DynamoDB request was throttled because it exceeded the read or write capacity available to a table, a partition, or a global secondary index. DynamoDB spreads capacity across partitions by partition-key hash; a request is rejected when the relevant partition’s per-second capacity is exhausted, even if the table’s total provisioned capacity looks under-used. It returns HTTP 400 and is retryable with backoff.

It surfaces from the CLI or an SDK:

An error occurred (ProvisionedThroughputExceededException) when calling the PutItem operation (reached max retries: 9): The level of configured provisioned throughput for the table was exceeded. Consider increasing your provisioning level with the UpdateTable API.

On-demand tables raise the closely related shape when they burst past their scaling ceiling:

An error occurred (ThrottlingException) when calling the Query operation: Throughput exceeds the current capacity of your table or index.

It occurs whenever request rate to one partition, index, or table outpaces available capacity — a hot key, a traffic spike faster than auto scaling can react, a full-table scan, or a GSF/GSI with too little capacity of its own.

Symptoms

  • Intermittent ProvisionedThroughputExceededException on PutItem, UpdateItem, Query, or GetItem that worsens under load.
  • ConsumedCapacity on the table looks below the provisioned total, yet requests still throttle (a hot partition).
  • CloudWatch shows ThrottledRequests / ReadThrottleEvents / WriteThrottleEvents spikes.
  • A GSI throttles while the base table is fine (independent capacity).
  • SDK reports reached max retries after several automatic attempts.
aws cloudwatch get-metric-statistics --namespace AWS/DynamoDB \
  --metric-name ThrottledRequests --dimensions Name=TableName,Value=Orders \
  --start-time "$(date -u -d '1 hour ago' +%Y-%m-%dT%H:%M:%SZ)" \
  --end-time "$(date -u +%Y-%m-%dT%H:%M:%SZ)" --period 300 --statistics Sum \
  --query 'Datapoints[].[Timestamp,Sum]' --output text

Common Root Causes

1. A hot partition (skewed partition key)

The most common cause. Traffic concentrates on one partition-key value, so one partition’s capacity is exhausted while the table total looks fine. Each partition gets only its share of provisioned capacity.

aws dynamodb describe-table --table-name Orders \
  --query 'Table.{Keys:KeySchema,Mode:BillingModeSummary.BillingMode,RCU:ProvisionedThroughput.ReadCapacityUnits,WCU:ProvisionedThroughput.WriteCapacityUnits}'

A low-cardinality partition key (e.g. a status flag or a single tenant id) forces most traffic onto one partition. Use CloudWatch Contributor Insights to name the hot key.

2. Provisioned capacity too low for the real workload

The table is in PROVISIONED mode with RCUs/WCUs set below sustained demand, and no (or too-slow) auto scaling.

aws application-autoscaling describe-scaling-policies \
  --service-namespace dynamodb \
  --resource-id "table/Orders" \
  --query 'ScalingPolicies[].[PolicyName,TargetTrackingScalingPolicyConfiguration.TargetValue]' \
  --output text

If no scaling policy exists, capacity is static and a traffic increase throttles immediately.

3. Auto scaling reacts slower than the spike

Target-tracking auto scaling adjusts over minutes; a sudden burst throttles until capacity catches up. Sharp, spiky traffic often needs on-demand mode instead.

4. A full-table scan competing with production traffic

A Scan (analytics job, export, un-indexed query) consumes large read capacity and starves online reads.

aws cloudtrail lookup-events \
  --lookup-attributes AttributeKey=EventName,AttributeValue=Scan \
  --query 'Events[].Username' --output text | sort | uniq -c | sort -rn

5. A GSI with its own undersized capacity

A GSI has independent provisioned capacity. A GSI throttle on writes also throttles the base-table write (writes must propagate to the index).

aws dynamodb describe-table --table-name Orders \
  --query 'Table.GlobalSecondaryIndexes[].[IndexName,ProvisionedThroughput.WriteCapacityUnits,ProvisionedThroughput.ReadCapacityUnits]' \
  --output table

6. Retries without backoff

An SDK configured with weak retries (or a hand-rolled fixed-delay loop) turns transient throttling into sustained throttling by piling on more requests.

aws configure get retry_mode; aws configure get max_attempts

Diagnostic Workflow

Step 1: Confirm the throttle in CloudWatch

for m in ReadThrottleEvents WriteThrottleEvents ThrottledRequests; do
  echo "== $m =="
  aws cloudwatch get-metric-statistics --namespace AWS/DynamoDB --metric-name $m \
    --dimensions Name=TableName,Value=Orders \
    --start-time "$(date -u -d '1 hour ago' +%Y-%m-%dT%H:%M:%SZ)" \
    --end-time "$(date -u +%Y-%m-%dT%H:%M:%SZ)" --period 300 --statistics Sum \
    --query 'Datapoints[].Sum' --output text
done

Step 2: Compare consumed vs provisioned capacity

aws cloudwatch get-metric-statistics --namespace AWS/DynamoDB \
  --metric-name ConsumedWriteCapacityUnits --dimensions Name=TableName,Value=Orders \
  --start-time "$(date -u -d '1 hour ago' +%Y-%m-%dT%H:%M:%SZ)" \
  --end-time "$(date -u +%Y-%m-%dT%H:%M:%SZ)" --period 60 --statistics Sum \
  --query 'Datapoints[].Sum' --output text

If consumed sits well below provisioned yet throttles occur, suspect a hot partition rather than a global shortfall.

Step 3: Identify the hot key with Contributor Insights

aws dynamodb update-contributor-insights --table-name Orders \
  --contributor-insights-action ENABLE
# then read the "MostThrottledKeys" / "AccessedKeys" rules in CloudWatch Contributor Insights

Step 4: Check whether a GSI or a scan is the source

aws dynamodb describe-table --table-name Orders \
  --query 'Table.GlobalSecondaryIndexes[].[IndexName,ProvisionedThroughput]' --output json
aws cloudtrail lookup-events \
  --lookup-attributes AttributeKey=EventName,AttributeValue=Scan \
  --query 'Events[].Username' --output text | sort | uniq -c

Step 5: Inspect and strengthen retry configuration

export AWS_RETRY_MODE=adaptive
export AWS_MAX_ATTEMPTS=10
aws dynamodb get-item --table-name Orders --key '{"pk":{"S":"REDACTED"}}' >/dev/null \
  && echo "OK with adaptive retries"

Example Root Cause Analysis

A checkout service began throwing ProvisionedThroughputExceededException on UpdateItem during a flash sale, even though the Orders table’s total consumed WCU was under 40% of provisioned.

CloudWatch showed write throttling with low aggregate consumption — the classic hot-partition signature. Contributor Insights named the culprit:

MostThrottledKeys: pk = "ORDER#STATUS#PENDING"

The application had modeled the partition key as order status, so every pending order wrote to the same partition-key value and therefore the same partition. Total capacity was ample; one partition’s slice was not.

Fix: remodel the key so writes spread across partitions — use the order id (high cardinality) as the partition key and keep status as a GSI attribute for querying:

Before: pk = ORDER#STATUS#PENDING   (all pending orders → one partition)
After:  pk = ORDER#<orderId>        (each order → its own hashed partition)
        GSI: statusIndex on (status, createdAt) for "list pending orders"

After the redeploy, writes distributed evenly and throttling stopped. As an immediate mitigation before the code change shipped, the table was switched to on-demand capacity mode so per-partition limits (not a static table total) governed the burst.

Prevention Best Practices

  • Design high-cardinality partition keys so traffic spreads across partitions; never key on a low-cardinality attribute like status or region.
  • Use on-demand capacity mode for spiky or unpredictable traffic; auto scaling in provisioned mode reacts over minutes and lags sharp bursts.
  • Give GSIs enough capacity of their own; a throttled GSI write throttles the base-table write too.
  • Keep Scan jobs off production capacity — run them against a separate read replica pattern, use Limit/rate control, or export to S3 instead.
  • Use the SDK’s adaptive retry mode with generous max_attempts; never hand-roll fixed-delay retries that ignore throttle signals.
  • Enable Contributor Insights on hot tables so you can name the throttled key immediately instead of guessing.

Quick Command Reference

# Confirm throttling events
aws cloudwatch get-metric-statistics --namespace AWS/DynamoDB --metric-name ThrottledRequests \
  --dimensions Name=TableName,Value=Orders --period 300 --statistics Sum \
  --start-time "$(date -u -d '1 hour ago' +%Y-%m-%dT%H:%M:%SZ)" \
  --end-time "$(date -u +%Y-%m-%dT%H:%M:%SZ)" --query 'Datapoints[].Sum' --output text

# Inspect table keys, mode, and provisioned capacity
aws dynamodb describe-table --table-name Orders \
  --query 'Table.{Keys:KeySchema,Mode:BillingModeSummary.BillingMode,GSIs:GlobalSecondaryIndexes[].IndexName}'

# Enable Contributor Insights to find the hot key
aws dynamodb update-contributor-insights --table-name Orders --contributor-insights-action ENABLE

# Switch to on-demand as an immediate mitigation
aws dynamodb update-table --table-name Orders --billing-mode PAY_PER_REQUEST

# Retry with adaptive backoff
AWS_RETRY_MODE=adaptive AWS_MAX_ATTEMPTS=10 aws dynamodb get-item \
  --table-name Orders --key '{"pk":{"S":"REDACTED"}}' >/dev/null

Conclusion

ProvisionedThroughputExceededException means a DynamoDB request exceeded the capacity available to its partition, index, or table. The usual root causes:

  1. A hot partition from a low-cardinality partition key concentrating traffic.
  2. Provisioned capacity set below real sustained demand.
  3. Auto scaling reacting slower than a sudden spike.
  4. A full-table Scan starving online reads.
  5. A GSI with its own undersized capacity throttling base-table writes.
  6. Retries without backoff turning a transient throttle into a sustained one.

Check consumed-vs-provisioned first: if the total is low but you still throttle, it is a hot partition — fix the key model. For spiky traffic, on-demand mode sidesteps static per-table limits. Throttling is about spreading load and matching capacity to the access pattern, not just retrying harder.

Free download · 368-page PDF

Fixed it? Get 500 AWS with AI & DevOps AI prompts — free

500 battle-tested, copy-paste AI prompts engineered by a senior systems engineer — every one with fill-in placeholders and safety/back-out notes. Drop your email and it's yours.

  • 500 prompts: Linux · Kubernetes · Terraform · OpenStack · GitLab · Docker · Monitoring · Incident Response
  • Instant PDF download — yours free, forever
  • Plus one practical AI-workflow email a week (no spam)

Single opt-in · unsubscribe anytime · no spam.

Did this fix your issue?

Free download · 368-page PDF

Get 500 Battle-Tested DevOps AI Prompts — Free

500 battle-tested, copy-paste AI prompts engineered by a senior systems engineer — every one with fill-in placeholders and safety/back-out notes. Drop your email and it's yours.

  • 500 prompts: Linux · Kubernetes · Terraform · OpenStack · GitLab · Docker · Monitoring · Incident Response
  • Instant PDF download — yours free, forever
  • Plus one practical AI-workflow email a week (no spam)

Single opt-in · unsubscribe anytime · no spam.