Advanced6 min read

Why a GSI throttles base-table writes in DynamoDB

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 even when base-table writes are perfectly spread.
  • The exception lies about the victim. The ResourceArn points 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 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:

fieldvaluenote
PK"CUST#8841"partition key
SK"ORD#2026-06-23#A7"sort key
order_state"PROCESSING"
warehouse"EU-MAD-2"
total_cents4990

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
fieldvaluenote
GSI-PKorder_state"PENDING" | "PROCESSING" | "SHIPPED" | "CANCELED"
GSI-SKSK

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:

PutItemorder_state=PENDINGBase tablespread by CUST#Async replicateto GSIGSI partitionPENDING (hot)Partition limitexceededThrottle theBASE write

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.

ModeReason codeWhat ran out
ProvisionedIndexWriteProvisionedThroughputExceededGSI's provisioned write capacity
ProvisionedIndexWriteKeyRangeThroughputExceededA single hot GSI partition
On-demandIndexWriteMaxOnDemandThroughputExceededGSI's configured max on-demand ceiling
On-demandIndexWriteAccountLimitExceededAccount/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:

KeyConditionExpression     "#s = :state"
ExpressionAttributeNames   { "#s": "order_state" }
ExpressionAttributeValues  { ":state": { "S": "PENDING" } }

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 the GSI's ConsumedWriteCapacityUnits and ThrottledRequests per partition, not just the table's.

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 watch a GSI's consumed capacity and throttle events against your own tables before a sale turns them red.

Updated