【AWSコスト削減】S3バッチオペレーションを使うと、大量のオブジェクトのコピーを楽にできた話

この記事は[うるるAdvent Calendar 2024](https://qiita.com/advent-calendar/2024/uluru)の24日目の記事です。

# はじめに
えんフォト事業部でバックエンドエンジニアやってる新卒3年目の山田です！
えんフォト事業部では大量の写真データを保持しており、年々S3のストレージコストが増加していきます。
そこで今年S3のストレージコスト削減施策をやったのですが、その際に活用したS3 バッチオペレーションが活躍してくれました。
今回は備忘録も兼ねて、活用事例とまとめていきます。
※間違っている記載があるかもしれませんが、ご了承ください

# S3 バッチオペレーションとは？
Amazon S3のバケットに保存されているオブジェクトに対して一括でAPIを実行できる機能です。（詳細は[こちら](https://aws.amazon.com/jp/s3/features/batch-operations/)）
対象とするオブジェクト一覧をマニフェストとして指定しジョブを作成することで、任意のオブジェクト群に一括でAPIを実行できます。

## なぜS3 バッチオペレーションを使用したのか？
まずストレージクラスの変更を行う際のアプローチとして浮かぶのは、**ライフサイクルルール**です。
しかし我々のケースの場合、下記の理由からライフサイクルルールが中々活用しにくい状況でした。
 - 条件を満たしたオブジェクトだけ変更対象となる
     - 条件はDBの複数データを参照することで定まる

そこでAPIで任意のオブジェクトに対して、ストレージクラス変更のリクエストを投げる方針に切り替えました。
ただここである問題が発生します。それは処理時間です。

実はS3のストレージクラスの変更は元々行っており、1日1回の定期バッチによって逐次でリクエスト処理を行っておりました。
基本的には問題ないのですが、日によっては数百万~1千万オブジェクトが対象となることもあります。
そうなった日には、「処理が終わらない、終わらないよ。。」と嘆くしかありません。
さらに問題なのが、初期変更です。
今回の施策では条件の変更や新たなストレージクラスへの変更等を行うため、初期変更の対象数はかなり多くなりました。約8千万オブジェクトのストレージクラスを変更する必要がありました。
8千万ものオブジェクトを逐次リクエストで対応していたら、かなりの日数かかりますし、サーバーにも負荷がかかります。

これらの問題を解消してくれる素晴らしい機能、それが**S3バッチオペレーション**でした。

- マニフェストファイルによって、対象オブジェクトの選択に柔軟性を持たせられる
- ジョブを作成すれば、S3 バッチオペレーションが並列に処理を進めてくれるため、処理速度が早い
- バッチの責務がマニフェストファイルの作成とジョブの作成のみなので、負荷がそれほどかからない

## 処理の流れ
基本的に下記の流れで、ストレージクラスの変更を行なっている。

--------------------バッチサーバー--------------------
1. マニフェストファイル(csv)を作成 
    1. DBデータを参照し、対象となるオブジェクトパスをCSVに書き込む
1. ファイルサイズが一定基準に達したら、S3にアップロード（メモリリーク対策）
1. S3バッチオペレーションのジョブを作成
1. 1~3を繰り返す

--------------------S3バッチオペレーション--------------------
1. マニフェストファイルとジョブを参照し、ファイルに記載のオブジェクトを指定のクラスに変更処理

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2953307/c562bfd3-4758-7d1e-8612-6b181352fe95.png)


# 実際にやったこと

### IAMロール作成
S3バッチオペレーションを実行するにあたって、IAMロールの作成が必要です。
具体的には下記。
- 対象パスの書き込み読み込み権限　
    - ※復元処理をバッチオペレーションで行う際は、Restore権限も必要

Trust Policyの例
```
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Principal": {
                "Service": "batchoperations.s3.amazonaws.com"
            },
            "Action": "sts:AssumeRole"
        }
    ]
}
```

Permissions Policyの例
```
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Action": [
                "s3:PutObject",
                "s3:PutObjectAcl",
                "s3:PutObjectTagging",
                "s3:RestoreObject"
            ],
            "Effect": "Allow",
            "Resource": [
                {対象パス}
            ]
        },
        {
            "Action": [
                "s3:GetObject",
                "s3:GetObjectAcl",
                "s3:GetObjectTagging",
                "s3:GetObjectVersion",
                "s3:ListBucket"
            ],
            "Effect": "Allow",
            "Resource": [
                {対象パス}
            ]
        }
    ]
}
```

参考記事
 - [複数のS3のオブジェクトをまとめて一括処理！Amazon S3 Batch Operationsを試してみた](https://dev.classmethod.jp/articles/try-amazon-s3-batch-operations/)
 - [バッチオペレーションに対するアクセス許可の付与](https://docs.aws.amazon.com/ja_jp/AmazonS3/latest/userguide/batch-ops-iam-role-policies.html)

### IAMユーザーのPermission設定とPass role設定
バッチサーバーがジョブの作成するにあたって、ジョブ作成の許可設定が必要です。
さらにジョブ作成時に、パラメーターにS3バッチオペレーションのRoleを渡す必要があるので、IAMのPassRole設定が必要です。

Permissions Policyの例

``` 
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": "s3:CreateJob",
      "Resource": "*"
    },
    {
      "Effect": "Allow",
      "Action": "iam:PassRole",
      "Resource": {IAMロールのarn}
    }
  ]
}
```
:::note warn
s3:CreateJobのResorceはワイルドカードを指定する必要がありそうです
:::

参考記事
 - [create-job — AWS CLI 2.9.6 Command Reference](https://awscli.amazonaws.com/v2/documentation/api/2.9.6/reference/s3control/create-job.html)
 - [Actions, resources, and condition keys for Amazon S3 - Service Authorization Reference](https://docs.aws.amazon.com/service-authorization/latest/reference/list_amazons3.html#amazons3-actions-as-permissions)
 - [S3 batch operation CreateJob access denied | AWS re:Post](https://repost.aws/ja/questions/QUWNaJ_FVeTGq5y69T2_gsLw/s3-batch-operation-createjob-access-denied)

### バッチの作成
下記処理を行うバッチの作成。

1. マニフェストファイルをCSV形式で作成
    1. 対象のオブジェクトを、CSVに書き込み.
        1列目：バケット名
        2列目：変更対象のオブジェクトのパス
    1. 専用のS3パスを用意
    1. 専用パスにマニフェストファイルをアップロード
1. ジョブの作成
    1. SDK for PHPを使用し、ジョブ作成
      作成時に変更先のストレージクラスの指定をしている

参考記事
- [AWS SDK for PHP 3.x](https://docs.aws.amazon.com/aws-sdk-php/v3/api/api-s3control-2018-08-20.html#createjob)


### 結果確認
正常にバッチオペレーションが起動したのかを確認します。
S3のConsole画面から、**バッチオペレーション**を選択すると確認ができます。

添付画像は完了済みのステータスのみですが、実際に複数のジョブが存在する場合やオブジェクト数が多い場合は、**処理待ち**や**アクティブ**といったステータスになります。

ここで成功率を確認することができます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2953307/dda8a15a-6989-4a1f-6e57-4ebb14ea91af.png)

<br>
またバッチオペレーションはレポートを出力することもでき、レポートは該当のジョブIDディレクトリ配下にレポートが出力されます。

![スクリーンショット 2024-12-24 17.44.32.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2953307/53368720-d9d0-abe1-e9c4-cc19d74254e6.png)

<br>

レポートはjson形式もあれば、CSV形式もあります。
resultパス内部には、成功した場合のCSV出力レポート、失敗した場合のCSVレポート両方が出力されます。
このパスに2つのCSVがある場合は、成功と失敗両方しているということを表します。
CSVの中身を見ると、エラーの原因も記載されてるので、エラー調査にも活用ができます。


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2953307/227f7dfd-139f-5e65-adb2-b194b0f4f33c.png)



# 注意点
これまでまとめた通り、S3バッチオペレーションは活用できる場面ではかなりの効果を発揮します。
ただ個人的な注意点としては、ジョブ作成コストです。

＜コスト＞
- 1ジョブあたり　0.25USD
- 実行された100万オブジェクトオペレーションあたり　1.00USD
- その他データ転送、リクエストなどは別途かかる


1つのジョブ作成に0.25USDかかります。
基本的にオブジェクト群をまとめて一括で処理したいときに使用するものなので、頻繁に作成することはないと思いますが、あまりに高頻度で作成するとコストが肥大化する点にはご注意ください。

# 最後に
読んでいただきありがとうございます。
今回はS3バッチオペレーションについてまとめてみました。
実際に８千万近くのオブジェクトのストレージクラスの変更が半日で終わり、想定以上の効果を発揮してくれました。
もし読んでいただいた誰かの課題解消につながれば、嬉しいなと思います。

明日は@dev-nakayaさんの記事です！お楽しみに！