Node.js での DynamoDB UpdateItem(AWS SDK v3)

低レベルの v3 クライアントは双方向で DynamoDB JSON を話します。つまり送るすべての数値も、返ってくるすべての数値も 文字列 です。これは瑕疵ではありません。唯一の数値型が double である言語で、38 桁の DynamoDB の数値が生き残る唯一の方法です。そして、そこがバグの住処でもあります。

コード

import {DynamoDBClient, UpdateItemCommand} from '@aws-sdk/client-dynamodb';

const client = new DynamoDBClient({region: 'us-east-1'});

const command = new UpdateItemCommand({
  TableName: 'Music',
  Key: {
    Artist: {S: 'Arturo Sandoval'},
    SongTitle: {S: 'Cubano Chant'}
  },
  UpdateExpression: 'SET #upd0 = :updValue0, #upd1 = :updValue1 ADD #upd2 :updValue2',
  ExpressionAttributeNames: {
    '#upd0': 'Genre',
    '#upd1': 'Year',
    '#upd2': 'Awards'
  },
  ExpressionAttributeValues: {
    ':updValue0': {S: 'Latin Jazz'},
    ':updValue1': {N: '1994'},
    ':updValue2': {N: '1'}
  },
  ReturnValues: 'ALL_NEW'
});

const response = await client.send(command);
console.log(response.Attributes); // the item after the update

GenreAwards も持っていなかったアイテムに対して、response.Attributes はこう返ってきます。

{"Artist":{"S":"Arturo Sandoval"},"Awards":{"N":"1"},"Genre":{"S":"Latin Jazz"},"Year":{"N":"1994"},"SongTitle":{"S":"Cubano Chant"}}

typeof response.Attributes.Awards.N"string" なので、response.Attributes.Awards.N + 1"11" に評価されます。例外も警告も出ず、間違った数値が次の書き込みに入っていきます。境界でパースしてください。Number(response.Attributes.Awards.N) です。

解説

  • 式はただの文字列で、v3 はそれを検査しませんUpdateItemCommand は入力オブジェクトの形は検証しますが、UpdateExpression の中の文法は決して検証しないので、タイプミスはラウンドトリップと 400 になります。文法は更新式にあります。ADD #upd2 :updValue2 がアトミックなインクリメントで、ConditionExpression: 'attribute_exists(Artist)' を加えると呼び出しはアップサートではなく更新専用になります。

  • ふつうに欲しいのは ReturnValues: 'UPDATED_NEW' のほうです。同じ更新は {"Awards":{"N":"2"}} だけを返します。ALL_NEW は毎回アイテム全体を送り返すので、太ったアイテムではカウンター 1 つを読むために帯域を払うことになります。

  • $metadata は v3 の帯域外チャネルです{"httpStatusCode":200,"requestId":"...","attempts":1,"totalRetryDelay":0} のような形です。attempts は「これはリトライしたのか」に対する正直な答えで、冪等でない書き込みが 2 回走ったかどうかを考えるときに効いてきます。

  • ValidationException は catch できるクラスではなく、比較できる name にすぎません。別名の付け忘れは err.name === 'ValidationException' として返り、err.messageInvalid UpdateExpression: Attribute name is a reserved keyword; reserved keyword: Year になります。

  • もう一方のトレードオフがドキュメントクライアントです@aws-sdk/lib-dynamodb はネイティブな JS の値を受け取り、レスポンスをアンマーシャルしますが、その文字列の安全性と引き換えです。@aws-sdk/util-dynamodbmarshall({awards: 9007199254740993}) は端的に拒否します。

    Number 9007199254740992 is greater than Number.MAX_SAFE_INTEGER. Use NumberValue from @aws-sdk/lib-dynamodb.

    このメッセージの数値をよく見てください。末尾はリテラルに書いた 3 ではなく 2 です。SDK が見る前に JavaScript がすでに丸めていたのです。このスニペットの低レベルクライアントにその問題は起こりえません。{N: '9007199254740993'} はワイヤーに乗るまでずっとテキストだからです。

失敗した条件が渡してくれるもの

入力に ReturnValuesOnConditionCheckFailure: 'ALL_OLD' を加えると、投げられたエラーがあなたに勝ったアイテムを運んできます。

name: ConditionalCheckFailedException | message: "The conditional request failed" | http: 400
err.Item: {"Artist":{"S":"Arturo Sandoval"},"Awards":{"N":"2"},"Year":{"N":"1994"},"SongTitle":{"S":"Cubano Chant"},"Genre":{"S":"Latin Jazz"}}

err.Item はどのクライアントが投げたかによらず生の DynamoDB JSON で、しかも無料です。これがなければ、楽観的並行制御の更新が失敗した理由を知る正直な方法は、読み取りコストがかかり、しかも取得時点ですでに古くなっているかもしれない追加の GetItem です。

DynamoDB JSON コンバーターは、そのペイロードを素の JS オブジェクトに、またその逆に変換します。実際のアイテムからフィクスチャを作る最速の方法です。そもそもそのアイテムをライブのテーブルから引き出すには、DynoTable をダウンロードしてください。

関連ガイド

参考資料

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

Console なしで DynamoDB を扱う

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

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