Node.js での DynamoDB 条件付き書き込み(AWS SDK v3)
AWS SDK v3 における条件付き書き込みの面白いところは ConditionExpression ではありません。それはどこでも同じように動き、DynamoDB の条件式で扱っています。面白いのは失敗経路です。v3 は、頼んでおけば負けたアイテムを投げられたエラーに載せて渡してくれますし、頼まなければ何もくれません。
コード
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;
}
}解説
- 失敗したチェックはステータスフィールドではなく、投げられるエラーです。v3 は Promise を reject するので、書き込み成功の経路と競合に負けた経路は別々の分岐になります。
err.name === 'ConditionalCheckFailedException'が判別条件で、それ以外は再スローしなければなりません。コード中のelseはそのためにあります。catchを丸ごと握りつぶすと、スロットリングを静かに何もしない処理に変えてしまいます。 - 誰に負けたのかを知る手段は
ReturnValuesOnConditionCheckFailureだけです。これがなければエラーはメッセージ以外に何も運ばず、必要のなかったGetItemに逆戻りです。API リファレンスは有効値をALL_OLD | NONEと定め、読み取りキャパシティを消費しないことも明記しています。 err.Itemは生のAttributeValueマップ で、送ったKeyと同じ形をしています。素の JavaScript ではありません。Versionを数値と比較する前に@aws-sdk/util-dynamodbのunmarshallを通してください。さもないと{N: '9'}と比較することになります。- 失敗した書き込みにも課金されます。デベロッパーガイドは、条件が false と評価されてもテーブルの書き込みキャパシティを消費すると明言しており、その大きさは古いアイテムと新しいアイテムの大きいほうで決まります。ホットキーに対するリトライループは請求書に本当に現れるので、試行回数に上限を設けてください。
- コード中のすべての名前が別名化されています(
#version→Version、#cond0→Artist)。生成元の Expression Builder が無条件に別名を付けるからです。ここでは必要以上に重装備ですが、決して間違いにはなりません。それがこのツールの選んだトレードオフです。
例外から敗者のコピーを読む
保存されている Version を 9 にしてから、7 を期待する上のコードを実行します。DynamoDB Local 3.3.0 は例外を投げ、捕まえたエラーはこれを運んできます。
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' }
}この Version: 9 こそが要点です。リトライは :expectedVersion を 9 にしてそのまま更新に戻れます。追加の読み取りは不要で、GetItem とリトライの間に第三の書き手が割り込む隙間もありません。
同じコマンドから ReturnValuesOnConditionCheckFailure を削除して再実行してください。name も message も 400 も同じで、err.Item は undefined になります。何の警告もありません。このパラメーターは省略可能で、無いことはエラーではなく、err.Item を読むコードは本番でただ undefined をログに出し始めます。
ここでの 400 がリクエストの不正を意味しないことにも注意してください。ValidationException と ConditionalCheckFailedException はステータスコードを共有していて、バグなのは片方だけです。分岐がステータスではなく err.name に対して行われているのはそのためです。
自分のデータに対して条件が成功したり失敗したりする様子を、式を手で打たずに書いてもらって確かめるには、DynoTable をダウンロードしてください。
関連する例
- Python での DynamoDB 条件付き書き込み — boto3 による同じ楽観的ロック。
- AWS CLI での DynamoDB 条件付き書き込み — シェルからの同じ楽観的ロック。
- Node.js での DynamoDB PutItem — 作成専用の
attribute_not_exists付き put。 - DynamoDB の条件式 — すべての関数とパターン。
- 複数属性への一意性の強制 — 条件とトランザクションの組み合わせ。
- DynamoDB ConditionalCheckFailedException — 失敗したチェックが想定内のとき、それを安く扱う方法。
参考資料
- UpdateItem — Amazon DynamoDB API Reference
- Condition expressions — Amazon DynamoDB Developer Guide
- DynamoDB read and write operations (capacity unit consumption) — Amazon DynamoDB Developer Guide
最終検証日 2026-07-28、上記にリンクした公式 AWS ドキュメントに照らして確認しました。