AWS SDKとGoogle Cloud SDKにおけるページネーションの違い
概要
ストレージ内のファイルを列挙する等の、結果が膨大かもしれない処理では、結果を小分けにして返してくれるページネーション処理が用いられます。AWSとGoogle CloudのSDKでは、その作りが逆と言えます。まとめておきます。
- AWS SDKは、素朴に使うと(低級APIでは)ページネーション無し
- Google Cloud SDKは、既定でページネーション有り
本記事の説明ではC#とPythonを使いました。全部は把握していないのですがおそらく、低級なAPIを使う限りは言語問わず同じ結論が言えるはずです。例題として、ストレージ (Amazon S3 / Google Cloud Storage) のListObjects
[1]操作を扱います。
以下公式ドキュメントを読めばだいたい終わりではありますが。
筆者の環境
- C# (.NET 6)
- Python 3.10
AWS
AWS SDKは、素朴に使うとページネーション無し
C#
低級なAPI
ListObjects(V2)
はデフォルトで1000件が上限で、バケットにもっと多数のオブジェクトがあったとしても1000件しか返しません。ListObjects
の結果から取れるNextContinuationToken
を次のリクエストのContinuationToken
に指定することで、次の1000件を得ることができます。低級なAPIでは言語問わずだいたい以下のようなイディオムを書くことになります。
using Amazon.S3;
using Amazon.S3.Model;
using var client = new AmazonS3Client();
var request = new ListObjectsV2Request
{
BucketName = "my-bucket",
Prefix = "foo/bar/",
};
do
{
var response = await client.ListObjectsV2Async(request);
foreach (var o in response.S3Objects)
{
...
}
request.ContinuationToken = response.NextContinuationToken;
} while (!string.IsNullOrEmpty(request.ContinuationToken));
ページネーション有り
.NET向けSDKの場合、S3Client
など一部のクライアントは、.Paginators
でページネーション対応の処理を行えます。ContinuationToken
の処理を裏で行ってくれます。 https://docs.aws.amazon.com/sdkfornet/v3/apidocs/items/S3/TIS3PaginatorFactory.html
using var client = new AmazonS3Client();
var response = client.Paginators.ListObjects(new ListObjectsRequest
{
BucketName = "my-bucket",
Prefix = "foo/bar/"
});
await foreach (var o in response.S3Objects)
{
...
}
Python
Pythonの例も述べておきます。boto3も同じくpaginatorを用意しています。以下ページにある通りです。
import boto3
client = boto3.client('s3')
paginator = client.get_paginator('list_objects')
page_iterator = paginator.paginate(Bucket='my-bucket')
for page in page_iterator:
print(page['Contents'])
もちろん、S3のlist_objectsに関して言えば、boto3.resource
を使えばもっと高級に書けるのはboto3利用者にはおそらくご承知の通りです。
import boto3
s3 = boto3.resource('s3')
bucket = s3.Bucket('my-bucket')
for object in bucket.objects.all():
print(object)
Google Cloud
Google Cloud SDKは、既定でページネーション有り
以下記載のように、裏で自動的にページネーション処理をしてくれて、全件の結果を得られます。
C#
全件処理
全件欲しいなら、何も考える必要はありません。
using Google.Cloud.Storage.V1;
var pagedObjects = client.ListObjectsAsync("my-bucket", "foo/bar/");
await foreach (var o in pagedObjects)
{
...
}
System.Linq.Async を導入すると、例えばすぐ配列として得たいときにToArrayAsync
など便利なメソッドが使えます。ただし、ものすごくファイル数が多い場所でToArrayなど全部をなめるようなことをしてしまうと、延々と処理が終わらずにメモリを食い尽くしていきます。欲しい量が決まっていれば、Take等で制御します。
var pagedObjects = client.ListObjectsAsync("my-bucket", "foo/bar/");
var allObjects = await pagedObjects.ToArrayAsync(); // やばい
var objects = await pagedObjects.Take(1000).ToArrayAsync();
ページごとに取得
あえてページ単位で結果を得ることもできます。AsRawResponses
を使います、
var options = new ListObjectsOptions
{
PageSize = 100,
};
var pagedObjects = client.ListObjectsAsync("my-bucket", "foo/bar/", options);
var raw = objects.AsRawResponses();
await foreach (var page in raw)
{
Console.WriteLine(page.Items.Count); // 100
}
page.Items
には、ここでは100件単位でオブジェクトの内容が入ります。
Python
同様に、ページネーションのことは考えなくても、普通にforを回せば全部取ってこれます。
from google.cloud import storage
client = storage.Client(project="my-project")
blobs = client.storage.list_blobs("my-bucket", prefix="foo/bar/")
for blob in blobs:
...
指定の件数だけ欲しければ、例えば itertools.islice
を使う手があります。
ここもC#でのToArrayで述べたのと同様に、[x for x in blobs]
のように安易に実体化するとメモリを食い尽くすかもしれません。できるだけイテレータのまま扱うようにします。
from itertools import islice
from google.cloud import storage
client = storage.Client(project="my-project")
all_blobs = client.storage.list_blobs("my-bucket", prefix="foo/bar/")
blobs = list(islice(all_blobs, 1000))
-
PythonのGoogle Cloud Storage SDKでは
list_blobs
という名前になっており、言語により差がある場合があります。https://googleapis.dev/python/storage/latest/client.html#google.cloud.storage.client.Client.list_blobs ↩︎
Discussion