Por que um GSI causa throttling nas escritas da tabela base no DynamoDB
Você escreve na sua tabela. A escrita falha com uma exceção de throughput — mas a exceção nomeia um Global Secondary Index, não a tabela. A tabela tem capacidade sobrando.
Vindo do SQL, isso não faz sentido: um índice secundário não pode bloquear um
INSERT. No DynamoDB ele pode, e o mecanismo se chama back-pressure de GSI.
Por que um GSI causa throttling nas escritas da tabela base no DynamoDB?
O DynamoDB faz throttling da escrita na tabela base porque cada escrita também é replicada para cada GSI, e se uma partição do GSI não consegue absorver sua parcela, o DynamoDB aplica back-pressure para impedir que o índice fique permanentemente para trás. Então um GSI subprovisionado ou com uma chave de baixa cardinalidade vira um teto rígido para a sua taxa de escrita na tabela base.
- Uma escrita na tabela base também escreve em cada GSI. Se um GSI não consegue absorver sua parcela, o DynamoDB faz throttling da escrita na tabela base para impedir que o índice fique permanentemente para trás. (documentação AWS)
- Uma tabela base equilibrada não te salva. O GSI é particionado pela própria
chave. Uma chave de GSI de baixa cardinalidade (como
status) cria uma partição de índice quente mesmo quando as escritas na tabela base estão perfeitamente espalhadas. - A exceção mente sobre a vítima. O
ResourceArnaponta para o GSI; a operação que de fato está sofrendo throttling é a sua escrita na tabela. - A correção é capacidade ou design da chave, não loops de retry — aumente o throughput do GSI, ou escolha uma chave de partição de GSI que espalhe.
Como uma única escrita toca o índice
Um PutItem na tabela base não é uma escrita só. O DynamoDB replica os atributos
projetados do item em cada GSI de forma assíncrona, num modelo eventualmente
consistente. Uma escrita lógica se desdobra em N escritas físicas — tabela mais cada
índice.
Essa replicação não é gratuita nem opcional. O GSI precisa acompanhar, ou o índice se afasta cada vez mais da tabela a cada operação.
Para deter esse afastamento, o DynamoDB aplica back-pressure: faz throttling da escrita de origem para que o índice nunca fique desatualizado de forma ilimitada.
Então a capacidade de escrita do GSI é um teto rígido para a sua taxa de escrita na tabela base — mesmo que você nunca escreva no GSI diretamente.
Um exemplo prático: uma tabela de pedidos
Digamos que você administre uma tabela de pedidos. O item base:
| 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 |
As escritas na tabela base estão saudáveis. CUST#... tem alta cardinalidade, então
as escritas de pedidos se espalham uniformemente pelas partições base. Sem chave
quente, capacidade de sobra.
Agora você adiciona um GSI para responder "mostre-me todo pedido em um dado estado":
| field | value | note |
|---|---|---|
| GSI-PK | order_state | "PENDING" | "PROCESSING" | "SHIPPED" | "CANCELED" |
| GSI-SK | SK |
Quatro valores possíveis de chave de partição. Durante uma promoção relâmpago, quase
todo pedido novo cai em order_state = "PENDING". Cada uma dessas escritas acerta a
mesma partição do GSI.
Essa partição tem um limite de throughput por partição, e você acabou de mirar toda a sua tempestade de escrita nela.
A tabela base está bem. A partição PENDING do GSI está pegando fogo. O DynamoDB faz
throttling do PutItem da tabela base para proteger o índice.
O fluxo que te morde
Eis o caminho do back-pressure — escrita base equilibrada, escrita de índice concentrada:
O throttle viaja para trás: uma partição quente do GSI rejeita a escrita da tabela base que a alimentou.
Leia a exceção, não o seu instinto
O tipo da exceção te diz exatamente qual teto você atingiu. O ResourceArn nomeia o
GSI; a operação sofrendo throttling ainda é a escrita na tabela.
| Modo | Código de motivo | O que acabou |
|---|---|---|
| Provisionado | IndexWriteProvisionedThroughputExceeded | Capacidade de escrita provisionada do GSI |
| Provisionado | IndexWriteKeyRangeThroughputExceeded | Uma única partição quente do GSI |
| On-demand | IndexWriteMaxOnDemandThroughputExceeded | Teto máximo on-demand configurado do GSI |
| On-demand | IndexWriteAccountLimitExceeded | Limite de throughput da conta/região |
Fonte: Understanding GSI write throttling and back pressure.
O motivo KeyRange é a pista para o caso de partição quente acima: a capacidade geral
do GSI pode parecer ok enquanto um intervalo de chave está saturado.
Como corrigir
Dê espaço ao GSI. A causa mais simples é o subprovisionamento. Um GSI tem sua própria capacidade de leitura e escrita, totalmente separada da tabela — veja GSI vs LSI.
Se você provisionou a tabela com generosidade e deixou o GSI magro, aumente a capacidade de escrita do GSI (ou seu máximo on-demand).
Conserte a chave de partição. Capacidade não salva uma chave de baixa cardinalidade — você não consegue superprovisionar uma única partição quente. Escolha uma chave de partição de GSI que espalhe.
Componha-a: order_state#shard onde shard é um pequeno sufixo aleatório, ou inclua
a data (PENDING#2026-06-23). As escritas se espalham pelas partições e você ainda
faz Query de um estado consultando os shards.
Projete menos atributos. Cada escrita no GSI copia os atributos projetados. Uma
projeção KEYS_ONLY ou um INCLUDE enxuto significa escritas de índice menores e
menos pressão do que ALL. Não projete o que você nunca vai ler do índice.
Descarte o GSI se ele for só para relatórios. Se "pedidos por estado" é uma pergunta administrativa ocasional, não um caminho quente, um scan periódico com filtro pode vencer um índice permanentemente quente — pese contra Query vs Scan.
Quando você for consultar esse índice, o
Expression Builder escreve a
KeyConditionExpression para você — ex.: #s = :state AND begins_with(SK, :prefix) —
com os nomes e valores escapados corretamente:
KeyConditionExpression "#s = :state"
ExpressionAttributeNames { "#s": "order_state" }
ExpressionAttributeValues { ":state": { "S": "PENDING" } }
A armadilha a lembrar
O instinto relacional — "índices só deixam as escritas um pouco mais lentas" — não se transfere. Um GSI do DynamoDB é uma dependência de throughput, não uma estrutura passiva. Subdimensione-o ou escolha uma chave que se aglomera, e ele faz back-pressure na tabela que serve.
Observe o ConsumedWriteCapacityUnits e o ThrottledRequests do GSI por partição,
não só os da tabela.
Próximos passos
- GSI vs LSI — por que um GSI tem sua própria capacidade e uma chave de partição diferente.
- Single-table design — sobrecarregando um GSI para servir muitos padrões sem multiplicar índices quentes.
- Query vs Scan — quando um índice não vale o custo de escrita.
Experimente o DynoTable para observar a capacidade consumida e os eventos de throttle de um GSI contra suas próprias tabelas antes que uma promoção as deixe vermelhas.