Profi7 Min. Lesezeit

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:

WorkspaceEvents (base table)
EventPKEventSKactorIdverbtargetRef
WS#orbit-9TS#2026-06-23T14:02:11ZUSR#kpROLE_GRANTEDUSR#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:

ByActor (GSI) — its own partition space
GSI1PKGSI1SKEventPKEventSKverb
USR#kpTS#2026-06-23T14:02:11ZWS#orbit-9TS#2026-06-23T14:02:11ZROLE_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 gespeichertWoher es kommtOptional?
GSI-Partition + Sort KeyDie Attribute, die du als GSI-Keys nanntestNein
Key(s) der BasistabelleAus jedem Basis-Item kopiertNein
Projizierte AttributeDeine Projection-WahlJa

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:

PutItemWS#orbit-9 EventIn Basis-partition committen200 OKan CallerAsync-Pfad:GSI-Keys extrahierenZu ByActor-Partition USR#kp routenProjizierteAttribute schreiben

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-Query gibt 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 Query mehr 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.

Aktualisiert