Condition expression in DynamoDB
Una condition expression è un predicato che DynamoDB valuta sull'item esistente
prima di committare la tua scrittura. Se il predicato è falso, la scrittura è
rifiutata e nulla cambia. È la cosa più vicina a una clausola WHERE su una
scrittura che DynamoDB possiede — e l'unico modo sicuro per imporre un'invariante.
Come funzionano le condition expression in DynamoDB?
Una condition expression è un predicato che DynamoDB valuta lato server sull'item corrente prima di committare una scrittura. Se è vero, la scrittura procede; se è falso, la scrittura viene rifiutata con ConditionalCheckFailedException e nulla cambia. Fonde il controllo e la mutazione in un'unica operazione atomica, così i chiamanti concorrenti non possono fare una gara su una lettura obsoleta.
- È una guardia, non un filtro. La
ConditionExpressionviene eseguita lato server sull'item corrente; un risultato falso fa fallire la scrittura conConditionalCheckFailedException. - Sostituisce il read-then-write. Niente round trip
SELECTpoiUPDATE— il controllo e la mutazione sono un'unica operazione atomica, così due chiamanti non possono fare una gara. - È gratis rifiutare, non gratis eseguire. Una scrittura condizionale fallita consuma comunque capacità di scrittura. La garanzia costa quanto la scrittura che blocca.
Venendo da SQL, leggeresti la riga, la controlleresti nel codice dell'app, poi la aggiorneresti. In DynamoDB quel divario tra lettura e scrittura è un bug di corruzione dati in attesa di un chiamante concorrente. La condition expression chiude il divario.
Dove si applicano
Attacchi una ConditionExpression a PutItem, UpdateItem, DeleteItem e a
ogni azione dentro TransactWriteItems. Non fa parte di Query o Scan —
quelli usano FilterExpression, che è una cosa diversa sul percorso di lettura.
Quella distinzione confonde le persone, quindi sii preciso:
ConditionExpression | FilterExpression | |
|---|---|---|
| Percorso | Scritture (Put/Update/Delete) | Letture (Query/Scan) |
| Effetto al fallimento | Rifiuta l'intera scrittura | Toglie l'item dai risultati |
| Vede | L'item corrente, pre-scrittura | Ogni item candidato, post-lettura |
| Costo | La scrittura fallita fattura comunque | Gli item filtrati sono comunque fatturati nella lettura |
Entrambe girano lato server. La differenza è cosa fa "falso": una condizione aborta una mutazione; un filtro nasconde solo una riga che hai già pagato per leggere. (AWS: Condition Expressions)
Le funzioni che userai davvero
Il linguaggio delle condizioni è piccolo. I cavalli da tiro:
attribute_exists(path)/attribute_not_exists(path)— questo attributo esiste sull'item? L'idioma classico per "crea solo se assente" / "aggiorna solo se presente".- Comparatori —
=,<>,<,<=,>,>=— contro un valore o un altro attributo. attribute_type,begins_with,contains,size— controlli di tipo e di stringa/set.BETWEEN … AND …,IN (…)— intervallo e appartenenza.AND,OR,NOT, parentesi — per combinare i precedenti.
attribute_not_exists sulla partition key è il modo canonico per far comportare
PutItem come un insert che non sovrascrive un item esistente — DynamoDB non ha
un'operazione "insert" separata, quindi la condizione è la semantica
dell'insert.
(AWS: Comparison Operator and Function Reference)
Un esempio pratico: proteggere un registro contabile dallo scoperto
Prendi un registro contabile bancario. Ogni conto è un item:
PK = "ACCT#a7f3"
SK = "BALANCE"
clearedCents = 50000
holdCents = 0L'invariante: un addebito non deve mai spingere il saldo disponibile sotto zero, e non devi mai addebitare un conto che non esiste. Due regole, entrambe imponibili nella scrittura stessa.
Il modo sbagliato (il footgun)
GetItem ACCT#a7f3 / BALANCE → clearedCents = 50000
if (50000 >= 30000) ... ← controllo lato app
UpdateItem SET clearedCents = 20000
Tra il GetItem e l'UpdateItem, un secondo addebito può leggere lo stesso
50000, superare il proprio controllo e scrivere anch'esso. Entrambi vanno a buon
fine; il conto va in negativo. È una gara read-modify-write, e nessuna quantità di
validazione lato app la risolve — il controllo e la scrittura sono operazioni
separate.
Il modo giusto
Incorpora il controllo nella scrittura. Addebita 30000 centesimi, condizionato al conto esistente e che ne abbia abbastanza:
UpdateItem ACCT#a7f3 / BALANCE
SET clearedCents = clearedCents - :amt
ConditionExpression:
attribute_exists(PK) AND clearedCents >= :amtcon :amt = 30000. Se il saldo è troppo basso, o l'item non è mai stato creato,
DynamoDB rifiuta la scrittura con ConditionalCheckFailedException e il saldo
resta intatto. L'addebito concorrente o vede il saldo originale ed è controllato
contro di esso, o vede quello aggiornato — mai una lettura obsoleta su cui ha
agito.
Puoi costruire e copiare l'espressione esatta — nomi, valori e tutto — con il
DynamoDB expression builder invece di
assemblare a mano la mappa ExpressionAttributeValues.
Ispezionare la guardia in DynoTable
Quando una scrittura condizionale fallisce, vuoi vedere lo stato reale dell'item,
non indovinarlo. Apri l'item del conto e leggi clearedCents direttamente.

Leggi il rifiuto, non ritentare alla cieca
ConditionalCheckFailedException non è un errore transitorio — ritentare la
stessa scrittura non cambia nulla. Significa che una regola di business è scattata:
fondi insufficienti, creazione duplicata, versione obsoleta. Esponilo come esito di
dominio, non come un singhiozzo dell'infrastruttura.
Due cose rendono i fallimenti debuggabili:
ReturnValuesOnConditionCheckFailure: ALL_OLD— DynamoDB restituisce l'item corrente insieme al fallimento, così puoi mostrare "il saldo era 20000, ne hai chiesti 30000" senza una seconda lettura. (AWS: Working with Items)- Distinguere le due ragioni di fallimento.
attribute_exists(PK) AND clearedCents >= :amtcollassa "nessun conto" e "nessun fondo" in un'unica eccezione. Se i chiamanti devono distinguerle, dividi in due scritture o ispeziona l'item restituito.
L'optimistic locking è lo stesso trucco
Il pattern del numero di versione è solo una condition expression con un altro
cappello. Memorizza un attributo version; ogni scrittura asserisce la versione
che hai letto e la incrementa:
UpdateItem ACCT#a7f3 / BALANCE
SET clearedCents = :new, version = :next
ConditionExpression: version = :seenSe un altro writer si è mosso prima, version = :seen è falso, la scrittura è
rifiutata, e rileggi e ritenti. È così che DynamoDB fa il controllo di concorrenza
senza lock — asserisci ciò che hai visto, fallisci se si è mosso. (AWS: Optimistic
Locking with Version Number)
Trappole e prossimi passi
- Nomi che si scontrano con parole riservate.
status,size,namee ~570 altre sono riservate. Aliasale conExpressionAttributeNames(#s = status) o la tua espressione fallisce silenziosamente nel parsing. - Una condizione non può riferirsi a un altro item. Vede solo l'item che si
sta scrivendo. Le invarianti tra item richiedono
TransactWriteItemscon unaConditionExpressionper azione, o unConditionCheckcontro un item sentinella. - Le scritture fallite costano comunque WCU. Una guardia che rifiuta il 90% delle volte fattura comunque quei rifiuti. Assicurazione economica, ma non gratis.
Per modellare le chiavi contro cui girano queste guardie, vedi single-table design e Query vs Scan. Quando sei pronto a emettere scritture condizionali contro dati reali, scarica DynoTable ed eseguile contro le tue tabelle.


