DynamoDB Conditional Write in Node.js (AWS SDK v3)
Der interessante Teil eines bedingten Writes im AWS SDK v3 ist nicht die ConditionExpression — die funktioniert überall gleich und ist in DynamoDB Condition Expressions behandelt. Es ist der Fehlerpfad: v3 reicht dir das unterlegene Item am geworfenen Fehler mit, wenn du danach gefragt hast, und gibt dir nichts, wenn nicht.
Code
import {DynamoDBClient, UpdateItemCommand} from '@aws-sdk/client-dynamodb';
const client = new DynamoDBClient({region: 'us-east-1'});
// Update the item only if nobody changed it since we read version 7.
const command = new UpdateItemCommand({
TableName: 'Music',
Key: {
Artist: {S: 'Arturo Sandoval'},
SongTitle: {S: 'Cubano Chant'}
},
UpdateExpression: 'SET #upd0 = :updValue0, #version = :newVersion',
ConditionExpression: 'attribute_exists(#cond0) AND #version = :expectedVersion',
ExpressionAttributeNames: {
'#upd0': 'Genre',
'#version': 'Version',
'#cond0': 'Artist'
},
ExpressionAttributeValues: {
':updValue0': {S: 'Latin Jazz'},
':expectedVersion': {N: '7'},
':newVersion': {N: '8'}
},
ReturnValuesOnConditionCheckFailure: 'ALL_OLD'
});
try {
await client.send(command);
console.log('Updated to version 8');
} catch (err) {
if (err.name === 'ConditionalCheckFailedException') {
// With ReturnValuesOnConditionCheckFailure: 'ALL_OLD', the current item
// rides back on the exception — no extra read to see what beat you.
console.log('Lost the race — item is now:', err.Item);
} else {
throw err;
}
}Erklärung
- Die fehlgeschlagene Prüfung ist ein geworfener Fehler, kein Statusfeld. v3 rejectet das Promise, der Write-Pfad und der Pfad des verlorenen Rennens sind also verschiedene Zweige.
err.name === 'ConditionalCheckFailedException'ist die Unterscheidung; alles andere muss erneut geworfen werden — dafür ist daselseim Fence da. Schluckst du das ganzecatch, hast du eine Drosselung stillschweigend in einen No-Op verwandelt. ReturnValuesOnConditionCheckFailureist der einzige Weg zu sehen, wer dir zuvorgekommen ist. Ohne das trägt der Fehler nur die Meldung und sonst nichts, und du bist zurück bei einemGetItem, das du nicht gebraucht hättest. Die API-Referenz legt die gültigen Werte aufALL_OLD | NONEfest und bestätigt, dass es keine Lesekapazität verbraucht.err.Itemist eine roheAttributeValue-Map, in derselben Form wie derKey, den du geschickt hast, und kein einfaches JavaScript. Schick es durchunmarshallaus@aws-sdk/util-dynamodb, bevor duVersionmit einer Zahl vergleichst, sonst vergleichst du gegen{N: '9'}.- Der fehlgeschlagene Write wird trotzdem abgerechnet. Der Developer Guide sagt ausdrücklich, dass eine zu
falseausgewertete Bedingung weiterhin Schreibkapazität verbraucht, bemessen am größeren von altem und neuem Item. Eine Retry-Schleife auf einem heißen Key ist eine echte Position auf der Rechnung — deckle also die Versuche. - Jeder Name im Fence ist aliasiert (
#version→Version,#cond0→Artist), weil der Expression Builder, der ihn generiert hat, bedingungslos aliasiert. Das ist hier schwerer als nötig und nie falsch — das ist der Kompromiss, den er eingeht.
Die Kopie des Verlierers aus der Exception lesen
Setze das gespeicherte Version auf 9 und führe den Fence aus, der 7 erwartet. DynamoDB Local 3.3.0 wirft, und der gefangene Fehler trägt:
err.name ConditionalCheckFailedException
err.message The conditional request failed
err.$metadata.httpStatusCode 400
err.Item {
Artist: { S: 'Arturo Sandoval' },
Year: { N: '1994' },
Version: { N: '9' },
SongTitle: { S: 'Cubano Chant' },
AlbumTitle: { S: 'Danzon' }
}Dieses Version: 9 ist der ganze Punkt. Der Retry kann direkt wieder durch das Update gehen, mit :expectedVersion auf 9 gesetzt — ohne zusätzlichen Read und ohne Fenster, in dem sich ein dritter Schreiber zwischen dein GetItem und deinen Retry schiebt.
Lösche ReturnValuesOnConditionCheckFailure aus demselben Command und führe es erneut aus. Gleicher name, gleiche message, gleiche 400 — und err.Item ist undefined. Nichts warnt dich: Der Parameter ist optional, sein Fehlen ist kein Fehler, und der Code, der err.Item liest, fängt einfach an, in der Produktion undefined zu loggen.
Beachte außerdem: Eine 400 bedeutet hier nicht, dass die Anfrage fehlerhaft war. ValidationException und ConditionalCheckFailedException teilen sich den Statuscode, und nur eine der beiden ist ein Bug — deshalb verzweigt man über err.name und nie über den Status.
Um eine Bedingung gegen deine eigenen Daten gelingen und scheitern zu sehen, mit einer Expression, die für dich geschrieben statt getippt wird, lade DynoTable herunter.
Verwandte Beispiele
- DynamoDB Conditional Write in Python — dasselbe optimistische Locking mit boto3.
- DynamoDB Conditional Write mit der AWS CLI — dasselbe optimistische Locking aus der Shell.
- DynamoDB PutItem in Node.js — der Nur-Anlegen-Put mit
attribute_not_exists. - DynamoDB Condition Expressions — jede Funktion, mit Mustern.
- Eindeutigkeit über mehrere Attribute erzwingen — Bedingungen und Transaktionen kombiniert.
- DynamoDB ConditionalCheckFailedException — wenn die fehlgeschlagene Prüfung erwartet ist und wie du sie günstig behandelst.
Referenzen
- UpdateItem — Amazon DynamoDB API Reference
- Condition expressions — Amazon DynamoDB Developer Guide
- DynamoDB read and write operations (capacity unit consumption) — Amazon DynamoDB Developer Guide
Zuletzt verifiziert am 2026-07-28 gegen die oben verlinkte offizielle AWS-Dokumentation.