Fortgeschritten7 Min. Lesezeit

DynamoDB-Sparse-Indexes

Ein Sparse Index ist ein Secondary Index, der nur die Items hält, die sein Key-Attribut tragen — sodass eine kleine, heiße Subset einer riesigen Tabelle zu ihrer eigenen vorgefilterten, ready-to-query Collection wird.

Du hast Millionen Rows, aber die Query, die du den ganzen Tag fährst, berührt einen winzigen Slice: die offenen Support-Tickets, die unbezahlten Invoices, die Accounts, die zur Review geflaggt sind.

Diesen Slice zu filtern scannt trotzdem die ganze Tabelle und rechnet jeden Read ab. Ein Sparse Index macht den Index selbst klein.

Was ist ein Sparse Index in DynamoDB?

Ein Sparse Index ist ein Secondary Index, der nur die Items hält, die sein Key- Attribut tragen. Weil DynamoDB jedes Item skippt, dem dieser Key fehlt, erfindest du einen Key, den nur die gewünschten Items schreiben — offene Tickets, unbezahlte Invoices — und der Index wird genau diese Subset. Queries lesen dann nur ihn, kein Filter, keine verschwendete Read Capacity.

  • Ein Secondary Index indexiert nur Items, die seinen Key haben. Omitte den Key auf einem Item und es betritt den Index nie — kein Placeholder, keine Null- Row.
  • Also erfindest du einen Key, den nur die gewünschten Items tragen. Schreibe ihn auf die Items, die du queryst, entferne ihn auf dem Rest. Der Index wird genau diese Subset.
  • Die Query liest nur die Subset, kein Filter. Ihre Größe trackt das kleine Hot Set, nicht die Table-Total.
  • REMOVE ist der Hebel, nicht Blanking. Ein leerer String ist kein gültiger Index-Key — DynamoDB rejectet den ganzen Write mit ValidationException — also musst du das Attribut löschen.

Das Problem: Filtering spart keine Reads

Aus SQL nimmst du an, eine WHERE-Klausel verengt die Arbeit. DynamoDBs FilterExpression tut das nicht. Sie läuft nachdem Items gelesen wurden, nicht vorher.

Laut AWS Developer Guide „verbraucht eine Query dieselbe Menge Read Capacity, unabhängig davon, ob eine Filter Expression vorhanden ist“ — du zahlst für jedes untersuchte Item und wirfst dann die Non-Matches weg.

Wenn also 50 deiner 5 Millionen Tickets offen sind, liest eine gefilterte Query/Scan durch Millionen, um dir diese 50 zu reichen.

Das ist der Fallstrick hinter jedem „warum ist mein Scan so teuer“-Thread; Query vs. Scan hat das volle Cost-Bild.

Ein Sparse Index umgeht es, indem er den Index selbst klein macht.

Wie Sparseness arbeitet

Ein Secondary Index indexiert nur Items, die die Key-Attribute des Index tatsächlich haben.

Die AWS Docs zu Sparse Indexes sagen das klar: DynamoDB schreibt ein Item nur dann in einen Secondary Index, wenn dieses Item die Key-Attribute des Index trägt, sodass ein Index über ein selten gesetztes Attribut natürlich klein bleibt.

Fehlt der Partition Key (oder Sort Key) des GSI auf einem Item, schreibt DynamoDB es einfach nicht in den Index. Kein Placeholder, keine Null-Row — das Item ist abwesend.

Diese „Absence by Default“ ist der ganze Trick. Indexiere kein status-Attribut, das jedes Item trägt. Erfinde ein Attribut, das nur die Items, die du querien willst, überhaupt tragen.

Der Index wird dann eine saubere Liste genau dieser Items, und eine Query dagegen liest nur sie — kein Filter, keine verschwendete Capacity.

Stell dir vor, die Base Table füttert den Index, und nur Items mit dem Key queren hinüber:

Key entferntKey entferntSparse GSI (nur open)OpenOpenBase Table (alle Items)Open: hat KeyOpen: hat KeyClosed: kein KeyClosed: kein Key

Nur die keyed (open) Items replizieren in den Index; geschlossene betreten ihn nie.

Das ist dieselbe Key-Shaping-Mindset wie Single-Table Design: Keys sind Tools, die du für ein spezifisches Access Pattern baust, keine treuen Spiegel deiner Daten.

Praxisbeispiel: „nur offene Tickets“

Nimm eine Support-Ticket-Tabelle. Die Base Table ist gekeyed, um ein Ticket per Id zu fetchen und die Tickets eines Customers zu listen:

PKSKattributes
TICKET#a91fDETAILsubject, body, priority, openState
CUSTOMER#88TICKET#a91fsubject, priority, openState

Über die Lebensdauer der Tabelle enden die meisten Tickets closed. Aber die Dashboard-Query, die deine Agents den ganzen Tag treffen, ist „zeig mir jedes offene Ticket, älteste zuerst“ — ein paar hundert Rows, versteckt in Millionen.

Definiere einen mit Partition Key openBucket und Sort Key openedAt, und schreibe openBucket nur auf offenen Tickets. Setze ihn, wenn das Ticket erstellt wird; REMOVE ihn, wenn das Ticket resolved.

PKSKopenBucketopenedAt
TICKET#a91fDETAILOPEN2026-06-23T09:14:00Z← open: in the index
TICKET#b02cDETAILOPEN2026-06-22T16:40:00Z← open: in the index
TICKET#77deDETAIL(absent)2026-05-30T11:02:00Z← closed: NOT in the index

Tickets a91f und b02c tragen openBucket, also leben sie im GSI. Ticket 77de wurde resolved und hatte openBucket entfernt, also fiel es still raus. Das Dashboard ist jetzt eine günstige Query:

Query  IndexName = "open-tickets-index"
KeyConditionExpression: openBucket = "OPEN"
ScanIndexForward: true        # oldest first

Das liest nur offene Tickets. Wenn Tickets schließen, schrumpft der Index von selbst — seine Größe trackt die offene Population, nie die Total.

Ein statischer Partition-Wert ("OPEN") ist hier fine, gerade weil das Set klein bleibt. Ein riesiges Open-Set bräuchte einen geshardeten Partition Key, aber der „kleine Subset“-Index ist genau dort, wo ein Wert der richtige Call ist.

Der Übergang, der es funktionieren lässt, ist eine einzelne — das Attribut entfernen, wenn das Ticket resolved.

Prototype diese REMOVE-Klausel und die typed Key Condition für die Read-Seite im DynamoDB Expression Builder, statt ExpressionAttributeNames und :val-Placeholder selbst zusammenzusetzen.

In DynoTable umsetzen

Der harte Teil eines Sparse Index ist zu sehen, welche Items es in den Index geschafft haben versus welche still herausgefallen sind.

DynoTable lässt dich eine Table-View auf einen Secondary Index umschalten und genau die populated Subset sehen. So kannst du bestätigen, dass ein resolved Ticket wirklich open-tickets-index verlassen hat, statt mit einem stale Key zu verweilen.

Die Support-Ticket-Tabelle durch ihren open-tickets Sparse GSI in DynoTable — nur Items, die den openBucket-Key tragen.
Die Support-Ticket-Tabelle durch ihren open-tickets Sparse GSI in DynoTable — nur Items, die den openBucket-Key tragen.

Fallstricke und nächste Schritte

Ein paar Dinge zum Beobachten:

  • Entferne den Key, blanke ihn nicht. Ein leerer String ist kein gültiger Index- Key — openBucket = "" zu schreiben failt mit ValidationException, also wird das Item nie damit indexiert. Um ein Item aus dem Index zu droppen, musst du das Attribut REMOVEen.
  • Der Index ist . GSIs updaten asynchron, also kann ein gerade resolved Ticket kurz noch erscheinen — GSI-Reads unterstützen nur Eventual Consistency. Vertrau ihm nicht für „ist dieses Ticket jetzt offen“.
  • Achte auf Attribute. Eine Query auf dem Index gibt nur die Attribute zurück, die hinein projeziert sind. Braucht das Dashboard Subject und Priority, projeziere sie — oder zahle ein extra GetItem für das volle Base-Item.
  • Sowohl GSIs als auch LSIs können sparse sein — der Hebel ist derselbe: omitte den Sort Key des Index auf Items, die du nicht indexiert haben willst. Ein GSI passt meist besser: du kannst ihn nach Table Creation hinzufügen und ihm eigenes Key Schema und Capacity geben. GSI vs. LSI bricht den Trade-off runter.

Sparse Indexes sind eine der ältesten Ideen im Modell. Das originale 2007 Amazon Dynamo Paper baute den Store darum, bekannte, high-volume Access Patterns günstig zu bedienen.

Ein Sparse Index ist genau das: forme die Keys so, dass die gängige Query nichts liest, was sie nicht braucht.

Um einen für real zu bauen und zu inspizieren, lade DynoTable herunter, zeige ihn auf deine Tabelle und flippe die Data-View auf deinen Sparse GSI — beobachte die Subset updaten, wenn Items den Index-Key gewinnen und verlieren.

Aktualisiert