5MBを超えるファイルをAWS S3バケットにアップロードする際、AWS SDK/CLIは自動的にアップロードを複数のHTTP PUTリクエストに分割します。これにより効率が高まり、再開可能なアップロードが実現するうえ、いずれかのパートが失敗しても、進行を止めることなくそのパートだけを再アップロードできます。

AWS S3へのアップロード後に「残りもの」を放置しないようにしましょう
ただし、マルチパートアップロードには見落としがちな落とし穴があります。
オブジェクトのアップロードが完了する前に中断された場合、アップロード済みのパーツを削除するまで課金が続きます。
その結果、すぐには気づかないままストレージコストが増加していくことがあります。
本記事では、アップロード済みパーツを特定する方法と、未完了のマルチパートアップロードがある場合のコスト削減方法を解説します。

画像出典:Jeff Barr氏のブログ記事
AWS S3コンソールでアップロード済みパーツを確認するには?
ここがポイントです。これらのオブジェクトはAWS S3コンソール上では確認できません。
本記事の検証として、S3バケットを作成し100GBのファイルをアップロードしました。40GBがアップロードされた時点で処理を停止しています。
S3コンソールにアクセスすると、バケット内のオブジェクト数は0と表示され、アップロード済みの40GB(マルチパート)は表示されませんでした。

続いてMetricsタブをクリックしたところ、バケットサイズが40GBであることが確認できました。
メトリクスの更新が反映されるまでに数時間かかる場合があります。

つまり、アップロードが完了していないためコンソール上にオブジェクトが表示されない状況でも、すでにアップロード済みのパーツに対しては課金が続いているのです。
実際の現場ではどう対処されているのか?
AWS S3を月単位で大規模に利用しているAWSアカウントを運用する複数社の同僚にヒアリングしました。
その大半が、100MB超から最大10TB超に及ぶ未完了のマルチパートアップロードを抱えていました。S3の利用量が大きく、アカウントの運用期間が長いほど、未完了オブジェクトが多く存在する傾向がある、というのが共通の見解でした。
単一オブジェクトのマルチパートサイズを算出する
まず、AWS CLIで以下のコマンドを実行し、現在のマルチパートオブジェクトを一覧表示します。
aws s3api list-multipart-uploads --bucket <bucket-name>
これにより、未完了かつ複数パートを持つすべてのオブジェクトが出力されます。

次に、list-partsコマンドに「UploadId」の値を指定し、マルチパートアップロード内のすべてのパートを一覧表示します。
aws s3api list-parts --upload-id 5IBStnpJl6REH... --bucket <bucket-name> --key example.exe

続いて、アップロード済みパーツのサイズ(バイト単位)を合計し、JQ(コマンドライン用JSONプロセッサ)でGB単位に変換します。
jq '.Parts | map(.Size/1024/1024/1024) | add'
マルチパートアップロードオブジェクトを手動で削除したい場合は、以下のコマンドを実行します。
aws s3api abort-multipart-upload --bucket <bucket-name> --key example.exe --upload-id 5IBStnpJl6REH...
未完了マルチパートアップロードへの課金を止めるには?
バケットレベルで設定すれば、未完了のマルチパートオブジェクトを数日後に自動削除するライフサイクルルールを作成できます。
「S3ライフサイクル設定とは、Amazon S3がオブジェクトのグループに適用するアクションを定義する一連のルールです」(AWSドキュメント)
ここでは2つの方法を紹介します。
- 既存バケット向けの手動による方法
- 新規バケット作成時に自動適用する方法
既存バケットでマルチパートアップロードを削除する
この方法では、既存バケット内の古いマルチパートオブジェクトを削除するためのオブジェクトライフサイクルルールを作成します。
注意:ライフサイクルルールの定義は慎重に行ってください。設定を誤ると、バケット内の既存オブジェクトが削除される恐れがあります。
まず、AWS S3コンソールを開き、対象のバケットを選択してManagementタブに移動します。
Lifecycle rulesの項目にあるCreate lifecycle ruleをクリックします。

次に、ライフサイクルルールに名前を付け、ルールのスコープとしてバケット内のすべてのオブジェクトを選択します。
「I acknowledge that this rule will apply to all objects in the bucket」のチェックボックスをオンにします。

続いてLifecycle rule actionsに進み、「Delete expired delete markers or incomplete multipart upload」のチェックボックスをオンにします。

「Delete incomplete multipart uploads」のチェックボックスをオンにし、必要に応じてNumber of daysを設定します(未完了アップロードの完了を待つには3日間あれば十分でしょう)。

上記の手順が完了すると、アップロード済みのマルチパートファイルは削除されます。ただし、即時ではなく反映までに少し時間がかかります。
留意点が2つあります。
- 削除操作は無料です。
- ライフサイクルルールを定義した時点で、削除対象となるデータには課金されなくなります。
新規バケット向けにライフサイクルルールを作成する
この方法では、新規バケットが作成されるたびに自動的に適用されるライフサイクルルールを作成します。

新しいバケットが作成されるたびに起動するシンプルなLambda自動化スクリプトを使用します。このLambda関数は、作成から3日経過したマルチパートオブジェクトをすべて削除するライフサイクルルールを適用します。
注意:EventBridgeは作成されたリージョンでのみ動作するため、運用するリージョンごとにLambda関数をデプロイする必要があります。

この自動化を実装するには?
- AWS CloudTrailの証跡を有効化します。証跡を設定すれば、AWS EventBridgeからLambda関数を起動できるようになります。
- 関数のRuntimeにPython 3.8を指定し、新しいLambda関数を作成します。
- 以下のコード(Github gist)を貼り付けます。
4. create functionを選択します。
5. ページ上部までスクロールし、Triggerの欄で「Add trigger」を選択、Trigger configurationではEventBridgeを選びます。
続いて新しいルールを作成し、名前と説明を入力します。
6. ルールタイプでEvent patternを選択し、下にある2つのボックスでSimple Storage Services (S3)とAWS API call via CloudTrailを選択します。
Detailボックスでは、OperationにCreateBucketを指定します。

下にスクロールし、Addボタンをクリックします。
7. Basic settingsタブまでスクロールし、Edit → IAM roleの順に進んで以下のポリシーをアタッチします。
このポリシーにより、Lambda関数がAWSアカウント内のすべてのバケットに対してライフサイクル設定を作成できるようになります。
{
"Version": "2012-10-17",
"Statement": [\
{\
"Sid": "VisualEditor0",\
"Effect": "Allow",\
"Action": "s3:PutLifecycleConfiguration",\
"Resource": "*"\
}\
]
}
8. バケットを作成し、Lambda関数が正しく動作することを確認します。
9. 以上です。これで、設定したリージョンで新しいバケットを作成するたびに、Lambda関数が自動的にライフサイクルルールを適用します。
お読みいただきありがとうございました! 最新情報はDoiT Engineering Blog、DoiTのLinkedInチャンネル、DoiTのTwitterチャンネルでお届けしています。採用情報はhttps://careers.doit-intl.comをご覧ください。