You write to your table. The write fails with a throughput exception — but the
exception names a Global Secondary Index, not the table. The table has spare
capacity.
Coming from SQL, that's nonsense: a secondary index can't block an INSERT. In
DynamoDB it can, and the mechanism is called GSI back-pressure.
Why does a DynamoDB GSI throttle base-table writes?
DynamoDB throttles the base-table write because every write also replicates to each GSI, and if a GSI partition can't absorb its share, DynamoDB applies back-pressure to stop the index falling permanently behind. So an under-provisioned or low-cardinality GSI key becomes a hard ceiling on your base-table write rate.
- A write to the base table also writes to every GSI. If a GSI can't absorb its share, DynamoDB throttles the base-table write to keep the index from falling permanently behind. (AWS docs)
-
An even base table doesn't save you. The GSI is partitioned by its own key.
A low-cardinality GSI key (like
status) creates a hot index partition even when base-table writes are perfectly spread. -
The exception lies about the victim. The
ResourceArnpoints at the GSI; the operation actually being throttled is your write to the table. - The fix is capacity or key design, not retry loops — raise GSI throughput, or pick a GSI partition key that spreads.
How a single write touches the index
A PutItem on the base table is not one write. DynamoDB replicates the item's
projected attributes into each GSI asynchronously, on an eventually consistent
model. One logical write fans out to N physical writes — table plus every index.
That replication is not free and not optional. The GSI must keep up, or the
index drifts further from the table on every operation.
To stop that drift, DynamoDB applies back-pressure: it throttles the source
write so the index never gets unboundedly stale.
So the GSI's write capacity is a hard ceiling on your base-table write rate —
even though you never write to the GSI directly.
A worked example: an orders table
Say you run an orders table. The base item:
| field | value | note |
|---|---|---|
| PK | "CUST#8841" | partition key |
| SK | "ORD#2026-06-23#A7" | sort key |
| order_state | "PROCESSING" | |
| warehouse | "EU-MAD-2" | |
| total_cents | 4990 |
Base-table writes are healthy. CUST#... has high cardinality, so order writes
spread evenly across base partitions. No hot key, plenty of capacity.
Now you add a GSI to answer "show me every order in a given state":
GSI: orders-by-state
| field | value | note |
|---|---|---|
| GSI-PK | order_state | "PENDING" | "PROCESSING" | "SHIPPED" | "CANCELED" |
| GSI-SK | SK |
Four possible partition-key values. During a flash sale, almost every new order
lands in order_state = "PENDING". Every one of those writes hits the same
GSI partition.
That partition has a per-partition throughput limit, and you've just aimed your
entire write storm at it.
The base table is fine. The PENDING GSI partition is on fire. DynamoDB
throttles the base-table PutItem to protect the index.
The flow that bites you
Here's the back-pressure path — base write balanced, index write concentrated:
The throttle travels backward: a hot GSI partition rejects the base-table write
that fed it.
Read the exception, not your gut
The exception type tells you exactly which ceiling you hit. The ResourceArn
names the GSI; the throttled op is still the table write.
| Mode | Reason code | What ran out |
|---|---|---|
| Provisioned | IndexWriteProvisionedThroughputExceeded |
GSI's provisioned write capacity |
| Both | IndexWriteKeyRangeThroughputExceeded |
A single hot GSI partition |
| On-demand | IndexWriteMaxOnDemandThroughputExceeded |
GSI's configured max on-demand ceiling |
| On-demand | IndexWriteAccountLimitExceeded |
Account/region throughput boundary |
Source: Understanding GSI write throttling and back pressure.
The KeyRange reason is the giveaway for the hot-partition case above: overall
GSI capacity can look fine while one key range is saturated.
How to fix it
Give the GSI room. The simplest cause is under-provisioning. A GSI has its
own read and write capacity, fully separate from the table — see
GSI vs LSI.
If you provisioned the table generously and left the GSI thin, raise the GSI's
write capacity (or its on-demand max).
Fix the partition key. Capacity won't save a low-cardinality key — you can't
out-provision a single hot partition. Pick a GSI partition key that spreads.
Compose it: order_state#shard where shard is a small random suffix, or fold
the date in (PENDING#2026-06-23). Writes spread across partitions and you still
Query a state by querying the shards.
Project fewer attributes. Each GSI write copies the projected attributes. A
KEYS_ONLY or tight INCLUDE projection means smaller index writes and less
pressure than ALL. Don't project what you'll never read off the index.
Drop the GSI if it's only for reporting. If "orders by state" is an
occasional admin question, not a hot path, a periodic scan with a filter may beat
a permanently hot index — weigh it against
Query vs Scan.
When you do query that index, the
Expression Builder writes the
KeyConditionExpression for you — e.g. #s = :state AND begins_with(SK, :prefix) —
with the names and values escaped correctly:
| Example | Notes |
|---|---|
KeyConditionExpression "#s = :state AND begins_with(SK, :prefix)" |
|
ExpressionAttributeNames { "#s": "order_state" } |
|
ExpressionAttributeValues { ":state": { "S": "PENDING" }, ":prefix": { "S": "ORD#2026-06-23" } } |
The trap to remember
The relational instinct — "indexes only slow writes a little" — does not
transfer. A DynamoDB GSI is a throughput dependency, not a passive structure.
Under-size it or pick a clumping key, and it back-pressures the table it serves.
Watch ConsumedWriteCapacityUnits and WriteThrottleEvents on the GSI
dimension, not just the table's, and use Contributor Insights to find the hot
keys.
Next steps
- GSI vs LSI — why a GSI has its own capacity and a different partition key.
- Single-table design — overloading one GSI to serve many patterns without multiplying hot indexes.
- Query vs Scan — when an index isn't worth its write cost.
Try DynoTable to inspect every GSI on your tables — key schema and
item counts — and query your indexes before a sale turns them red.

Top comments (0)