DynamoDB-Filtering-Strategien
„Filtering“ in DynamoDB meint vier verschiedene Dinge unter demselben Wort. Drei
verengen die Daten bevor sie gelesen und abgerechnet werden; eines — das
namens Filter — verengt sie danach. Zu wissen, welches welches ist, ist der
größte Teil der Skill.
Wie funktioniert Filtering in DynamoDB?
DynamoDB hat vier Wege zu filtern, und nur einer läuft, nachdem du abgerechnet
wurdest. Der wählt eine Partition, der Sort Key
verengt einen Slice, und ein Sparse Index filtert per Attribut-Präsenz — alle drei
senken deine Read-Kosten vor dem Metern. Eine FilterExpression läuft nach dem
Read, also schrumpft sie die Response, nie die Rechnung.
- ist der günstigste Filter: er wählt die Partition, sodass du den Rest der Tabelle nie berührst.
- filtert innerhalb einer Partition mit
begins_with,between,<,>— immer noch vor dem Billing, immer noch günstig. - filtert per Absence: ein Item erscheint nur im Index, wenn es das indexierte Attribut hat, also ist der Index das gefilterte Set.
FilterExpressionist die Falle: sie läuft, nachdem DynamoDB den Read metert, also kürzt sie deine Response-Größe, nie deine Rechnung.
Das Beispiel aufsetzen
Ein Product Catalog. Eine Tabelle, Partition Key PK, Sort Key SK:
PK = "DEPT#kitchen" SK = "PROD#00194"
Jedes Product trägt auch price, inStock (ein Boolean) und clearanceAt (ein
Unix-Timestamp, nur auf Items vorhanden, die für Clearance markiert sind). Items
in einer Department teilen eine Partition, sortiert nach Product-Id.
Wir wollen vier Access Patterns. Jedes mappt auf eine andere Filtering-Strategie —
und die falsche Wahl bei jedem ist ein Scan, den du forever zahlst.
Per Partition Key filtern
„Gib mir jedes Product in kitchen.“ Der Partition Key beantwortet das direkt:
Query PK = "DEPT#kitchen"
DynamoDB liest genau eine Partition. Nichts anderes in der Tabelle wird berührt
oder abgerechnet. Das ist der einzige Filter, der im Sinne, der zählt, gratis ist
— der Unterschied zwischen Query und Scan.
Aus SQL fühlt sich das rückwärts an: es gibt kein WHERE department = 'kitchen',
das einen Index scannt, du nennst einfach die Partition. Wenn du sie nicht
nennen kannst, ist das ein Modeling-Problem, kein Query-Problem.
Per Sort Key filtern
„Gib mir Kitchen-Products ab PROD#00100.“ Der Sort Key verengt innerhalb der
Partition, und zwar bevor der Read gemetered wird:
Query PK = "DEPT#kitchen" AND SK between "PROD#00100" AND "PROD#00200"
Sort-Key-Conditions sind absichtlich begrenzt: =, <, <=, >, >=,
between und begins_with. Kein OR, kein beliebiges Prädikat.
Dieser Constraint hält den Read gezielt — DynamoDB läuft einen contiguous Slice, nicht die ganze Partition.
Der Hebel hier ist wie du den Sort Key kodierst. Wenn dein Pattern „per Price
Band“ ist, hilft ein PROD#<id>-Sort-Key nicht — du würdest Price in den Key
backen.
Das ist eine Sort-Key-Strategie-Entscheidung, zur Design-Zeit, nicht zur Query-Zeit.
Per Sparse Index filtern
„Gib mir alles, was aktuell on Clearance ist.“ Die meisten Products sind es nicht, also willst du den Catalog nicht lesen, um die wenigen zu finden.
Ein Sparse Index löst das per Absence. Ein enthält ein Item nur, wenn dieses Item beide Key-Attribute des Index hat.
Setze ein konstantes clearance = "CLEARANCE"-Flag als GSI-Partition-Key —
geschrieben nur auf Clearance-Items — mit clearanceAt als Sort Key, und der
Index hält nichts anderes.
AWS sagt das klar: ein Global Secondary Index enthält nur Items, die die Key- Attribute des Index haben, also werden Items ohne das Key-Attribut einfach nicht propagiert (AWS — Take advantage of sparse indexes).
Jetzt liest die Query nur die Clearance-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 gewählt hast,
ob clearanceAt überhaupt gesetzt wird. Der Index ist das gefilterte Set. Siehe
GSI vs LSI dafür, welcher Index-Typ passt.
Mit FilterExpression filtern
„Gib mir Kitchen-Products, die in Stock sind.“ inStock ist kein Key-Attribut,
also greifst du nach einer FilterExpression:
Query PK = "DEPT#kitchen"
Filter inStock = true
DynamoDB liest jedes Item in der kitchen-Partition, metert die Capacity für
alle und droppt dann die Out-of-Stock-ones.
AWS sagt, eine Filter Expression werde „nachdem eine Query fertig ist, aber
bevor die Results zurückgegeben werden“ angewandt, und „eine Query verbraucht
dieselbe Menge Read Capacity, unabhängig davon, ob eine Filter Expression
vorhanden ist“ — du hast den Full Read schon bezahlt
(AWS — Filter expressions for Query).
Wenn kitchen also 10.000 Products hat und 12 in Stock sind, zahlst du, 10.000
zu lesen. Die Response ist klein; die Rechnung nicht. FilterExpression schrumpft
das Payload über die Leitung, nie den Read.
Es gibt eine zweite, schärfere Kante: Pagination wird vor dem Filtering gemetered. Eine Page ist 1 MB gelesener Items, nicht 1 MB Matches.
Ein Filter kann eine leere Page mit gesetztem LastEvaluatedKey zurückgeben —
DynamoDB hat ein volles Megabyte gelesen, nichts gematcht, dir ein leeres Array
gereicht. Du page weiter und hast für jede leere Page bezahlt.
Baue die Expression — Names, Values und das richtige Reserved-Word-Escaping — mit
dem DynamoDB Expression Builder, sodass die
#inStock/:val-Placeholder beim ersten Versuch korrekt sind.
Der Builder unten ist auf einen Scan mit FilterExpression voreingestellt — das
genaue Anti-Pattern oben. Beachtee: der Filter läuft über die ganze Tabelle, nicht
über einen Key-Slice:
Die vier vergleichen
| Wann sie filtert | Senkt Read-Kosten? | Prädikat-Power | Setup-Kosten | |
|---|---|---|---|---|
| Partition Key | Vor dem Read | Ja — eine Partition | Nur Equality | Gratis (ist der Key) |
| Sort Key | Vor dem Read | Ja — ein Slice | Range / begins_with | Sort-Key-Design |
| Sparse Index | Vor dem Read | Ja — Index-only | Präsenz eines Attributs | Extra GSI + Write-Kosten |
| FilterExpression | Nach dem Read | Nein | Fast jede Condition | Keine |
Lies die Tabelle von oben nach unten: Prädikat-Power geht hoch, Cost Control geht
runter. FilterExpression kann alles präzise ausdrücken, weil sie auf schon
gelesenen Items läuft — genau deshalb kann sie dir kein Geld sparen.
In DynoTable ansehen
Wenn du eine Query mit Filter fährst, ist die Lücke zwischen gelesenen und
zurückgegebenen Items die ganze Story. DynoTable zeigt Items scanned neben Items
returned, während ein gefilterter Read streamt — sodass ein Filter, der still die
ganze Partition liest, sichtbar ist, nicht in deiner Monatsrechnung versteckt.
Für echte Cross-Item-Fragen, die ein Filter nicht beantworten kann — „durchschnittlicher
Preis pro Department“, „Products in Stock joined mit ihren Reviews“ — führt
DynoTables SQL Workbench GROUP BY, JOIN und Aggregate clientseitig über ein
begrenztes Resultset aus, statt zu einem Table-weiten Scan zu kompilieren.
Fallstricke und nächste Schritte
- Nutze
FilterExpressionnicht als primären Access Path. Wenn ein Pattern gängig ist, modelliere es in einen Key oder einen Sparse Index. Ein Filter ist für das letzte bisschen Verengung, nicht für den Bulk davon. - Achte auf leere Pages. Eine gefilterte Query kann lange pagen und nichts
zurückgeben. Honor
LastEvaluatedKey; nimm nicht an, dass eine leere Page „fertig“ heißt. - Ein Sparse Index ist nicht gratis. Er kostet Write Capacity und Storage für jedes Item, das darin landet — günstig, wenn das Attribut selten ist, weniger wenn nicht.
Schätze, was ein gefilterter Read wirklich kostet, mit dem Pricing Calculator, und probier DynoTable, um Consumed Capacity gegen returned Rows auf deinen eigenen Tabellen zu beobachten.