Wie ein DynamoDB-GSI intern gespeichert wird
Ein Global Secondary Index ist kein Zeiger zurück in deine Tabelle. Er ist eine separate, intern verwaltete Tabelle — eigene Partitionen, eigenes Key-Schema, eigene Kapazität —, die DynamoDB synchron hält, indem es Writes asynchron in sie hineinkopiert.
Von SQL kommend ist ein Index ein B-Baum, der an dieselbe physische Tabelle geschraubt und innerhalb derselben Transaktion aktualisiert wird. Ein GSI bricht beide dieser Annahmen, und fast jede GSI-Überraschung lässt sich auf diese eine Tatsache zurückführen.
Wie wird ein DynamoDB-GSI gespeichert?
Ein DynamoDB-GSI wird als separate, intern verwaltete Tabelle gespeichert — mit eigenen Partitionen, eigenem Key-Schema und eigener Kapazität — und nicht als Zeiger in die Basistabelle. DynamoDB kopiert jeden Write asynchron in den Index und speichert dabei nur die GSI-Keys, die Keys der Basistabelle sowie alle projizierten Attribute.
- Ein GSI ist seine eigene Tabelle. Er hat einen vollständig unabhängigen Partitionsraum, verschlüsselt nach dem Partition Key des GSI, nicht dem der Basistabelle.
- Writes replizieren asynchron. Dein Write committet zuerst in die Basistabelle, dann fächert DynamoDB ihn auf einem Hintergrundpfad zu jedem GSI aus.
- Nur projizierte Attribute werden gespeichert. Der Index hält die GSI-Keys, die Basis-Keys, plus welche Attribute auch immer du projiziert hast — sonst nichts.
- Der GSI-Key muss nicht eindeutig sein. Mehrere Basis-Items können sich einen GSI-Partition-/Sort-Key teilen; der Primary Key der Basis ist der Tiebreaker, der sie auseinanderhält.
Beginne mit einem Basis-Item
Nimm ein SaaS-Audit-Log. Jede privilegierte Aktion in einem Workspace wird ein
unveränderliches Event. Die Basistabelle, WorkspaceEvents, ist so
verschlüsselt, dass alle Events eines Workspaces in einer Item-Collection leben,
nach Zeit geordnet:
| EventPK | EventSK | actorId | verb | targetRef |
|---|---|---|---|---|
| WS#orbit-9 | TS#2026-06-23T14:02:11Z | USR#kp | ROLE_GRANTED | USR#mara |
EventPK = "WS#orbit-9" partitioniert nach Workspace; EventSK ist ein
ISO-Timestamp, sodass eine Query die Events eines Workspaces in chronologischer
Reihenfolge zurückgibt. Das bedient „zeig mir die Timeline dieses Workspaces"
perfekt.
Es bedient nichts anderes. Du kannst nicht fragen „was hat USR#kp über jeden
Workspace getan?" — actorId ist kein Key, also ist der einzige Weg, das auf der
Basistabelle zu beantworten, ein vollständiger Scan. Das
ist das Access Pattern, das ein GSI existiert, um es hinzuzufügen.
Füge einen GSI hinzu und beobachte, wie eine zweite Tabelle erscheint
Definiere einen GSI, ByActor, der dieselben Events neu partitioniert nach dem,
der sie ausgeführt hat:
ByActor (GSI)
GSI1PK = actorId ("USR#kp")
GSI1SK = EventSK ("TS#2026-06-23T14:02:11Z")
DynamoDB pflegt jetzt eine zweite physische Struktur. Dasselbe logische Event wird
zweimal gespeichert — einmal in der WS#orbit-9-Partition der Basistabelle und
nochmal in der USR#kp-Partition des GSI:
| GSI1PK | GSI1SK | EventPK | EventSK | verb |
|---|---|---|---|---|
| USR#kp | TS#2026-06-23T14:02:11Z | WS#orbit-9 | TS#2026-06-23T14:02:11Z | ROLE_GRANTED |
Beachte, was mitfuhr: Die Keys der Basistabelle (EventPK, EventSK) werden
in jedem GSI-Item automatisch gespeichert. So kann ein GSI-Treffer dich zurück zum
vollen Item zeigen — und warum ein
KEYS_ONLY-Index trotzdem Speicher
kostet.
Was tatsächlich im GSI lebt
Der Index kopiert nicht das ganze Item. Jeder GSI-Eintrag hält genau drei Dinge, und du kontrollierst nur das dritte:
| Im GSI gespeichert | Woher es kommt | Optional? |
|---|---|---|
| GSI-Partition + Sort Key | Die Attribute, die du als GSI-Keys nanntest | Nein |
| Key(s) der Basistabelle | Aus jedem Basis-Item kopiert | Nein |
| Projizierte Attribute | Deine Projection-Wahl | Ja |
Projection ist KEYS_ONLY, INCLUDE (eine benannte Liste) oder ALL. Eine
Query auf dem GSI kann nur Attribute zurückgeben, die im Index sind.
Frage nach einem, das nicht projiziert ist, und DynamoDB holt es nicht transparent — du bekommst nichts für dieses Feld zurück. (AWS GSI-Doku)
Das ist die relationale Falle umgekehrt: SQL würde zurück zum Heap joinen für die fehlende Spalte. Ein GSI tut das nie. Die Projektion ist der ganze Vertrag.
Wie ein Write den Index erreicht
Die Replikation ist der Teil, der die SQL-Intuition am härtesten bricht. Ein Basis-Write und sein Index-Update sind keine atomare Operation.
Wenn du PutItem machst, committet DynamoDB dauerhaft in die Basistabelle,
bestätigt deinen Write, und dann propagiert es die Änderung auf einen
Hintergrundpfad, der jeden GSI aktualisiert. Die Bestätigung wartet nicht auf den
Index.
Hier ist die Reihenfolge der Ereignisse für unseren Audit-Write, oben nach unten:
Der Caller bekommt sein 200 OK bei Schritt drei, bevor die Schritte vier bis
sechs fertig sind — sodass eine Query auf ByActor in der Lücke ein
brandneues Event verpassen kann.
Diese Asynchronie ist beabsichtigt, kein Defekt: Es ist die Abstammung des Amazon-Dynamo-Papers von 2007, das Verfügbarkeit über synchrone Konsistenz wählte. Die vollen Konsequenzen leben in warum ein GSI letztendlich konsistent ist.
Der GSI-Key ist kein eindeutiger Key
In SQL ist ein nicht-eindeutiger Secondary Index der Standard und ein eindeutiger eine Constraint, für die du dich entscheidest. Ein GSI ist das Gegenteil: Er hat keine Eindeutigkeitsgarantie, niemals.
Zwei Audit-Events vom selben Actor mit Timestamps, die kollidieren, würden sich
denselben GSI1PK und GSI1SK teilen. DynamoDB speichert beide — es
unterscheidet sie intern durch den Primary Key der Basistabelle, der immer
mitgeführt wird.
Also kann eine GSI-Query für einen Actor in einem Moment legitim mehrere Items
zurückgeben. Wenn du eine Zeile pro Key angenommen hast, so wie ein eindeutiger
SQL-Index sie dir geben würde, ist das das Footgun.
Wenn du den Index abfragst, schreibt der
DynamoDB Expression Builder die
KeyConditionExpression mit korrekt escapten Names und Values — z. B. einen Actor
seit einem Stichtag matchend:
KeyConditionExpression: "#a = :actor AND #ts > :since"
ExpressionAttributeNames: { "#a": "actorId", "#ts": "EventSK" }
ExpressionAttributeValues: {
":actor": { "S": "USR#kp" },
":since": { "S": "TS#2026-06-01T00:00:00Z" }
}Kapazität lebt beim Index, nicht bei der Tabelle
Weil der GSI seine eigene Tabelle ist, hat er seine eigene Read- und
Write-Kapazität, getrennt von der Basistabelle abgerechnet und gethrottelt. Ein
Read aus ByActor verbraucht die Read Units des GSI, nie die der Tabelle.
Die umgekehrte Kopplung ist die, die beißt: Jeder Basistabellen-Write schreibt auch den Index, und wenn der GSI das nicht absorbieren kann, übt er Gegendruck auf den Basis-Write aus. Dieser Mechanismus bekommt seinen eigenen Guide — wann ein GSI Basistabellen-Writes throttelt.
Das ist auch, warum der Partition Key eines GSI genauso wichtig ist wie der der Basistabelle. Ein Low-Cardinality-GSI-Key klumpt Writes auf eine Index-Partition, selbst wenn die Basis-Writes perfekt verteilt sind — eine Hot Partition, die du durch Neuverschlüsselung erschaffen hast.
Fallstricke und nächste Schritte
- Erwarte keine nicht-projizierten Attribute zurück. Eine GSI-
Querygibt nur zurück, was der Index speichert. Wenn du das volle Item brauchst, projiziere es oder hole es aus der Basistabelle über die mitgeführten Keys. - Behandle einen GSI-Key nicht als eindeutig. Plane dafür, dass eine
Querymehr als ein Item pro Key zurückgibt; der Primary Key der Basis ist die einzige echte Identität. - Lies einen GSI nicht direkt nach dem Write, der ihn gefüttert hat. Der Async-Pfad bedeutet, dass der Index deinen Write vielleicht noch nicht zeigt — lies die Basistabelle, wenn du Read-your-own-Writes brauchst.
- Dimensioniere die Kapazität des GSI bewusst. Sie ist unabhängig bei Reads und eine versteckte Abhängigkeit bei Writes.
Das ganze Spiel ist, Key-Formen zu wählen, die deinen Patterns dienen — Single-Table-Design überlädt einen GSI über viele von ihnen; GSI vs LSI deckt ab, wann stattdessen ein lokaler Index passt.
Baue und previewe deine GSI-KeyConditionExpression im
DynamoDB Expression Builder, dann
probiere DynoTable aus, um die projizierten Attribute eines Index zu
inspizieren und zu beobachten, wie Writes in den GSI replizieren — auf deinen
eigenen Tabellen.