Fix DynamoDB ProvisionedThroughputExceededException
A ProvisionedThroughputExceededException means your table (or an index on it) just tried to do more reads or writes per second than you've told DynamoDB to expect, and DynamoDB rejected the extra requests instead of silently slowing them down. The fix depends on which of four distinct causes triggered it — and the most common cause of all has nothing to do with your total provisioned capacity.
Here's the part almost nobody expects: you can hit this error while your table sits at 20–30% of its provisioned capacity in the CloudWatch dashboard. DynamoDB doesn't throttle a table as one unit. It throttles individual partitions, each capped at 3,000 read capacity units and 1,000 write capacity units per second no matter how much you've provisioned overall. Aim traffic at one partition key and you can be throttled with 97% of your table's capacity sitting idle everywhere else.
Jake runs a small phone repair and resale shop, and six months ago he built a simple DynamoDB-backed inventory app so his two employees could scan a phone's IMEI and pull up its repair history instantly. It worked fine in testing. Then he ran a weekend trade-in promotion, every customer's phone got scanned and logged within the same ten-minute window, and the app started throwing errors at the register — in front of a line of customers.
The fix took about twenty minutes once he understood what was actually happening — and the CloudWatch graph he was staring at while it happened was telling him almost nothing useful, for a reason that surprises most people the first time they hit it. Here's the full breakdown, starting with what this error actually means.
What this error actually means
DynamoDB measures throughput in two units. A read capacity unit (RCU) covers one strongly consistent read per second, or two eventually consistent reads per second, for an item up to 4 KB. A write capacity unit (WCU) covers one write per second for an item up to 1 KB. Transactional reads and writes cost double — two RCUs or two WCUs per item, per second. If your item is bigger than the base size, the calculation rounds up: a strongly consistent read of a 5 KB item costs 2 RCUs, not 1.25.
"So it's like a speed limit for the table," Jake said, when Ethan first explained it to him.
"Closer to a speed limit per lane than per road," Ethan said. "That distinction is the whole reason your inventory app broke."
In provisioned capacity mode, you tell DynamoDB the exact RCUs and WCUs you want available for a table, and DynamoDB reserves that capacity. Go over it, and every request beyond the limit gets rejected with ProvisionedThroughputExceededException: "The level of configured provisioned throughput for the table was exceeded. Consider increasing your provisioning level with the UpdateTable API." In on-demand capacity mode, you don't set RCUs or WCUs at all — DynamoDB scales automatically — but you can still be throttled, just under a different exception name (ThrottlingException) and for different reasons, covered further down.
DynamoDB gives you some slack before either of those limits bites. It banks up to five minutes (300 seconds) of unused read and write capacity per partition as burst capacity, so an occasional short spike above your provisioned rate can succeed without throttling — though AWS can also draw on that same reserve for background maintenance without warning, so it isn't something to plan around for predictable traffic. Separately, "adaptive capacity" automatically and instantly redirects unused throughput toward a partition that's receiving more traffic than its neighbors, up to the full per-partition ceiling of 3,000 RCUs or 1,000 WCUs. Both of these exist specifically to reduce throttling from uneven or bursty traffic, and both are on by default at no extra cost. When you're still getting throttled despite them, you've found a real capacity problem, not a fluke.
The four things that actually cause this error
DynamoDB's own troubleshooting documentation groups throttling into four categories, and knowing which one you've hit changes what you do next. The table and write throughput errors are the classic ProvisionedThroughputExceededException; the other two mostly show up as ThrottlingException on on-demand tables, but the underlying logic is the same across both modes.
| Reason | Applies to | What it means |
|---|---|---|
| Provisioned throughput exceeded | Provisioned mode only | Your total table (or GSI) consumption is sustained above the RCUs/WCUs you configured. |
| Key range throughput exceeded | Both modes | One or a few partitions are overloaded — a hot partition — even though the table overall has headroom. |
| Account limit exceeded | On-demand mode | Your table hit the default per-account, per-table throughput quota (40,000 RCUs / 40,000 WCUs). |
| On-demand maximum throughput exceeded | On-demand mode | You set a self-imposed MaxReadRequestUnits/MaxWriteRequestUnits cap, and traffic hit it. |
The SDKs surface some of this diagnosis for you directly. The exception object includes a ThrottlingReason field, formatted as resource type plus operation type plus limit type — for example, TableWriteProvisionedThroughputExceeded tells you it's your base table's write capacity, not a GSI, not a hot partition. Read that field before you guess.
Diagnosis 1: you're genuinely under-provisioned
This is the straightforward case, and it's diagnosed with one comparison: open CloudWatch and look at ConsumedWriteCapacityUnits (or ConsumedReadCapacityUnits) against ProvisionedWriteCapacityUnits for the same table. If consumption is riding at or near your provisioned ceiling across the whole table — not spiking on one narrow window — your application simply needs more throughput than you've configured. Sustained high traffic gets throttled this way regardless of whether Auto Scaling is enabled, because Auto Scaling reacts to demand rather than anticipating it, and it's bounded by whatever maximum you set for it.
Occasional throttling in this category isn't automatically an emergency. DynamoDB's own guidance is blunt about it: hitting your ceiling now and then, with your application retrying gracefully, is a normal and expected state for a well-tuned table. The question worth asking before you touch anything is whether the throttling is actually costing you — degraded latency, failed operations, unhappy users — or just showing up in a metric nobody's impacted by.
Diagnosis 2: you have a hot partition
This is the one that catches people off guard, and it's what actually happened to Jake. DynamoDB splits a table's data across partitions based on a hash of the partition key. Every partition has its own hard ceiling — 3,000 RCUs and 1,000 WCUs per second — completely independent of how much throughput you've provisioned for the table as a whole. If your access pattern concentrates traffic onto one partition key value (or a narrow range of them), that one partition can throttle while every other partition, and the table's aggregate CloudWatch graph, looks nearly empty.
🙋♂️ Jake's Reality Check
"My CloudWatch graph showed we were nowhere near our provisioned capacity. So why was it throwing errors like we'd blown way past it?"
Because the graph shows the table's total, and your throttling was happening on one partition inside it. Jake's app was writing every scan with the promotion's event date as the partition key. Every trade-in that weekend hit the same partition, all day, regardless of the table's total headroom.
DynamoDB throttles reads and writes on a partition independently of each other, so it's entirely possible for writes to throttle while reads sail through, or the reverse. The exception's ThrottlingReason field will read TableWriteKeyRangeThroughputExceeded (or the read equivalent) in this scenario, which is your clearest signal that more provisioned capacity is not the answer.
There's some good news: DynamoDB often resolves this on its own. When a partition stays consistently busy, DynamoDB can split it — a mechanism sometimes called "split for heat" — so each half handles a smaller slice of the keyspace going forward. If your throttling spike stopped after a short period without you doing anything, that's often exactly what happened. If it's still going, the next step is finding which key is hot, using CloudWatch Contributor Insights in "Throttled keys" mode, which is built specifically to surface the partition key values behind throttled requests without the overhead of logging every request.
Diagnosis 3: you hit an account-level quota
On-demand tables don't have a provisioned ceiling for you to exceed, but they aren't unlimited either. Every table on an AWS account has a default per-table throughput quota — 40,000 RCUs and 40,000 WCUs, in most Regions — that exists as a safeguard against runaway traffic and to keep resource usage fair across customers. Cross that, and DynamoDB returns an AccountLimitExceeded throttling reason.
This is a real limit but rarely the everyday cause of throttling for small and mid-size applications, since 40,000 RCUs a second is a lot of sustained traffic. If you're building something at that scale, or you know a launch will legitimately need more, you request an increase through the Service Quotas console using the specific quota code for read (L-CF0CBE56) or write (L-AB614373) throughput. Build in a couple of days for that: quota increases typically take 24 to 48 hours to process, so this isn't a same-day fix if you haven't asked ahead of time.
Diagnosis 4: you set the ceiling yourself
On-demand tables let you optionally set your own MaxReadRequestUnits and MaxWriteRequestUnits at the table or index level — a deliberate cost or downstream-protection cap, not a DynamoDB-imposed limit. If traffic reaches that self-configured number, DynamoDB throttles the excess with a MaxOnDemandThroughputExceeded reason, exactly as designed.
🕐 What changed between versions
- Before May 3, 2024: on-demand tables were bounded only by the account-level default quota (40,000 RCUs / 40,000 WCUs), which applied uniformly and couldn't be customized per table.
- Now: you can configure your own maximum throughput per on-demand table and per GSI, so a single misconfigured job can't run up an unbounded bill.
- What that means for you: if you're seeing this specific throttling reason, check the table's configured maximum before you touch anything else — it's very likely a limit you set on purpose.
If that's the case, the fix isn't really a fix so much as a decision: was that limit protecting something real, like a downstream service that can't handle unbounded traffic, or was it set once and forgotten? Raise it through the console, CLI, or the UpdateTable API if your needs have genuinely grown.
Fix: raise provisioned capacity manually
This is the fastest way out of a genuine, sustained under-provisioning problem, and it works whether or not Auto Scaling is already on — though if Auto Scaling is on, tuning its target is usually a better long-term move than a one-off manual bump.
- Open the table in the DynamoDB console, or use the AWS CLI / SDK's
UpdateTablecall, and increaseReadCapacityUnitsand/orWriteCapacityUnitsto a level above your actual peak consumption. - Confirm the new numbers stay under both your per-table quota and your account's per-Region quota — if you're already close to those, switching to on-demand mode may make more sense than repeatedly requesting quota increases.
- Give the change a few minutes to fully apply before you judge whether throttling has stopped; capacity updates aren't instant.
⚠️ What this actually breaks
Decreases are rationed, not increases: DynamoDB limits you to four decreases of a table's read or write capacity in the first four hours of a UTC day. If you're planning to scale a table up for a launch and back down afterward, plan the down-scaling with that limit in mind rather than assuming you can adjust freely all day.
Fix: turn on (or tune) Auto Scaling
If you created your table through the console, Auto Scaling is already on by default. If you created it through the CLI, a SDK, or infrastructure as code, it likely isn't, and that's worth checking before you assume manual increases are your only lever.
- In the table's Additional settings tab, open the Read/write capacity section and enable Auto Scaling for read capacity, write capacity, or both — independently, and independently again for any GSIs.
- Set a minimum and maximum capacity range. The minimum should cover your quietest traffic without waste; the maximum should cover your realistic peak plus headroom, and it acts as a hard ceiling — Auto Scaling will not exceed it even under sustained load.
- Set a target utilization percentage between 20% and 90%. DynamoDB recommends 70% as a starting point: high enough to avoid paying for a lot of idle capacity, low enough to leave room for Auto Scaling to react before you throttle.
"People assume Auto Scaling means 'never throttles,'" Ethan said. "It means 'throttles less, and only briefly.' It's a control loop watching a CloudWatch alarm, not a psychic."
That's not an exaggeration of the mechanics: scaling up typically needs two consecutive breaches of your target within a short window, an UpdateTable call then has to complete, and any requests above the old capacity are throttled during that gap. If your traffic already sits near your maximum most of the time, or your target utilization is set too low, throttling during scale-up events is close to unavoidable — the target just determines how much headroom you're buying yourself before it happens.
Fix: switch the table to on-demand
On-demand removes capacity planning entirely: you stop setting RCUs and WCUs, and DynamoDB scales to meet traffic automatically, billing per request instead of per provisioned unit. It's the right call for genuinely unpredictable or spiky workloads — the kind where you'd otherwise have to provision for a peak that only shows up occasionally, and pay for that ceiling around the clock.
| Traffic shape | Better mode | Why |
|---|---|---|
| Steady, predictable, gradual growth | Provisioned + Auto Scaling | Provisioned pricing is generally cheaper when usage is consistent enough to plan around. |
| Spiky, unknown, or new workload | On-demand | No provisioning to under-guess; you pay per request instead of for idle headroom. |
| Drops to zero or under 30% of peak for hours at a time | On-demand | Provisioned capacity sits idle (and billed) through those quiet stretches. |
✅ Why this is the one to use for Jake's shop
A trade-in promotion twice a year, sandwiched between long quiet stretches, is exactly the shape on-demand is built for. Paying for provisioned capacity sized to a weekend rush, all year, would waste money the other 350 days.
You can switch a table from provisioned to on-demand up to four times within a rolling 24-hour window — a limit AWS widened from once per 24 hours in August 2025 — and from on-demand back to provisioned at any time with no frequency limit. New on-demand tables can immediately sustain 4,000 WCUs and 12,000 RCUs. From there, on-demand instantly accommodates traffic up to double whatever peak the table has previously reached; sustain that new peak, and the ceiling doubles again from there. The catch is the word "previously reached" — if traffic jumps to more than double the prior peak within a 30-minute window, on-demand tables can still throttle, with the reason ThrottlingException rather than ProvisionedThroughputExceededException. For a known, extreme spike — a product launch, not a gradual ramp — ramping traffic up over at least 30 minutes beforehand, or pre-warming the table to your expected level, avoids that.
On-demand is also not automatically the cheaper option. Per-request on-demand pricing generally runs several times the equivalent provisioned rate, so a table with genuinely steady, high, predictable traffic will usually cost less on provisioned capacity with Auto Scaling tuned well. Switching to fix an occasional spike and leaving it there indefinitely can be the more expensive habit, not the smarter one.
Fixing a hot partition for good
None of the fixes above touch a hot partition, because none of them raise the per-partition ceiling — that 3,000 RCU / 1,000 WCU limit is fixed regardless of capacity mode or how much you've provisioned in aggregate. This is the case where "throw more capacity at it" genuinely does not work, and it's worth saying plainly, because it's the popular first instinct.
Short-term, a few things buy you room without a redesign. If your reads don't strictly need up-to-the-millisecond accuracy, switching from strongly consistent to eventually consistent reads consumes half the RCUs for the same requests, which can immediately double the effective read capacity available on that partition. For writes concentrated on a handful of keys, or a keyspace being scanned in order (sequential timestamps, for instance), you're dealing with what's sometimes called a rolling hot partition, where the busy partition shifts over time rather than staying fixed — harder to pin down with a single Contributor Insights snapshot, and usually worth correlating against your application's actual write pattern rather than assuming a single culprit key.
The durable fix is redesigning the partition key so traffic spreads across enough distinct values that no single one concentrates the load. Jake's date-based partition key was the textbook version of the problem: every write for a given day landed on one partition, capping his whole application at that partition's write ceiling no matter what the table's total provisioned capacity was. Adding a higher-cardinality element to the key — combining the date with something like a device ID or a shard number — spreads writes across many partitions instead of one.
"It's not that your key was wrong," Ethan told him. "It's that it was fine at your old traffic and stopped being fine at your new traffic. That's a very normal way to end up here."
⚠️ What this actually breaks
Redesigning a partition key isn't a config change. It requires migrating existing data to the new key structure and updating every piece of application code that reads or writes the table, and it commonly needs downtime or a dual-write period during the transition. Treat it as a planned migration, not a quick patch, and expect it to touch more of your codebase than the table definition alone.
Handling throttling in your application code
Whichever capacity fix you apply, throttling can still happen occasionally — that's the burst-capacity design working as intended, not a failure — so your application still needs to handle it gracefully rather than treating every throttle as a fatal error.
The official AWS SDKs already retry a ProvisionedThroughputExceededException automatically using exponential backoff, so if you're calling DynamoDB through a supported SDK, a lot of transient throttling resolves itself before your code ever sees an exception. If you're calling the API directly without an SDK, or you want finer control, the pattern to implement yourself is the same one AWS documents generally for service throttling: retry with a delay that grows exponentially between attempts, plus some randomized jitter, so retries from many clients don't all land on the same instant and re-trigger the throttle.
Two operations behave differently and deserve their own handling. With BatchWriteItem, DynamoDB does not raise a throttling exception for individual throttled items inside the batch — it returns them in an UnprocessedItems list in the response, and your code is responsible for retrying those specific items, typically with the same backoff approach. With a transaction, there's no partial success: if any single item in the transaction hits throttling, the whole operation fails with a TransactionCanceledException, and you retry the transaction as a unit rather than picking out the one throttled item.
Monitoring, so you catch this before your users do
A handful of CloudWatch metrics cover almost every diagnosis in this article, and they're worth an alarm rather than something you check only after a customer complains.
| Metric | What it tells you |
|---|---|
ConsumedReadCapacityUnits / ConsumedWriteCapacityUnits | Your actual usage — compare against provisioned levels for diagnosis 1. |
ReadProvisionedThroughputThrottleEvents / WriteProvisionedThroughputThrottleEvents | Throttles specifically from table-wide provisioned limits. |
ReadKeyRangeThroughputThrottleEvents / WriteKeyRangeThroughputThrottleEvents | Throttles specifically from a hot partition — your signal for diagnosis 2. |
ReadThrottleEvents / WriteThrottleEvents | Any throttling of any kind — the broad alarm to start with. |
For the hot-partition case specifically, CloudWatch Contributor Insights is worth enabling ahead of time in "Throttled keys" mode rather than after the fact, since it only processes events when throttling actually occurs and is a cost-effective way to keep continuous visibility without logging every single request.
When nothing above fixes it
A less obvious cause worth naming: your base table writes can throttle because of a global secondary index (GSI), not the base table itself. Every write to a base table item that includes an attribute projected into a GSI triggers a corresponding write to that GSI. If the GSI's own provisioned write capacity (or its own hot partition) can't keep up, DynamoDB applies back-pressure to the base table's writes to protect the index — so you can see write throttling on a table whose own capacity looks perfectly healthy, purely because one of its indexes can't keep pace. Checking each GSI's own consumed and provisioned capacity, not just the base table's, is worth doing before you conclude a fix isn't working.
A related and specifically common trigger: GSIs built on low-cardinality attributes, like a status flag with only a handful of possible values. If 80% of your items share the same status, a GSI keyed on that status concentrates writes onto a small number of partitions no matter how well-distributed your base table's partition key is. Combining the low-cardinality attribute with something high-cardinality, like a composite key of status plus customer ID, spreads that same GSI traffic out.
If you've worked through diagnosis, applied the matching fix, and you're still seeing throttling that doesn't correlate with any single identifiable key or index, it's worth treating that as its own investigation rather than repeating the same fix harder — at that point you're most likely looking at a genuinely uneven or rapidly shifting access pattern that needs correlating against your application's write logic directly, not a setting anywhere in the DynamoDB console.
Frequently asked questions
What does ProvisionedThroughputExceededException actually mean?
It means a table (or one of its indexes) in provisioned capacity mode received requests faster than its configured read or write capacity units allow, and the excess requests were rejected rather than queued.
Do I need to understand capacity units just to use DynamoDB?
Only if you're using provisioned mode. On-demand mode is built specifically so you can create a table and start reading and writing without ever setting an RCU or WCU value; you only need to understand capacity units once you're diagnosing throttling or comparing costs between modes.
What exactly is a write capacity unit?
One write capacity unit covers one write per second for an item up to 1 KB in size. A write to a larger item consumes proportionally more WCUs, rounded up — a 2.5 KB item write costs 3 WCUs.
How does DynamoDB decide when to throttle a request?
It checks the specific partition the request lands on against that partition's throughput limit, factoring in any banked burst capacity and adaptive capacity boost, before it checks the table's overall provisioned or account-level limit. That's why partition-level (hot partition) throttling can happen even when the table-wide numbers look fine.
Does switching to on-demand fix throttling?
It fixes throttling caused by under-provisioned capacity, since there's no capacity to under-provision anymore. It does not fix a hot partition, since the per-partition ceiling applies in on-demand mode too, and it doesn't eliminate throttling from a sudden spike more than double your table's previous peak within 30 minutes.
Can an on-demand table still be throttled?
Yes, in three of the four ways covered in this article: a hot partition, an account-level quota, or a self-configured maximum throughput setting. The exception name changes to ThrottlingException, but the underlying causes and fixes largely mirror the provisioned-mode ones.
Will turning on Auto Scaling stop this error immediately?
Not immediately. Auto Scaling reacts to sustained breaches of your target utilization, and applying the resulting capacity increase takes several minutes, so requests above your old capacity can still throttle during that window. It reduces future throttling; it doesn't clear an in-progress spike instantly.
How do I know if I have a hot partition?
The clearest signal is total table consumption sitting well below your provisioned capacity while throttling still occurs. Confirm it with the ThrottlingReason field on the exception (look for KeyRangeThroughputExceeded) or the ReadKeyRangeThroughputThrottleEvents/WriteKeyRangeThroughputThrottleEvents CloudWatch metrics, then use Contributor Insights to identify the specific key.
Does the AWS SDK retry this error automatically?
Yes. The official SDKs automatically retry requests that receive a ProvisionedThroughputExceededException, using exponential backoff, so a brief throttle often resolves without your code needing to catch anything.
Should I write my own retry logic on top of the SDK's?
Generally not for single-item operations already covered by SDK retries. You do need your own handling for BatchWriteItem's UnprocessedItems and for retrying a canceled transaction, since those aren't retried automatically the same way.
Can throttling cause data loss?
A throttled write simply fails to write; DynamoDB doesn't silently drop or corrupt data because of it. Data loss only becomes a risk if your application discards the failed write instead of retrying it or surfacing the failure.
How many times can I switch capacity modes?
Provisioned to on-demand: up to four times within a rolling 24-hour window. On-demand back to provisioned: any time, with no frequency limit.
Does switching capacity modes cost anything?
There's no separate charge for the switch itself. What changes is your ongoing billing model — per-request pricing on-demand versus per-provisioned-unit pricing on provisioned mode — so the cost impact comes from which mode fits your traffic shape, not from the act of switching.
Does BatchWriteItem fail completely if some items are throttled?
No. Throttled items within a batch come back in the response's UnprocessedItems list rather than raising an exception for the whole batch; your code retries just those items.
Do transactions behave differently when throttled?
Yes. Unlike BatchWriteItem, a transaction has no partial success. If any item in it is throttled, the entire transaction fails with TransactionCanceledException, and you retry the whole transaction rather than a single item.
Will increasing my provisioned capacity fix a hot partition?
No. The per-partition throughput ceiling — 3,000 RCUs and 1,000 WCUs per second — is fixed regardless of your table's total provisioned capacity. Raising the table's overall numbers doesn't raise what any single partition can absorb; only a better-distributed partition key, or DynamoDB's own automatic partition splitting over time, addresses that.
- What is Amazon DynamoDB? A NoSQL primer
Start here if the terms in this article — tables, items, partitions — still feel unfamiliar.
Revision note. Written August 2026, covering DynamoDB's current provisioned and on-demand throttling behavior, including the four documented throttling reasons and the August 13, 2025 change allowing up to four provisioned-to-on-demand switches in a 24-hour window. It'll need revisiting if AWS changes the per-partition throughput ceiling or the account-level default quotas. If you're reading this mid-outage with customers watching, take a breath — this is one of the more fixable errors DynamoDB throws, and you'll be back up faster than it feels like right now.