Fortgeschritten7 Min. Lesezeit

DynamoDB-Filterstrategien

„Filtern" in DynamoDB bedeutet vier verschiedene Dinge, die dasselbe Wort tragen. Drei engen die Daten vor dem Lesen und Abrechnen ein; eines — das mit dem Namen Filter — engt sie danach ein. Zu wissen, was was ist, ist der Großteil des Könnens.

Wie funktioniert das Filtern in DynamoDB?

DynamoDB kennt vier Arten zu filtern, und nur eine davon läuft, nachdem dir bereits abgerechnet wurde. Der Partition Key wählt eine Partition aus, der Sort Key engt eine Scheibe ein, und ein Sparse Index filtert nach dem Vorhandensein eines Attributs — alle drei senken deine Lesekosten, bevor abgerechnet wird. Eine FilterExpression läuft nach dem Lesevorgang, verkleinert also die Antwort, aber nie die Rechnung.

  • Partition Key ist der günstigste Filter: Er wählt die Partition, sodass du den Rest der Tabelle nie anfasst.
  • Sort Key filtert innerhalb einer Partition mit begins_with, between, <, > — immer noch vor der Abrechnung, immer noch günstig.
  • Sparse Index filtert per Abwesenheit: Ein Item erscheint nur im Index, wenn es das indexierte Attribut hat, also ist der Index die gefilterte Menge.
  • FilterExpression ist die Falle: Sie läuft, nachdem DynamoDB den Lesevorgang abgerechnet hat, also verkleinert sie deine Antwortgröße, aber nie deine Rechnung.

Stelle das Beispiel auf

Ein Produktkatalog. Eine Tabelle, Partition Key PK, Sort Key SK:

PK = "DEPT#kitchen"   SK = "PROD#00194"

Jedes Produkt trägt außerdem price, inStock (ein Boolean) und clearanceAt (ein Unix-Timestamp, nur auf Items vorhanden, die zum Abverkauf markiert sind). Items in einer Abteilung teilen eine Partition, sortiert nach Produkt-ID.

Wir wollen vier Zugriffsmuster. Jedes mappt auf eine andere Filterstrategie — und die falsche Wahl bei einem davon ist ein Scan, für den du für immer zahlst.

Nach Partition Key filtern

„Gib mir jedes Produkt in kitchen." Der Partition Key beantwortet das direkt:

Query  PK = "DEPT#kitchen"

DynamoDB liest genau eine Partition. Nichts anderes in der Tabelle wird angefasst oder abgerechnet. Das ist der einzige Filter, der im Sinne, der zählt, kostenlos ist — es ist der Unterschied zwischen Query und Scan.

Aus SQL kommend fühlt sich das rückwärts an: Es gibt kein WHERE department = 'kitchen', das einen Index scannt, du benennst einfach die Partition. Wenn du sie nicht benennen kannst, ist das ein Modellierungsproblem, kein Abfrageproblem.

Nach Sort Key filtern

„Gib mir Kitchen-Produkte ab PROD#00100 aufwärts." Der Sort Key engt innerhalb der Partition ein, und zwar bevor der Lesevorgang abgerechnet wird:

Query  PK = "DEPT#kitchen"  AND  SK between "PROD#00100" AND "PROD#00200"

Sort-Key-Bedingungen sind mit Absicht begrenzt: =, <, <=, >, >=, between und begins_with. Kein OR, kein beliebiges Prädikat.

Diese Beschränkung ist, was den Lesevorgang gezielt hält — DynamoDB läuft eine zusammenhängende Scheibe ab, nicht die ganze Partition.

Der Hebel hier ist, wie du den Sort Key kodierst. Wenn dein Muster „nach Preisband" ist, hilft ein PROD#<id>-Sort-Key nicht — du würdest den Preis in den Key einbacken.

Das ist eine Sort-Key-Strategie-Entscheidung, die zur Design-Zeit getroffen wird, nicht zur Abfrage-Zeit.

Nach Sparse Index filtern

„Gib mir alles, was gerade im Abverkauf ist." Die meisten Produkte sind es nicht, also willst du nicht den Katalog lesen, um die wenigen zu finden, die es sind.

Ein Sparse Index löst das durch Abwesenheit. Ein Global Secondary Index enthält ein Item nur dann, wenn dieses Item beide Key-Attribute des Index hat.

Setze den GSI-Partition-Key auf clearanceAt — nur auf Abverkaufs-Items vorhanden — und der Index hält nichts anderes.

AWS buchstabiert das aus: Ein GSI „enthält nur Items, die die indexierten Attribute haben", also werden Items, denen das Key-Attribut fehlt, einfach nicht propagiert (AWS — Sparse Indexes nutzen).

JaNeinBasistabelle alle ProdukteHat clearanceAt?Repliziert in ClearanceIndexNicht im IndexQuery auf den Index = nurAbverkaufs-Items

Jetzt liest die Abfrage nur die Abverkaufs-Items, abgerechnet nur für sie:

Query  ON ClearanceIndex   GSI_PK = "CLEARANCE"   (sorted by clearanceAt)

Der Filter passierte, als du die Daten geschrieben hast — indem du wähltest, ob du clearanceAt überhaupt setzt. Der Index ist die gefilterte Menge. Siehe GSI vs. LSI dafür, welcher Index-Typ passt.

Mit FilterExpression filtern

„Gib mir Kitchen-Produkte, die auf Lager sind." inStock ist kein Key-Attribut, also greifst du zu einer FilterExpression:

Query  PK = "DEPT#kitchen"
Filter inStock = true

Hier ist die Falle. DynamoDB liest jedes Item in der kitchen-Partition, rechnet die Kapazität für alle ab und dann verwirft es die nicht vorrätigen.

Die offizielle Regel: Eine Filter-Expression wird „angewendet, nachdem ein Query fertig ist, aber bevor die Ergebnisse zurückgegeben werden", und „verbraucht keine zusätzlichen Read Capacity Units" — du hast bereits für den vollen Lesevorgang gezahlt (AWS — Filter-Expressions für Query).

Wenn kitchen also 10.000 Produkte hat und 12 auf Lager sind, zahlst du dafür, 10.000 zu lesen. Die Antwort ist klein; die Rechnung nicht. FilterExpression verkleinert die Payload, die über die Leitung geht, nie den Lesevorgang.

Es gibt eine zweite, schärfere Kante: Pagination wird vor dem Filtern abgerechnet. Eine Seite ist 1 MB an gelesenen Items, nicht 1 MB an Treffern.

Ein Filter kann eine leere Seite mit gesetztem LastEvaluatedKey zurückgeben — DynamoDB hat ein volles Megabyte gelesen, nichts getroffen, dir ein leeres Array gereicht. Du blätterst weiter, und du hast für jede leere Seite gezahlt.

Baue die Expression — Namen, Werte und das richtige Reservierte-Wort-Escaping — mit dem DynamoDB Expression Builder, damit die #inStock/:val-Platzhalter beim ersten Versuch korrekt sind.

Vergleiche die vier

Wann sie filtertSenkt Lesekosten?Prädikat-StärkeEinrichtungskosten
Partition KeyVor dem LesenJa — eine PartitionNur GleichheitKostenlos (es ist der Key)
Sort KeyVor dem LesenJa — eine ScheibeBereich / begins_withSort-Key-Design
Sparse IndexVor dem LesenJa — nur IndexVorhandensein eines AttributsZusätzlicher GSI + Schreibkosten
FilterExpressionNach dem LesenNeinFast jede BedingungKeine

Lies die Tabelle von oben nach unten: Die Prädikat-Stärke geht hoch, die Kostenkontrolle geht runter. FilterExpression kann alles präzise ausdrücken, gerade weil sie auf bereits gelesenen Items läuft — das ist derselbe Grund, warum sie dir kein Geld sparen kann.

Sieh es in DynoTable

Wenn du einen Query mit einem Filter ausführst, ist die Lücke zwischen gelesenen und zurückgegebenen Items die ganze Geschichte. DynoTable zeigt die verbrauchte Kapazität neben der Ergebniszahl — sodass ein Filter, der still die ganze Partition liest, sichtbar ist und nicht in deiner Monatsrechnung versteckt bleibt.

Für echte Item-übergreifende Fragen, die ein Filter nicht beantworten kann — „Durchschnittspreis pro Abteilung", „Produkte auf Lager, gejoint mit ihren Bewertungen" — führt DynoTables SQL Workbench GROUP BY, JOIN und Aggregate clientseitig über einer begrenzten Ergebnismenge aus, statt zu einem tabellenweiten Scan zu kompilieren.

Fallstricke und nächste Schritte

  • Verwende FilterExpression nicht als deinen primären Zugriffspfad. Wenn ein Muster häufig ist, modelliere es in einen Key oder einen Sparse Index. Ein Filter ist für das letzte bisschen Einengung, nicht für den Großteil davon.
  • Achte auf leere Seiten. Eine gefilterte Abfrage kann lange blättern und nichts zurückgeben. Respektiere LastEvaluatedKey; nimm nicht an, dass eine leere Seite „fertig" bedeutet.
  • Ein Sparse Index ist nicht kostenlos. Er kostet Schreibkapazität und Speicher für jedes Item, das in ihm landet — günstig, wenn das Attribut selten ist, weniger, wenn nicht.

Schätze ab, was ein gefilterter Lesevorgang tatsächlich kosten wird, mit dem Kapazitätsrechner, und probiere DynoTable aus, um die verbrauchte Kapazität gegen zurückgegebene Zeilen auf deinen eigenen Tabellen zu beobachten.

Aktualisiert