Les condition expressions DynamoDB
Une condition expression est un prédicat que DynamoDB évalue sur l'item existant
avant de valider ton écriture. Si le prédicat est faux, l'écriture est rejetée et
rien ne change. C'est ce que DynamoDB a de plus proche d'une clause WHERE sur une
écriture — et le seul moyen sûr d'imposer un invariant.
Comment fonctionnent les condition expressions DynamoDB ?
Une condition expression est un prédicat que DynamoDB évalue côté serveur sur l'item courant avant de valider une écriture. S'il est vrai, l'écriture est effectuée ; s'il est faux, l'écriture est rejetée avec ConditionalCheckFailedException et rien ne change. Elle fond la vérification et la mutation en une seule opération atomique, de sorte que des appelants concurrents ne peuvent pas se courir après sur une lecture périmée.
- C'est un garde, pas un filtre.
ConditionExpressions'exécute côté serveur sur l'item courant ; un résultat faux fait échouer l'écriture avecConditionalCheckFailedException. - Elle remplace le read-then-write. Pas d'aller-retour
SELECTpuisUPDATE— la vérification et la mutation sont une seule opération atomique, donc deux appelants ne peuvent pas se courir après. - Le rejet est gratuit, l'exécution non. Une écriture conditionnelle échouée consomme quand même de la capacité d'écriture. La garantie coûte autant que l'écriture qu'elle bloque.
Venant de SQL, tu lirais la ligne, la vérifierais dans le code applicatif, puis mettrais à jour. Dans DynamoDB cet écart entre lecture et écriture est un bug de corruption de données qui attend un appelant concurrent. La condition expression ferme l'écart.
Où elles s'appliquent
Tu attaches une ConditionExpression à PutItem, UpdateItem, DeleteItem, et à
chaque action dans un TransactWriteItems. Elle ne fait pas partie de Query ni
de Scan — ceux-là utilisent FilterExpression, qui est une autre chose sur le
chemin de lecture.
Cette distinction fait trébucher les gens, alors sois précis :
ConditionExpression | FilterExpression | |
|---|---|---|
| Chemin | Écritures (Put/Update/Delete) | Lectures (Query/Scan) |
| Effet en cas d'échec | Rejette toute l'écriture | Retire l'item des résultats |
| Voit | L'item courant, avant écriture | Chaque item candidat, après lecture |
| Coût | L'écriture échouée facture quand même | Les items filtrés sont quand même facturés à la lecture |
Les deux s'exécutent côté serveur. La différence est ce que fait « faux » : une condition avorte une mutation ; un filtre masque juste une ligne que tu as déjà payée pour lire. (AWS : Condition Expressions)
Les fonctions que tu utiliseras vraiment
Le langage de condition est petit. Les bêtes de somme :
attribute_exists(path)/attribute_not_exists(path)— cet attribut existe-t-il sur l'item ? L'idiome classique pour « créer seulement si absent » / « mettre à jour seulement si présent ».- Comparateurs —
=,<>,<,<=,>,>=— contre une valeur ou un autre attribut. attribute_type,begins_with,contains,size— vérifications de type et de chaîne/ensemble.BETWEEN … AND …,IN (…)— plage et appartenance.AND,OR,NOT, parenthèses — pour combiner ce qui précède.
attribute_not_exists sur la clé de partition est le moyen canonique de faire en
sorte que PutItem se comporte comme un insert qui n'écrasera pas un item existant —
DynamoDB n'a pas d'opération « insert » séparée, donc la condition est la sémantique
d'insert.
(AWS : Comparison Operator and Function Reference)
Un exemple travaillé : protéger un grand livre contre le découvert
Prends un grand livre bancaire. Chaque compte est un item :
PK = "ACCT#a7f3"
SK = "BALANCE"
clearedCents = 50000
holdCents = 0L'invariant : un débit ne doit jamais pousser le solde disponible sous zéro, et tu ne dois jamais débiter un compte qui n'existe pas. Deux règles, toutes deux imposables dans l'écriture elle-même.
La mauvaise façon (l'arme à fragmentation)
GetItem ACCT#a7f3 / BALANCE → clearedCents = 50000
if (50000 >= 30000) ... ← vérification côté app
UpdateItem SET clearedCents = 20000
Entre le GetItem et le UpdateItem, un second débit peut lire le même 50000,
passer sa propre vérification, et écrire aussi. Les deux réussissent ; le compte passe
négatif. C'est une course read-modify-write, et aucune validation côté app ne la
corrige — la vérification et l'écriture sont des opérations séparées.
La bonne façon
Replie la vérification dans l'écriture. Débite 30000 cents, conditionnel à l'existence du compte et au fait qu'il en détienne assez :
UpdateItem ACCT#a7f3 / BALANCE
SET clearedCents = clearedCents - :amt
ConditionExpression:
attribute_exists(PK) AND clearedCents >= :amtavec :amt = 30000. Si le solde est trop bas, ou si l'item n'a jamais été créé,
DynamoDB rejette l'écriture avec ConditionalCheckFailedException et le solde est
intact. Le débit concurrent soit voit le solde original et est vérifié contre lui,
soit voit le mis à jour — jamais une lecture périmée sur laquelle il a agi.
Tu peux construire et copier l'expression exacte — noms, valeurs, et tout — avec le
DynamoDB expression builder au lieu d'assembler
à la main la map ExpressionAttributeValues.
Inspecter le garde dans DynoTable
Quand une écriture conditionnelle échoue, tu veux voir l'état réel de l'item, pas le
deviner. Ouvre l'item du compte et lis clearedCents directement.

Lis le rejet, ne réessaie pas aveuglément
ConditionalCheckFailedException n'est pas une erreur transitoire — réessayer la même
écriture ne change rien. Ça signifie qu'une règle métier s'est déclenchée : fonds
insuffisants, création en double, version périmée. Présente-le comme un résultat
métier, pas comme un hoquet d'infra.
Deux choses rendent les échecs débogables :
ReturnValuesOnConditionCheckFailure: ALL_OLD— DynamoDB renvoie l'item courant à côté de l'échec, pour que tu puisses montrer « le solde était 20000, tu as demandé 30000 » sans une seconde lecture. (AWS : Working with Items)- Distinguer les deux raisons d'échec.
attribute_exists(PK) AND clearedCents >= :amtfond « pas de compte » et « pas de fonds » en une seule exception. Si les appelants doivent les distinguer, sépare en deux écritures ou inspecte l'item renvoyé.
Le verrouillage optimiste est la même astuce
Le motif du numéro de version n'est qu'une condition expression portant un autre
chapeau. Stocke un attribut version ; chaque écriture affirme la version que tu as
lue et l'incrémente :
UpdateItem ACCT#a7f3 / BALANCE
SET clearedCents = :new, version = :next
ConditionExpression: version = :seenSi un autre écrivain a bougé en premier, version = :seen est faux, l'écriture est
rejetée, et tu relis et réessaies. C'est ainsi que DynamoDB fait du contrôle de
concurrence sans verrous — affirme ce que tu as vu, échoue si ça a bougé. (AWS :
Optimistic Locking with Version Number)
Pièges et étapes suivantes
- Des noms qui entrent en collision avec des mots réservés.
status,size,name, et ~570 autres sont réservés. Aliase-les avecExpressionAttributeNames(#s = status) ou ton expression échoue silencieusement à se parser. - Une condition ne peut pas référencer un autre item. Elle ne voit que l'item en
cours d'écriture. Les invariants inter-items nécessitent un
TransactWriteItemsavec uneConditionExpressionpar action, ou unConditionCheckcontre un item sentinelle. - Les écritures échouées coûtent quand même des WCU. Un garde qui rejette 90 % du temps facture quand même ces rejets. Assurance bon marché, mais pas gratuite.
Pour modéliser les clés contre lesquelles ces gardes s'exécutent, vois single-table design et Query vs Scan. Quand tu es prêt à émettre des écritures conditionnelles contre de vraies données, télécharge DynoTable et lance-les contre tes propres tables.


