(途中)S3 Batch Operationでバッチ処理してみた
はじめに
「s3のオブジェクトを大量に移動させたいけど大変」「画像データを全てリサイズしたいけど、データ数も多く時間がかかる…」そんな思いをした時に、出会ったのが「S3 Batch Operation」です。
存在は知っていたのですが、あまり周りで使っている人もいなかったので、試してみました。
そもそもS3 Batch Operationsって?
S3のオブジェクトに対しバッチ処理をすることができる機能。あらかじめ用意されているオブジェクトのコピーや移動など基本的な操作だけでなく、Lambda Functionでカスタムの操作を実行することも可能。Lambdaを起動して並列処理することで高速に処理することができるというのが、この機能のうまみなのかなと思います。
今回の目標
今回は「画像データをリサイズして別bucketに保存する」をやってみました。この操作は元々は用意されていない処理なので、Lambda Functionで実装する必要があります。今回はresizeにpillowを使用しましたが、lambdaには元々入っていないかつ、os依存のライブラリなため、自ら環境構築をする必要があります。Docker Imageを利用してLambda Functionを作ることにしました。
今回の成果
用意した画像データ1.2万枚(なんとその容量おそよ160GB)全てにリサイズ処理を行い、別のbucketに保存する処理を試したところ、約18分で完了しました(バッチ処理を行わず1枚ずつ同様の処理をした場合と比較しないといけないところですが...)。
手順
1. テストで利用する画像とs3 bucketを用意
今回は以下の3つのbucketを用意しました。
bucket名 | 役割 |
---|---|
src-20220722 | リサイズ前の画像を入れておく |
dst-20220722 | リサイズした画像をこっちに保存する |
reports-20220722 | バッチ処理に必要なcsvファイルや実行後生成されるreportの保存場所 |
画像は適当に用意しました。1枚10MB以上と非常に容量が大きく、サクッと画像を確認しようにもDLするのにも時間がかかるので、リサイズしたいです。 |
$ aws s3 ls s3://src-20220722/test-images --sum --human-readable
2022-03-02 22:29:23 13.4 MiB 220301235925.jpg
2022-03-02 22:29:29 13.5 MiB 220301235932.jpg
2022-03-02 22:29:34 13.6 MiB 220301235939.jpg
2022-03-02 22:29:39 13.5 MiB 220301235946.jpg
2022-03-02 22:29:45 13.5 MiB 220301235953.jpg
...
Total Objects: 12303
Total Size: 153.8 GiB
...
IAM Roleの作成
S3 Batch Operationsで作成したjobに持たせるIAM Roleとjobが呼び出すLambdaに持たせるIAM Roleの2つが必要になります。今回行う操作に必要な最低限の権限に絞りましたが、もう少し柔軟な権限にしても良いかもしれません。
ref]
S3 Batch job用のIAM Role [Trust policy
s3 batch job用のTrust policyは予め用意されたものはないので、カスタムで作成する必要がある。
IAM > Roles > Create Role > Custome trust policyから以下のように設定する。
{
"Version":"2012-10-17",
"Statement":[
{
"Effect":"Allow",
"Principal":{
"Service":"batchoperations.s3.amazonaws.com"
},
"Action":"sts:AssumeRole"
}
]
}
Permission policy
続いてPermission policy。今回必要な権限は、s3に保存したmanifest(またはcsvファイル。後述)を読み込む権限、実行結果レポートをs3に書き込む権限、Lambdaを呼び出す権限なので、それらを付与します。
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"s3:GetObject",
"s3:PutObject"
],
"Resource": [
"arn:aws:s3:::reports-20220722/*"
]
},
{
"Effect": "Allow",
"Action": [
"lambda:InvokeFunction"
],
"Resource": "arn:aws:lambda:ap-northeast-1:012345678901:function:test-function"
}
]
}
ref]
Lambda Function用のIAM Role [CloudWatchにログを書き込むためのAWSLambdaBasicExecutionRoleと今回実施する操作に必要なポリシー(今回は元となるbucketの読み込み権限とコピー先のbucketへの書き込み権限)をカスタムで作成し付与する。
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"s3:GetObject"
],
"Resource": [
"arn:aws:s3:::src-20220722/*"
]
},
{
"Effect": "Allow",
"Action": [
"s3:PutObject"
],
"Resource": [
"arn:aws:s3:::dst-20220722/*"
]
},
]
}
ref]
3. csvファイルを用意する[s3 batch jobの実行のためには、対象となるオブジェクトのbucket名とobject名が記載されたcsvファイルを用意する必要があります。s3 batch jobを実行すると設定されたcsvファイルに記載されたオブジェクトを読み込み、lambda functionを呼び出され処理が実行されるという流れになります[ref]。
csvファイルは非常にシンプルでbucketとobject keyを記載します。例のようにオブジェクトキーはURLエンコードされている必要があります。
examplebucket,objectkey1
examplebucket,objectkey2
examplebucket,objectkey3
examplebucket,photos/jpgs/objectkey4
examplebucket,photos/jpgs/newjersey/objectkey5
examplebucket,object%20key%20with%20spaces
例えば1行目のexamplebucketのobjectkey1に対しては以下のようなイベントがLambda側に送られます
{
"invocationSchemaVersion": "1.0",
"invocationId": "YXNkbGZqYWRmaiBhc2RmdW9hZHNmZGpmaGFzbGtkaGZza2RmaAo",
"job": {
"id": "f3cc4f60-61f6-4a2b-8a21-d07600c373ce"
},
"tasks": [
{
"taskId": "dGFza2lkZ29lc2hlcmUK",
"s3Key": "objectKey1",
"s3VersionId": "1",
"s3BucketArn": "arn:aws:s3:us-east-1:0123456788:examplebucket"
}
]
}
また、object keyの位置には任意の文字列を入れられるようです。そのため、以下の例のように辞書形式で色々なパラメーターをlambda側に渡すことで柔軟な操作が可能となります。
# encode前
my-bucket,{"origKey": "object1key", "newKey": "newObject1Key"}
my-bucket,{"origKey": "object2key", "newKey": "newObject2Key"}
my-bucket,{"origKey": "object3key", "newKey": "newObject3Key"}
# encode後(こちらを使う)
my-bucket,%7B%22origKey%22%3A%20%22object1key%22%2C%20%22newKey%22%3A%20%22newObject1Key%22%7D
my-bucket,%7B%22origKey%22%3A%20%22object2key%22%2C%20%22newKey%22%3A%20%22newObject2Key%22%7D
my-bucket,%7B%22origKey%22%3A%20%22object3key%22%2C%20%22newKey%22%3A%20%22newObject3Key%22%7D
今回は以下のように元のオブジェクトのbucket名とobject key, 保存先のbucket名、object key、そしてリサイズ
# encode前
src-bucket,{"src_bucket": "src-20220722", "src_key": "220301235925.jpg", "dst_bucket": "dst-20220722", "dst_key": "resized_220301235925.jpg", "resize_ratio": 0.5}
src-bucket,{"src_bucket": "src-20220722", "src_key": "220301235932.jpg", "dst_bucket": "dst-20220722", "dst_key": "resized_220301235932.jpg", "resize_ratio": 0.5}
src-bucket,{"src_bucket": "src-20220722", "src_key": "220301235939.jpg", "dst_bucket": "dst-20220722", "dst_key": "resized_220301235939.jpg", "resize_ratio": 0.5}
ちょっと休憩します...
Discussion