】
きっかけ
AWS S3のバケットからオブジェクト一覧を取得しようと
boto3のlist_objects_v2
を使っていましたが、
リストの上限(1000件)があるため、
一部のモジュールが取得漏れしていることに気づきました。
小規模なバケットでは問題なかったため、
見逃しやすい落とし穴だと思います。
対応
調査の結果、boto3には
**Paginator(ページネーター)**機能があり、
ループ処理で全件取得できることが分かりました。
上限1000件を気にせず、すべてのオブジェクトを取得できます。
実装例
以下のように、Paginatorを使ってすべてのページをたどり、
さらにyield
を使ってスマートにオブジェクトを返すようにしています。
def list_objects_v2_paginator(self, prefix='', suffix=''):
paginator = self.client.get_paginator('list_objects_v2')
page_iterator = paginator.paginate(Bucket=self.bucket_name, Prefix=prefix)
for page in page_iterator:
if 'Contents' in page:
for obj in page['Contents']:
key = obj['Key']
if suffix:
if key.endswith(suffix):
yield obj
else:
yield obj
Paginatorを利用するとなぜできるのか?
通常のlist_objects_v2
は1回の呼び出しで最大1000件しか返せませんが、
Paginatorを使うと、
- API内部で**
ContinuationToken
(継続トークン)**を自動的に扱い - 次のページも順番にたどって
- 最後のページまですべてのデータを取得してくれます。
つまり、プログラム側で何も意識せずに全件取得できるようになります!
【補足】yieldを使うと何が違うのか?【一括読み込みとの比較】
通常の一括読み込み(リストにためるパターン)
例えばS3のオブジェクトを全部リストにためてから返すと、こうなります。
def list_all_objects(self):
objects = []
paginator = self.client.get_paginator('list_objects_v2')
page_iterator = paginator.paginate(Bucket=self.bucket_name)
for page in page_iterator:
if 'Contents' in page:
for obj in page['Contents']:
objects.append(obj)
return objects
この場合、
- 最後まで全部読み込んでから返す
- オブジェクト数が多いとメモリを大量に消費する
- 全部読み終わるまで次の処理に進めない
という特徴になります。
yieldを使った場合(毎回読み込み)
先ほどのlist_objects_v2_paginator
はyield
を使っているので、
- 1件読み込むたびに返す
- メモリに大量データをため込まない
- すぐに次の処理に進める(読みながら処理できる)
という動きになります。
def list_objects_v2_paginator(self, prefix='', suffix=''):
paginator = self.client.get_paginator('list_objects_v2')
page_iterator = paginator.paginate(Bucket=self.bucket_name, Prefix=prefix)
for page in page_iterator:
if 'Contents' in page:
for obj in page['Contents']:
key = obj['Key']
if suffix:
if key.endswith(suffix):
yield obj
else:
yield obj
比較まとめ
項目 | 一括読み込み (リスト) | 毎回読み込み (yield) |
---|---|---|
メモリ使用量 | 多い(全部保持) | 少ない(1件ずつ) |
データ取得タイミング | 最後まで読んでから | 1件ずつすぐ使える |
小規模データ(数百件)向き | ◎ | ○ |
大規模データ(数万件)向き | △(メモリ注意) | ◎(安定) |
コードのわかりやすさ | シンプル(初心者向き) | 少し慣れが必要 |
まとめ
- boto3の
list_objects_v2
単体では1000件上限に注意! - Paginatorを使えば全件取得できる
-
yield
を使えばメモリ効率もよくなる
小規模なら一括取得でも問題ないですが、
数千~数万件以上のオブジェクトを扱うなら、
Paginator+yieldが必須テクニックです!