Python(boto3)での DynamoDB BatchGetItem

batch_get_item は 1 リクエストで プライマリキー指定のアイテムを最大 100 件 取得します。下のフェンスにある while request_items: が boto3 のイディオムそのものです。DynamoDB は 成功した レスポンスで残りを返してよこし、空の dict は偽値なので、ループは自分で終わります。上限と部分結果のルールは DynamoDB のバッチ操作にあります。このページで扱うのは boto3 の呼び出しと、それが送出するエラーです。

コード

import time

import boto3

client = boto3.client("dynamodb")

request_items = {
    "Music": {
        "Keys": [
            {"Artist": {"S": "Arturo Sandoval"}, "SongTitle": {"S": "Cubano Chant"}},
            {"Artist": {"S": "Arturo Sandoval"}, "SongTitle": {"S": "A Mis Abuelos"}},
            {"Artist": {"S": "Ella Fitzgerald"}, "SongTitle": {"S": "Misty"}},
        ]
    }
}

items = []
attempt = 0

while request_items:
    response = client.batch_get_item(RequestItems=request_items)
    items.extend(response["Responses"].get("Music", []))

    # A partial result is NOT an error: throttling, a >16 MB response, or an
    # internal failure returns the leftovers in UnprocessedKeys. Retry them
    # with exponential backoff.
    request_items = response["UnprocessedKeys"]
    if request_items:
        attempt += 1
        time.sleep(min(0.1 * 2**attempt, 5))

print(f"Fetched {len(items)} items")

解説

  • response["UnprocessedKeys"] は常に存在します。完全に処理されたバッチでもキーは存在し、{} を保持します。だから request_items = response["UnprocessedKeys"] は安全に添字アクセスでき、偽値の空 dict が while を終わらせます。注意すべきなのは Responses のほうです。キーがすべて外れたテーブルはそこに現れないので、フェンスでは .get("Music", []) を使っています。
  • ConsistentReadProjectionExpression はテーブルごとの dict の中RequestItems の隣ではなく "Keys" と並べて置きます。boto3 は置き場所を誤ったキーも平気で送り、サービスに拒否させます。
  • これは低レベルクライアントなので、値は DynamoDB JSON です{"S": ...}{"N": ...})。リソース API では batch_get_itemTable ではなく ServiceResource にあります。boto3.resource("dynamodb").batch_get_item(...) はネイティブな Python の値を取りますが、table.batch_get_item は存在しません。この非対称は、table.batch_writer() を使ったあとに手を伸ばした人を驚かせます。あちらは 確かに Table のメソッドだからです。
  • バックオフは UnprocessedKeys にだけ適用しますValidationException はリクエスト側のバグであり、リトライは時計を空回りさせるだけです。

この呼び出しが送出する 2 つのエラー、逐語で

どちらもリトライでは直らないクライアント側の誤りで、どちらも素の botocore.exceptions.ClientError として現れます。DynamoDB Local 3.3.0 に対する str(e) はこうです。

An error occurred (ValidationException) when calling the BatchGetItem operation: Too many items requested for the BatchGetItem call
An error occurred (ValidationException) when calling the BatchGetItem operation: Provided list of item keys contains duplicates

1 つ目は 101 個のキー、2 つ目は同じキーを 2 回並べた場合です。これらを捕まえるために 書けない ものにも注目してください。

except client.exceptions.ValidationException:  # AttributeError

botocore は DynamoDB クライアント上に 34 個の名前付き例外クラスをモデル化していますが、ValidationException はその 1 つではありません。ConditionalCheckFailedExceptionProvisionedThroughputExceededException は含まれます。だから条件付き書き込みのページではクラスで捕捉できるのに、こちらではできないのです。モデル化された DuplicateItemException すら存在しますが、バッチ内の重複キーで得られるのはそれでは ありません。というわけで、バッチ読み取りではコードで分岐するしかありません。

except ClientError as e:
    if e.response["Error"]["Code"] == "ValidationException":
        raise  # a bug in the request; retrying will not help

実コードで刺さるのは重複キーのケースです。Query の結果や結合テーブルから組み立てたキーのリストは、自然に重複するからです。送る前に重複を除きましょう。2 つの dict が等しいのはキー属性がすべて一致するときだけだ、という点をお忘れなく。

100 件のバッチが 100 件のままでいられないもう 1 つの理由がサイズです。各アイテムは課金上 4 KB に切り上げられ、レスポンスの 16 MB 上限に対して数えられるので、300 KB のアイテム 100 件は、およそ 52 件が返り、残りが UnprocessedKeys に入ります。アイテムサイズ計算ツールが、掛け算に使うアイテムあたりの数値を出してくれます。

キーの集合を引き戻し、ループを書く前に何が返ってきたか確かめるには、DynoTable をダウンロードしてください。

関連する例

参考資料

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

Console なしで DynamoDB を扱う

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

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