DynamoDB Condition Expressions
Eine Condition Expression ist ein Prädikat, das DynamoDB vor dem Commit deines
Writes auf dem bestehenden Item auswertet. Ist das Prädikat falsch, wird der Write
abgelehnt und nichts ändert sich. Es ist das Nächste, was DynamoDB an eine
WHERE-Klausel auf einem Write hat — und der einzige sichere Weg, eine Invariante
zu erzwingen.
Wie funktionieren DynamoDB Condition Expressions?
Eine Condition Expression ist ein Prädikat, das DynamoDB serverseitig gegen das aktuelle Item auswertet, bevor ein Write committet wird. Ist es wahr, läuft der Write durch; ist es falsch, wird der Write mit ConditionalCheckFailedException abgelehnt und nichts ändert sich. Sie faltet die Prüfung und die Mutation zu einer einzigen atomaren Operation, sodass gleichzeitige Aufrufer nicht gegen einen veralteten Read racen können.
- Es ist ein Guard, kein Filter.
ConditionExpressionläuft serverseitig auf dem aktuellen Item; ein falsches Ergebnis lässt den Write mitConditionalCheckFailedExceptionscheitern. - Es ersetzt Read-then-Write. Kein
SELECT-dann-UPDATE-Round-Trip — Prüfung und Mutation sind eine atomare Operation, sodass zwei Aufrufer nicht racen können. - Es ist gratis abzulehnen, nicht gratis auszuführen. Ein gescheiterter Conditional Write verbraucht trotzdem Write Capacity. Die Garantie kostet dasselbe wie der Write, den sie blockiert.
Aus SQL kommend würdest du die Zeile lesen, sie im App-Code prüfen, dann aktualisieren. In DynamoDB ist diese Lücke zwischen Read und Write ein Datenkorruptions-Bug, der auf einen gleichzeitigen Aufrufer wartet. Die Condition Expression schließt die Lücke.
Wo sie gelten
Du hängst eine ConditionExpression an PutItem, UpdateItem, DeleteItem und
jede Action innerhalb von TransactWriteItems. Sie ist kein Teil von Query
oder Scan — die verwenden FilterExpression, was auf dem Read-Pfad etwas
anderes ist.
Diese Unterscheidung bringt Leute durcheinander, also sei präzise:
ConditionExpression | FilterExpression | |
|---|---|---|
| Pfad | Writes (Put/Update/Delete) | Reads (Query/Scan) |
| Effekt bei Fehlschlag | Lehnt den ganzen Write ab | Lässt das Item aus den Ergebnissen fallen |
| Sieht | Das aktuelle Item, pre-Write | Jedes Kandidaten-Item, post-Read |
| Kosten | Gescheiterter Write rechnet trotzdem ab | Gefilterte Items werden für den Read trotzdem abgerechnet |
Beide laufen serverseitig. Der Unterschied ist, was "falsch" bewirkt: eine Bedingung bricht eine Mutation ab; ein Filter versteckt nur eine Zeile, für deren Read du schon bezahlt hast. (AWS: Condition Expressions)
Die Funktionen, die du tatsächlich nutzt
Die Bedingungssprache ist klein. Die Arbeitspferde:
attribute_exists(path)/attribute_not_exists(path)— existiert dieses Attribut auf dem Item? Das klassische Idiom für "nur anlegen, wenn abwesend" / "nur aktualisieren, wenn vorhanden".- Vergleichsoperatoren —
=,<>,<,<=,>,>=— gegen einen Wert oder ein anderes Attribut. attribute_type,begins_with,contains,size— Typ- und String/Set-Checks.BETWEEN … AND …,IN (…)— Range und Mitgliedschaft.AND,OR,NOT, Klammern — um das Obige zu kombinieren.
attribute_not_exists auf dem Partition Key ist der kanonische Weg, PutItem sich
wie ein Insert verhalten zu lassen, der ein bestehendes Item nicht überschreibt —
DynamoDB hat keine separate "Insert"-Operation, also ist die Bedingung die
Insert-Semantik.
(AWS: Comparison Operator and Function Reference)
Ein durchgespieltes Beispiel: ein Ledger gegen Überziehung absichern
Nimm ein Bank-Ledger. Jedes Konto ist ein Item:
PK = "ACCT#a7f3"
SK = "BALANCE"
clearedCents = 50000
holdCents = 0Die Invariante: eine Abbuchung darf den verfügbaren Saldo nie unter null drücken, und du darfst nie ein Konto belasten, das nicht existiert. Zwei Regeln, beide im Write selbst erzwingbar.
Der falsche Weg (die Falle)
GetItem ACCT#a7f3 / BALANCE → clearedCents = 50000
if (50000 >= 30000) ... ← app-seitige Prüfung
UpdateItem SET clearedCents = 20000
Zwischen dem GetItem und dem UpdateItem kann eine zweite Abbuchung dieselben
50000 lesen, ihre eigene Prüfung bestehen und ebenfalls schreiben. Beide
gelingen; das Konto geht ins Minus. Das ist ein Read-modify-write-Race, und kein
Maß an app-seitiger Validierung behebt es — Prüfung und Write sind getrennte
Operationen.
Der richtige Weg
Falte die Prüfung in den Write. Buche 30000 Cent ab, bedingt darauf, dass das Konto existiert und genug hält:
UpdateItem ACCT#a7f3 / BALANCE
SET clearedCents = clearedCents - :amt
ConditionExpression:
attribute_exists(PK) AND clearedCents >= :amtmit :amt = 30000. Wenn der Saldo zu niedrig ist oder das Item nie angelegt
wurde, lehnt DynamoDB den Write mit ConditionalCheckFailedException ab und der
Saldo bleibt unangetastet. Die gleichzeitige Abbuchung sieht entweder den
ursprünglichen Saldo und wird dagegen geprüft, oder sie sieht den aktualisierten —
nie einen veralteten Read, auf dem sie handelte.
Du kannst die exakte Expression — Namen, Werte und alles — mit dem
DynamoDB Expression Builder bauen und
kopieren, statt die ExpressionAttributeValues-Map von Hand
zusammenzubauen.
Den Guard in DynoTable inspizieren
Wenn ein Conditional Write scheitert, willst du den echten Zustand des Items
sehen, nicht raten. Hol das Konto-Item hoch und lies clearedCents direkt.

Lies die Ablehnung, retry nicht blind
ConditionalCheckFailedException ist kein transienter Fehler — denselben Write zu
wiederholen ändert nichts. Es bedeutet, dass eine Geschäftsregel feuerte:
unzureichende Mittel, doppeltes Anlegen, veraltete Version. Stelle es als
Domänen-Ergebnis dar, nicht als Infra-Aussetzer.
Zwei Dinge machen Fehlschläge debuggbar:
ReturnValuesOnConditionCheckFailure: ALL_OLD— DynamoDB gibt das aktuelle Item neben dem Fehlschlag zurück, sodass du "Saldo war 20000, du hast 30000 verlangt" ohne einen zweiten Read zeigen kannst. (AWS: Working with Items)- Die beiden Fehlschlagsgründe unterscheiden.
attribute_exists(PK) AND clearedCents >= :amtkollabiert "kein Konto" und "keine Mittel" in eine Exception. Wenn Aufrufer sie auseinanderhalten müssen, splitte in zwei Writes oder inspiziere das zurückgegebene Item.
Optimistic Locking ist derselbe Trick
Das Versionsnummern-Muster ist nur eine Condition Expression mit einem anderen Hut.
Speichere ein version-Attribut; jeder Write behauptet die Version, die du gelesen
hast, und erhöht sie:
UpdateItem ACCT#a7f3 / BALANCE
SET clearedCents = :new, version = :next
ConditionExpression: version = :seenWenn ein anderer Writer zuerst zog, ist version = :seen falsch, der Write wird
abgelehnt, und du liest erneut und versuchst es nochmal. So macht DynamoDB
Nebenläufigkeitskontrolle ohne Locks — behaupte, was du sahst, scheitere, wenn es
sich bewegte. (AWS: Optimistic Locking with
Version Number)
Fallstricke und nächste Schritte
- Namen, die mit reservierten Wörtern kollidieren.
status,size,nameund ~570 weitere sind reserviert. Aliasiere sie mitExpressionAttributeNames(#s = status), oder deine Expression scheitert still beim Parsen. - Eine Bedingung kann kein anderes Item referenzieren. Sie sieht nur das Item,
das geschrieben wird. Item-übergreifende Invarianten brauchen
TransactWriteItemsmit einerConditionExpressionpro Action oder einenConditionCheckgegen ein Sentinel-Item. - Gescheiterte Writes kosten trotzdem WCUs. Ein Guard, der 90 % der Zeit ablehnt, rechnet diese Ablehnungen trotzdem ab. Billige Versicherung, aber nicht gratis.
Zum Modellieren der Keys, gegen die diese Guards laufen, siehe Single-Table Design und Query vs. Scan. Wenn du bereit bist, Conditional Writes gegen echte Daten auszugeben, lade DynoTable herunter und führe sie gegen deine eigenen Tabellen aus.


