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-dynamodbunmarshall を通してください。さもないと {N: '9'} と比較することになります。
  • 失敗した書き込みにも課金されます。デベロッパーガイドは、条件が false と評価されてもテーブルの書き込みキャパシティを消費すると明言しており、その大きさは古いアイテムと新しいアイテムの大きいほうで決まります。ホットキーに対するリトライループは請求書に本当に現れるので、試行回数に上限を設けてください。
  • コード中のすべての名前が別名化されています#versionVersion#cond0Artist)。生成元の 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 を削除して再実行してください。namemessage も 400 も同じで、err.Itemundefined になります。何の警告もありません。このパラメーターは省略可能で、無いことはエラーではなく、err.Item を読むコードは本番でただ undefined をログに出し始めます。

ここでの 400 がリクエストの不正を意味しないことにも注意してください。ValidationExceptionConditionalCheckFailedException はステータスコードを共有していて、バグなのは片方だけです。分岐がステータスではなく err.name に対して行われているのはそのためです。

自分のデータに対して条件が成功したり失敗したりする様子を、式を手で打たずに書いてもらって確かめるには、DynoTable をダウンロードしてください。

関連する例

参考資料

最終検証日 2026-07-28、上記にリンクした公式 AWS ドキュメントに照らして確認しました。

Console なしで DynamoDB を扱う

DynamoDB では実行できない本物の SQL(JOINs、GROUP BY、集計)を実行する高速な DynamoDB デスクトップクライアント。ビジュアル編集と、あなた自身の Bedrock キーで動く AI エージェントを備えています。

30日間無料トライアル、クレジットカード不要 — その後は期限のない Free プラン。