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

BatchWriteItem は 1 リクエストで 最大 25 件のアイテム を put または delete します。これは小さな UpdateItem ではありません。どの PutRequest も保存されているアイテムを丸ごと置き換えますし、v3 の型には条件を付けられる場所がありません。上限と部分的失敗のモデルは DynamoDB のバッチ操作で扱っています。このページは v3 の呼び出しと、それが静かにデータを失う唯一の経路についてです。

コード

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

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

const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));

let requestItems = {
  Music: [
    {
      PutRequest: {
        Item: {
          Artist: {S: 'Arturo Sandoval'},
          SongTitle: {S: 'Cubano Chant'},
          AlbumTitle: {S: 'Danzon'},
          Year: {N: '1994'}
        }
      }
    },
    {
      PutRequest: {
        Item: {
          Artist: {S: 'Arturo Sandoval'},
          SongTitle: {S: 'A Mis Abuelos'},
          AlbumTitle: {S: 'Danzon'},
          Year: {N: '1994'}
        }
      }
    },
    {
      DeleteRequest: {
        Key: {Artist: {S: 'Ella Fitzgerald'}, SongTitle: {S: 'Misty'}}
      }
    }
  ]
};

let attempt = 0;

do {
  const response = await client.send(new BatchWriteItemCommand({RequestItems: requestItems}));

  // Writes that were throttled come back in UnprocessedItems — resubmit them
  // with exponential backoff until the map is empty.
  requestItems = response.UnprocessedItems;
  if (requestItems && Object.keys(requestItems).length > 0) {
    attempt += 1;
    await sleep(Math.min(100 * 2 ** attempt, 5000));
  }
} while (requestItems && Object.keys(requestItems).length > 0);

console.log('Batch written');

解説

  • 残りが入るメンバーは UnprocessedKeys ではなく UnprocessedItems です。読み取り側はもう一方の名前を使います。そして JavaScript ではここでのタイプミスがコンパイルを通り、undefined として読まれ、do/while を 1 回で終わる呼び出しに変えて、スロットリングされた書き込みを取りこぼします。TypeScript は捕まえますが、素の JS は捕まえません。
  • 条件を置く場所がありません。v3 の WriteRequest 型が持つ省略可能なメンバーはちょうど 2 つ、PutRequestDeleteRequest だけで、どちらも ConditionExpressionReturnValues を受け付けません。これは SDK が慎重なのではありません。API リファレンスが、個々の put および delete リクエストに条件は指定できないと述べています。ガードが必要な書き込みはバッチの仕事ではなく、条件付きの UpdateItem かトランザクションの仕事です。
  • catch できる間違いは 2 つ、どちらもリトライ不可 で、err.name で識別します。26 件のエントリは ValidationException / Too many items requested for the BatchWriteItem call を投げます。1 つのキーに 2 回触れると Provided list of item keys contains duplicates が出ますが、このメッセージは 2 つの put だけでなく put と delete の組み合わせも指すため、初めて見ると妙に読めます。
  • バッチ全体が拒否される条件は、目につく 3 つより多くあります。25 件超のリクエスト、400 KB 超のアイテム、合計 16 MB 超に加えて、存在しないテーブル、スキーマに合わないキー、2048 バイトを超えるパーティションキー、1024 バイトを超えるソートキーでも DynamoDB はバッチを拒否します。1 件の不正なエントリで 25 件すべてを失います。
  • バッチが節約するのはラウンドトリップであってキャパシティではありません。各エントリは個別の PutItem または DeleteItem として 1 KB 単位に切り上げて課金され、存在しないアイテムへの delete でも書き込みユニットを消費します。

キーだけの PutRequest はアイテムの残りを破壊します

Ella Fitzgerald / Misty は最初 AlbumTitleYear を持っています。ここで 2 つのキー属性だけを載せた PutRequest を 1 つ送ります。

{PutRequest: {Item: {Artist: {S: 'Ella Fitzgerald'}, SongTitle: {S: 'Misty'}}}}

その後 ConsistentRead: true で読み戻します。DynamoDB Local 3.3.0 はこう返します。

{
  "Artist": { "S": "Ella Fitzgerald" },
  "SongTitle": { "S": "Misty" }
}

AlbumTitleYear は消えました。呼び出しは成功し、UnprocessedItems{} で、レスポンスのどこにも落とした 2 つの属性への言及はありません。put はアイテム全体の置き換えなので、部分的なペイロード(API リクエストのボディ、CSV の一部の列、属性を省いた射影付き Query の結果)から組み立てたバッチは、そのペイロードが運ばなかった属性をすべて消し去ります。

更新のつもりでバッチを使うときに備えるべき失敗モードがこれです。対策は、現在のアイテムを先に読んでマージするか、バッチをやめて、名前を挙げた属性だけに触れる UpdateItem を使うことです。

25 件のバッチが 12 件のバッチになるもう 1 つの理由はサイズです。書き込みは課金上それぞれ 1 KB 単位に切り上げられ、リクエストは 16 MB で頭打ちになるので、アイテムの実際のバイト数が請求額と収まる件数の両方を決めます。アイテムサイズ計算ツールは、配列を組み立てる前にアイテムごとのその数字を教えてくれます。

置き換えのセマンティクスを手書きせずにアイテムをまとめて読み込み、編集し、削除するには、DynoTable をダウンロードしてください。

関連する例

参考資料

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

Console なしで DynamoDB を扱う

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

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