Fortgeschritten7 Min. Lesezeit

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.
  • FilterExpression ist 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).

JaNeinBase Table alle ProductsHat clearanceAt?Zum ClearanceIndex repliziertNicht im IndexIndex querien = nurClearance-Items

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:

Erstelle deine Anfrage
Generierter Code
new ScanCommand({
  "TableName": "AuditLog",
  "FilterExpression": "#filter0 = :filterValue0",
  "ExpressionAttributeNames": {
    "#filter0": "action"
  },
  "ExpressionAttributeValues": {
    ":filterValue0": {
      "S": "delete"
    }
  }
})

Die vier vergleichen

Wann sie filtertSenkt Read-Kosten?Prädikat-PowerSetup-Kosten
Partition KeyVor dem ReadJa — eine PartitionNur EqualityGratis (ist der Key)
Sort KeyVor dem ReadJa — ein SliceRange / begins_withSort-Key-Design
Sparse IndexVor dem ReadJa — Index-onlyPräsenz eines AttributsExtra GSI + Write-Kosten
FilterExpressionNach dem ReadNeinFast jede ConditionKeine

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 FilterExpression nicht 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.

Aktualisiert