Python×AWS

boto3でS3のオブジェクト数と合計サイズを数えると「1000件」で止まる理由と正しい集計方法

S3バケットの棚卸しや使用量の確認で、オブジェクトの総数と合計サイズを数えたくなる場面は多いところです。ところが、list_objects_v2 を1回呼ぶだけの素直なコードだと、1,000件を超えた分はエラーもなく静かに数え漏れます。

この記事では、1,024バイトのオブジェクトを1,005件用意して実際にコードを動かし、数え漏れが起きる様子と、ページネーターを使った正しい集計方法を検証しました。

なお、検証はAWS APIのモックライブラリ「moto」(バージョン5.2.5)の上で行っています。ListObjectsV2の応答上限(1リクエスト最大1,000件)とページネーションの仕様は公式ドキュメントで定められたもので、実AWSでも同じです。一方で、ネットワークや認証、実際の料金は今回の検証対象外です。

検証環境

  • Python + boto3 1.43.123
  • moto 5.2.5(AWS APIのモック)
  • リージョン設定:ap-northeast-1
  • 検証用バケットに、1,024バイトのオブジェクトを1,005件作成

まず素直に書くと数え漏れる

最初に思いつくのは、list_objects_v2 を1回呼んで、返ってきた Contents を数えるコードです。

import boto3

s3 = boto3.client("s3", region_name="ap-northeast-1")

resp = s3.list_objects_v2(Bucket="verify-bucket")
contents = resp.get("Contents", [])
print("件数:", len(contents))
print("合計サイズ:", sum(o["Size"] for o in contents))
print("IsTruncated:", resp["IsTruncated"])

実行結果はこうなりました。

件数: 1000
合計サイズ: 1024000
IsTruncated: True

1,005件入れたはずなのに、返ってきたのは1,000件だけ。5件分(5,120バイト)が欠けています。しかもエラーは出ません。「動いているように見えて、結果が少しだけ間違っている」という、一番厄介な壊れ方です。

なぜ1,000件で止まるのか

ListObjectsV2は、1回のリクエストで最大1,000件までしか返さない仕様です。続きがある場合は応答の IsTruncated が True になり、NextContinuationToken が返ってきます。残りを取得するには、このトークンを渡して続きをリクエストする必要があります。

つまり「1回呼べば全部取れる」という前提で書いたコードは、バケットの中身が1,000件を超えた途端に壊れます。開発中は小さいバケットでテストするため、気づかないまま本番で踏むのが典型的なパターンです。

ページネーターで全件集計する

boto3には、トークンの受け渡しを自動でやってくれるページネーターがあります。集計コードはこうなります。

import boto3

s3 = boto3.client("s3", region_name="ap-northeast-1")

paginator = s3.get_paginator("list_objects_v2")
count = 0
total_size = 0
for page in paginator.paginate(Bucket="verify-bucket"):
    for obj in page.get("Contents", []):
        count += 1
        total_size += obj["Size"]

print("件数:", count)
print("合計サイズ:", total_size)

実行結果です。

件数: 1005
合計サイズ: 1029120

用意した1,005件・1,029,120バイトと完全に一致しました。集計用途では、最初からページネーターを使うのが確実です。

実務で使うときの注意点

  • 対象を Prefix で絞れば、リクエスト回数と処理時間を減らせます
  • リクエストは1,000件ごとに1回発生します。桁違いに件数が多いバケットの棚卸しなら、都度LISTで集計するより、S3 InventoryやS3 Storage Lensのような集計機能の方が向いています
  • 合計サイズはバイト単位です。GBに直すなら total_size / 1024**3 で計算します
  • バージョニングを有効にしたバケットでは、過去バージョンはこの方法では数えられません。別途 list_object_versions が必要です

まとめ

  • list_objects_v2 は1回で最大1,000件まで。IsTruncated を確認しないコードは静かに数え漏れます
  • オブジェクトの集計には get_paginator("list_objects_v2") を使うのが確実です
  • 「エラーが出ないから正しい」とは限らないのが、集計コードの怖いところです

参考リンク

  • Boto3 ドキュメント「Paginators」: https://docs.aws.amazon.com/boto3/latest/guide/paginators.html
  • Boto3「S3.Client.list_objects_v2」: https://docs.aws.amazon.com/boto3/latest/reference/services/s3/client/list_objects_v2.html
  • Boto3「S3.Paginator.ListObjectsV2」: https://docs.aws.amazon.com/boto3/latest/reference/services/s3/paginator/ListObjectsV2.html
  • この記事を書いた人

slimebest

-Python×AWS