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.
FilterExpressionist 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).
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 filtert | Senkt Lesekosten? | Prädikat-Stärke | Einrichtungskosten | |
|---|---|---|---|---|
| Partition Key | Vor dem Lesen | Ja — eine Partition | Nur Gleichheit | Kostenlos (es ist der Key) |
| Sort Key | Vor dem Lesen | Ja — eine Scheibe | Bereich / begins_with | Sort-Key-Design |
| Sparse Index | Vor dem Lesen | Ja — nur Index | Vorhandensein eines Attributs | Zusätzlicher GSI + Schreibkosten |
| FilterExpression | Nach dem Lesen | Nein | Fast jede Bedingung | Keine |
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
FilterExpressionnicht 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.