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 updateGenre も Awards も持っていなかったアイテムに対して、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.messageはInvalid UpdateExpression: Attribute name is a reserved keyword; reserved keyword: Yearになります。もう一方のトレードオフがドキュメントクライアントです。
@aws-sdk/lib-dynamodbはネイティブな JS の値を受け取り、レスポンスをアンマーシャルしますが、その文字列の安全性と引き換えです。@aws-sdk/util-dynamodbのmarshall({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 をダウンロードしてください。
関連ガイド
- DynamoDB の更新式 —
SET、ADD、REMOVE、DELETEとイディオム。 - ReturnValues を理解する — 各
ReturnValuesオプションが返すもの。 - 「Attribute name is a reserved keyword」 — ここの別名マップが任意ではない理由。
- 「Invalid UpdateExpression」の構文エラー — よくある SET/ADD の構文ミスを解説。
参考資料
- UpdateItem — Amazon DynamoDB API Reference
- UpdateItemCommand — AWS SDK for JavaScript v3 Reference
- Update expressions — Amazon DynamoDB Developer Guide
最終検証日 2026-07-28、上記にリンクした公式 AWS ドキュメントに照らして確認しました。