Python (boto3) での DynamoDB Scan

boto3 における scan は 2 つの決定です。リクエストそのものと、それをどうページングするか。以下のスニペットは組み込みのページネーターを使っています。これは誰かがあなたのループの周りに書いたラッパーではありません。botocore の設定 5 行であり、その 5 行が、スキャンが正しいかどうかと、いくらかかるかを決めます。(そもそもスキャンすべきかどうかは別の問題です。)

コード

import boto3

client = boto3.client("dynamodb")

paginator = client.get_paginator("scan")

items = []
for page in paginator.paginate(
    TableName="Music",
    FilterExpression="#filter0 >= :filterValue0",
    ExpressionAttributeNames={"#filter0": "Year"},
    ExpressionAttributeValues={":filterValue0": {"N": "2010"}},
):
    items.extend(page["Items"])

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

解説

  • ページネーターはコードではなくデータです。botocore は paginators-1.json に操作ごとのエントリを 1 つずつ同梱しており、Scan のものは {"input_token": "ExclusiveStartKey", "output_token": "LastEvaluatedKey", "limit_key": "Limit", "result_key": ["Items", "Count", "ScannedCount"], "non_aggregate_keys": ["ConsumedCapacity"]} と読めます。以下のすべてはこれらのキーから導かれます。
  • PaginationConfig={"PageSize": n}Limit を設定しますLimitlimit_key だからです。Limit が制限するのは 読まれる アイテム数であって、返るアイテム数ではありません。したがって FilterExpression があると、ページが空でもコストは発生します。
  • MaxItemsresult_key のアイテムを数え、後続のプロセスで StartingToken として渡せる NextToken を返します。これは、指定した打ち切り点を越えて読むこと自体を止めるものではありません。
  • build_full_result() が集約するのは result_key のフィールドだけですItemsCountScannedCount は合算されますが、ConsumedCapacitynon_aggregate_key なので、マージされた結果は 1 ページ分のキャパシティをスキャン全体のものであるかのように報告します。ページごとに自分で合計しないと、ページ数の分だけ過小報告することになります。
  • FilterExpression は読み取りの後にサーバー側で走るので、課金は Count ではなく ScannedCount に対して行われます。#filter0Year をエイリアスしているのは、それが予約語だからです。エイリアスがなければ、リクエストは何も読まないうちに失敗します。
  • エラーはすべて botocore.exceptions.ClientError として届きますe.response["Error"]["Code"] で分岐しましょう。エラーごとのクラスはクライアント上に生成される属性としてのみ存在し(client.exceptions.ProvisionedThroughputExceededException)、import できるシンボルとしては決して存在しません。
  • リソース API はもう一方の使い勝手ですTable.scan はネイティブな Python の型を取り、数値を decimal.Decimal で返し、プレースホルダーのマップではなく Attr("Year").gte(2010) でフィルターを組み立てます。

フィルター付きの 1 ページが実際にいくらかかるか

それぞれ約 2 KB のアイテム 60 件、そのうち 2 件が Year = 2024 に合致、PageSize=10、DynamoDB Local に対して実行した結果です。

page 1: Count=0 ScannedCount=10 CU=2.5 LastEvaluatedKey=yes
page 2: Count=1 ScannedCount=10 CU=2.5 LastEvaluatedKey=yes
page 3: Count=0 ScannedCount=10 CU=2.5 LastEvaluatedKey=yes
page 4: Count=1 ScannedCount=10 CU=2.5 LastEvaluatedKey=yes
page 5: Count=0 ScannedCount=10 CU=2.5 LastEvaluatedKey=yes
page 6: Count=0 ScannedCount=10 CU=2.5 LastEvaluatedKey=yes
page 7: Count=0 ScannedCount=0 CU=0.0 LastEvaluatedKey=no
total CU across pages: 15.0

実体のある 6 ページのうち 4 ページが、満額を払って何も返しませんでした。これこそ、ページネーターが防ぐために存在するバグの形です。Items が空のときに break する自作のループは 1 ページ目で終了し、合致する 2 曲を 0 件と報告します。

7 ページ目がもう半分です。6 ページ目はテーブルの最後のアイテムで Limit に達したので、DynamoDB はそれでも LastEvaluatedKey を返し、ページネーターはもう 1 往復かけて「もう何もない」ことを知りました。LastEvaluatedKey が意味するのは「まだ続きがある」ではなく「そこで止めた」です。

同じスキャンで build_full_result() を呼ぶと CapacityUnits: 2.5 と報告されます。6 ページの実消費は 15.0 でした。

ループを書かずにページングする

DynamoDB クエリビルダーは、フィルター、エイリアスのマップ、ページネーションのループを 1 つの実行可能なプログラムとして組み立てるので、上の LimitCount の罠は貼り付ける前に処理済みです。スクリプトからではなく対話的に実際のテーブルをページングするには、DynoTable をダウンロードしてください。

関連ガイド

参考資料

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

このリクエストをビジュアルに組み立てる

この操作を無料の DynamoDB クエリビルダーで組み立て — キー条件、フィルタ、インデックス、Limit、ソート順、ページネーションループ — 実行可能な SDK v3・CLI・boto3 のプログラムとしてコピーして戻れます。

DynamoDB クエリビルダーを開く

Console なしで DynamoDB を扱う

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

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