Warum ein GSI in DynamoDB Schreibvorgänge auf der Basistabelle throttelt
Du schreibst in deine Tabelle. Der Schreibvorgang scheitert mit einer Durchsatz-Exception — aber die Exception nennt einen Global Secondary Index, nicht die Tabelle. Die Tabelle hat freie Kapazität.
Aus SQL kommend ist das Unsinn: Ein Sekundärindex kann kein INSERT blockieren. In
DynamoDB kann er das, und der Mechanismus heißt GSI-Back-Pressure.
Warum throttelt ein DynamoDB-GSI Schreibvorgänge auf der Basistabelle?
DynamoDB throttelt den Schreibvorgang auf der Basistabelle, weil jeder Schreibvorgang auch in jeden GSI repliziert wird, und wenn eine GSI-Partition ihren Anteil nicht absorbieren kann, übt DynamoDB Back-Pressure aus, damit der Index nicht dauerhaft zurückfällt. Ein unterdimensionierter GSI-Key oder einer mit geringer Kardinalität wird so zu einer harten Obergrenze für deine Schreibrate auf der Basistabelle.
- Ein Schreibvorgang auf der Basistabelle schreibt auch in jeden GSI. Wenn ein GSI seinen Anteil nicht absorbieren kann, throttelt DynamoDB den Schreibvorgang der Basistabelle, damit der Index nicht dauerhaft zurückfällt. (AWS-Doku)
- Eine gleichmäßige Basistabelle rettet dich nicht. Der GSI wird nach seinem
eigenen Key partitioniert. Ein GSI-Key mit geringer Kardinalität (wie
status) erzeugt eine heiße Index-Partition, selbst wenn die Schreibvorgänge der Basistabelle perfekt verteilt sind. - Die Exception lügt über das Opfer. Die
ResourceArnzeigt auf den GSI; die Operation, die tatsächlich gethrottelt wird, ist dein Schreibvorgang in die Tabelle. - Die Lösung ist Kapazität oder Key-Design, nicht Retry-Schleifen — erhöhe den GSI-Durchsatz oder wähle einen GSI-Partition-Key, der streut.
Wie ein einzelner Schreibvorgang den Index berührt
Ein PutItem auf der Basistabelle ist nicht ein Schreibvorgang. DynamoDB repliziert
die projizierten Attribute des Items asynchron, nach einem letztendlich konsistenten
Modell, in jeden GSI. Ein logischer Schreibvorgang fächert zu N physischen
Schreibvorgängen auf — Tabelle plus jeder Index.
Diese Replikation ist nicht kostenlos und nicht optional. Der GSI muss mithalten, sonst driftet der Index bei jeder Operation weiter von der Tabelle weg.
Um diese Drift zu stoppen, wendet DynamoDB Back-Pressure an: Es throttelt den Quell-Schreibvorgang, damit der Index nie unbegrenzt veraltet.
Die Schreibkapazität des GSI ist also eine harte Obergrenze für die Schreibrate deiner Basistabelle — obwohl du nie direkt in den GSI schreibst.
Ein durchgespieltes Beispiel: eine Bestelltabelle
Angenommen, du betreibst eine Bestelltabelle. Das Basis-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 |
Die Schreibvorgänge der Basistabelle sind gesund. CUST#... hat hohe
Kardinalität, also verteilen sich Bestellschreibvorgänge gleichmäßig über die
Basispartitionen. Kein Hot Key, reichlich Kapazität.
Jetzt fügst du einen GSI hinzu, um „zeige mir jede Bestellung in einem bestimmten Status" zu beantworten:
| field | value | note |
|---|---|---|
| GSI-PK | order_state | "PENDING" | "PROCESSING" | "SHIPPED" | "CANCELED" |
| GSI-SK | SK |
Vier mögliche Partition-Key-Werte. Während eines Flash-Sales landet fast jede neue
Bestellung in order_state = "PENDING". Jeder dieser Schreibvorgänge trifft
dieselbe GSI-Partition.
Diese Partition hat ein Durchsatzlimit pro Partition, und du hast gerade deinen gesamten Schreibsturm darauf gerichtet.
Die Basistabelle ist in Ordnung. Die PENDING-GSI-Partition brennt. DynamoDB
throttelt das PutItem der Basistabelle, um den Index zu schützen.
Der Ablauf, der dich beißt
Hier ist der Back-Pressure-Pfad — Basis-Schreibvorgang ausgewogen, Index-Schreibvorgang konzentriert:
Der Throttle wandert rückwärts: Eine heiße GSI-Partition weist den Schreibvorgang der Basistabelle ab, der sie gespeist hat.
Lies die Exception, nicht dein Bauchgefühl
Der Exception-Typ sagt dir genau, welche Obergrenze du getroffen hast. Die
ResourceArn nennt den GSI; die gethrottelte Operation ist weiterhin der
Tabellen-Schreibvorgang.
| Modus | Reason-Code | Was ausging |
|---|---|---|
| Provisioned | IndexWriteProvisionedThroughputExceeded | Provisioned-Schreibkapazität des GSI |
| Provisioned | IndexWriteKeyRangeThroughputExceeded | Eine einzelne heiße GSI-Partition |
| On-Demand | IndexWriteMaxOnDemandThroughputExceeded | Konfigurierte On-Demand-Obergrenze des GSI |
| On-Demand | IndexWriteAccountLimitExceeded | Account-/Region-Durchsatzgrenze |
Quelle: Understanding GSI write throttling and back pressure.
Der KeyRange-Grund ist der verräterische Hinweis auf den Hot-Partition-Fall
oben: Die Gesamtkapazität des GSI kann in Ordnung aussehen, während ein
Key-Bereich gesättigt ist.
Wie du es behebst
Gib dem GSI Raum. Die einfachste Ursache ist Unterdimensionierung. Ein GSI hat seine eigene Lese- und Schreibkapazität, vollständig getrennt von der Tabelle — siehe GSI vs. LSI.
Wenn du die Tabelle großzügig provisioniert und den GSI dünn gelassen hast, erhöhe die Schreibkapazität des GSI (oder sein On-Demand-Maximum).
Repariere den Partition Key. Kapazität rettet einen Key mit geringer Kardinalität nicht — du kannst eine einzelne heiße Partition nicht überprovisionieren. Wähle einen GSI-Partition-Key, der streut.
Setze ihn zusammen: order_state#shard, wobei shard ein kleines zufälliges
Suffix ist, oder falte das Datum ein (PENDING#2026-06-23). Schreibvorgänge
verteilen sich über Partitionen, und du fragst einen Status weiterhin per Query
über die Shards ab.
Projiziere weniger Attribute. Jeder GSI-Schreibvorgang kopiert die projizierten
Attribute. Eine KEYS_ONLY- oder enge INCLUDE-Projektion bedeutet kleinere
Index-Schreibvorgänge und weniger Druck als ALL. Projiziere nicht, was du nie aus
dem Index liest.
Verwirf den GSI, wenn er nur fürs Reporting ist. Wenn „Bestellungen nach Status" eine gelegentliche Admin-Frage ist und kein heißer Pfad, kann ein periodischer Scan mit einem Filter einen dauerhaft heißen Index schlagen — wäge es gegen Query vs. Scan ab.
Wenn du diesen Index abfragst, schreibt der
Expression Builder die KeyConditionExpression
für dich — z. B. #s = :state AND begins_with(SK, :prefix) — mit korrekt
escapeten Namen und Werten:
KeyConditionExpression "#s = :state"
ExpressionAttributeNames { "#s": "order_state" }
ExpressionAttributeValues { ":state": { "S": "PENDING" } }
Die Falle, die du dir merken solltest
Der relationale Instinkt — „Indizes verlangsamen Schreibvorgänge nur ein bisschen" — lässt sich nicht übertragen. Ein DynamoDB-GSI ist eine Durchsatz-Abhängigkeit, keine passive Struktur. Unterdimensioniere ihn oder wähle einen klumpenden Key, und er übt Back-Pressure auf die Tabelle aus, der er dient.
Beobachte die ConsumedWriteCapacityUnits und ThrottledRequests des GSI pro
Partition, nicht nur die der Tabelle.
Nächste Schritte
- GSI vs. LSI — warum ein GSI seine eigene Kapazität und einen anderen Partition Key hat.
- Single-Table-Design — einen GSI überladen, um viele Muster zu bedienen, ohne heiße Indizes zu vervielfachen.
- Query vs. Scan — wann ein Index seine Schreibkosten nicht wert ist.
Probiere DynoTable aus, um die verbrauchte Kapazität und Throttle-Events eines GSI gegen deine eigenen Tabellen zu beobachten, bevor ein Sale sie rot färbt.