S3マルチパートアップロード ～Amazon S3パフォーマンス最適化してますか？～

# 突然ですが
皆さん。
S3へサイズの大きいオブジェクトをアップロードする時に、こんな**イラっ**としたことありませんか？

> 長時間のアップロード中にネットワークエラーで処理失敗。また最初からかよ・・・

そのようなときには「マルチパートアップロード」を利用することで、中断したアップロードをリトライすることができます。

今回は、S3のパフォーマンスを最適化する機能「マルチパートアップロード」についてまとめてみました。

# マルチパートアップロードとは
## 簡単にいうと
**サイズの大きいオブジェクトを分割して効率よくアップロード**するS3機能のことです。
S3の[ユーザーガイド](https://docs.aws.amazon.com/ja_jp/AmazonS3/latest/userguide/mpuoverview.html)によるとオブジェクトサイズが100MB以上の場合に利用を推奨しています。

また、利用メリットは下記になります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2611811/aadc8074-d839-fede-f61b-e38753b1af8b.png)

## アップロード完了までの道のり


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2611811/32b440eb-a8db-5304-8f27-854670d2ac8f.png)

大まかに下記の流れでマルチパートアップロードできます。

:::note info
コマンド等の詳細は「**マルチパートアップロードを試してみる**」に記載してます。
:::

>**１．オブジェクトを分割する
２．マルチパートアップロードを作成する
３．分割ファイルごとにアップロードする（最終パートまで繰り返す）
４．JSONファイルを作成する
５．マルチパートアップロードを完了する**


もし、普通のアップロードをすると、
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2611811/70fe90f4-1881-179f-0779-cac84c18bbf2.png)
アップロード中に処理が失敗(中断)したときに送っていたデータが削除されてしまいます。
なので、また一からアップロードし直す必要があります。

しかし、マルチパートアップロードの場合は、
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2611811/c3778608-8e82-f8f4-7a63-0899ee7c7f92.png)
アップロード中に処理が失敗しても、
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2611811/2d8b98d1-c3d1-a413-1c40-7b8b910aad57.png)
失敗した部分からアップロード再開することができます。


## S3のクォータ
S3は最大5TBのオブジェクトを扱うことができますが、
1度にアップロードできるサイズは最大5GBまでとなっているのです。
そのため、5TBのオブジェクトをアップロードしたい場合は、マルチパートアップロードを利用してコツコツアップロードする必要があります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2611811/18fce6ad-710e-32c1-f336-8df0bcbb0150.png)


## マルチパートアップロードの注意点
アップロードが失敗した時、それまで**送れていたパート(部分ファイル)にもストレージ料金が発生**してしまいます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2611811/8d95f096-7859-6811-5b7e-a123ae4a87f0.png)
>マルチパートアップロードの完了リクエストが正常に送信されなかった場合、Amazon S3 はパートを組み立てず、オブジェクトも作成しません。
アップロードされたパートに関連のあるすべてのストレージに対して料金が請求されます。
参照：[マルチパートアップロードの中止](https://docs.aws.amazon.com/ja_jp/AmazonS3/latest/userguide/abort-mpu.html)

このパートはオブジェクトとして未完成のため**マネコンから確認することや削除することができません。**
そのため、無駄に料金が発生していることに気づかない方もいるのではないでしょうか？

そうならないために、**ライフサイクルポリシーを設定**することをオススメします。(AWSのベストプラクティス)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2611811/b8532503-2894-62d2-2c2a-21047ead4290.png)

# マルチパートアップロードを試してみる
マルチパートアップロードのことがだいたい分かってきたところで、仕上げにハンズオンをしてみます。
## 実行タスク
１．ダミーファイルの作成＆分割
２．マルチパートアップロードの開始
３．マルチパートアップロードの終了

:::note warn
下記事前準備は割愛しています。
・アップロード先S3の作成
・AWS CLIインストール
:::
:::note info
このハンズオンでは、300MBのファイルを100MB毎に分割しアップロードします。
:::
## では、ハンズオン開始です
### １．ダミーファイルの作成＆分割 ＜Windowsの場合＞


　**１-１．コマンドプロンプトを起動し、下記コマンドを実行します。**
　`fsutil file createnew (ファイル名) (ファイルサイズ(B))`

```console
PS C:\Users\g1322\multipartup> fsutil file createnew testfile.dat 314572800
ファイル C:\Users\g1322\multipartup\testfile.dat が作成されました
```
　フォルダ上にダミーファイルが作成できました。
　![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2611811/84546901-a276-f34c-6800-b2a1226e668b.png)

　**１-２．7zipでファイル分割を実施します。**（Linuxの場合は、**splitコマンド**を使用します）
　![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2611811/88faf8d0-5c62-a59c-e2b1-ecf9e04edbc9.png)
　フォルダ上に分割ファイルが作成されました。
　![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2611811/80bc0cd6-53de-19e6-c635-aeb142188146.png)
　以上で、アップロードファイルの分割まで完了です。

### ２．マルチパートアップロードの開始
　**２-１．下記コマンドを実行し、アップロードIDを取得します。**
　`aws s3api create-multipart-upload --bucket (バケット名) --key (ファイル名)`
```console
PS C:\Users\g1322\multipartup> aws s3api create-multipart-upload --bucket ozaki-test2 --key testfile.dat
    "ServerSideEncryption": "AES256",
    "Bucket": "ozaki-test2",
    "Key": "testfile.dat",
    "UploadId": "k_7lV1IWRNPKtdmEfQ9oBzRM8OJ3B2IuG_J4OcjF4ShYv0YPQdDCVXEVVvGk9aq37oUZPBkPxK5q_G3Q3TMG2ZtLDxeQAE0eAdrWyFgjJ7S0wGua2y5kFXlZjH_BW.gcmjfXEIjacDD.6zakHzZyQQ--"
}   
```
　UploadIdは後続で使用するためコピーしておきます。
　

　**２-２．1つ目の分割ファイルをアップロードします。**
　`aws s3api upload-part --bucket (バケット名) --key (ファイル名) --part-number 1 --body (分割ファイル名) --upload-id (アップロードID)`
```console
PS C:\Users\g1322\multipartup> aws s3api upload-part --bucket ozaki-test2 --key testfile.dat --part-number 1 --body testfile.dat.001 --upload-id k_7lV1IWRNPKtdmEfQ9oBzRM8OJ3B2IuG_J4OcjF4ShYv0YPQdDCVXEVVvGk9aq37oUZPBkPxK5q_G3Q3TMG2ZtLDxeQAE0eAdrWyFgjJ7S0wGua2y5kFXlZjH_BW.gcmjfXEIjacDD.6zakHzZyQQ--
{                                                                                                                                                                 
    "ServerSideEncryption": "AES256",
    "ETag": "\"2f282b84e7e608d5852449ed940bfc51\""
}
```
　結果のETagをコピーしておきます。

 
<font color="DarkViolet">**－【メモ】－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－**</font>
この時点ではまだオブジェクトの一部しかアップロードできていないため、マネコン上ではオブジェクト0件となっています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2611811/2eaba042-e5f3-8c31-8de8-c2b835fe911f.png)
下記コマンドを実行すると、不完全なデータとしてS3上に存在していることが確認できます。
`aws s3api list-multipart-uploads --bucket (バケット名)`
```console
PS C:\Users\g1322\multipartup> aws s3api list-multipart-uploads --bucket ozaki-test2
{
    "Uploads": [
        {
            "UploadId": "k_7lV1IWRNPKtdmEfQ9oBzRM8OJ3B2IuG_J4OcjF4ShYv0YPQdDCVXEVVvGk9aq37oUZPBkPxK5q_G3Q3TMG2ZtLDxeQAE0eAdrWyFgjJ7S0wGua2y5kFXlZjH_BW.gcmjfXEIjacDD.6zakHzZyQQ--",
            "Key": "testfile.dat",
            "Initiated": "2022-05-06T09:43:54+00:00",
            "StorageClass": "STANDARD",
            "Owner": {
                "DisplayName": "*********************",
                "ID": "*********************"
            },
            "Initiator": {
                "ID": "arn:aws:iam::*********************",
                "DisplayName": "*********************"
            }
        }
    ]
}
```
<font color="DarkViolet">**－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－**</font>

　**２-３．残りの部分ファイルに対して手順２-２を繰り返します。**
　アップロードする各部分ごとに`part-number`を増やしてください。

　**２-４．対象の部分ファイルがすべてアップロードできていることを確認します。**
　`aws s3api list-parts --bucket (バケット名) --key (ファイル名) --upload-id (アップロードID)`

```console
PS C:\Users\g1322\multipartup> aws s3api list-parts --bucket ozaki-test2 --key testfile.dat --upload-id k_7lV1IWRNPKtdmEfQ9oBzRM8OJ3B2IuG_J4OcjF4ShYv0YPQdDCVXEVVvGk9aq37oUZPBkPxK5q_G3Q3TMG2ZtLDxeQAE0eAdrWyFgjJ7S0wGua2y5kFXlZjH_BW.gcmjfXEIjacDD.6zakHzZyQQ--
{
    "Parts": [
        {
            "PartNumber": 1,
            "LastModified": "2022-05-06T09:52:29+00:00",
            "ETag": "\"2f282b84e7e608d5852449ed940bfc51\"",
            "Size": 104857600
        },
        {
            "PartNumber": 2,
            "LastModified": "2022-05-06T16:40:47+00:00",
            "ETag": "\"2f282b84e7e608d5852449ed940bfc51\"",
            "Size": 104857600
        },
        {
            "PartNumber": 3,
            "LastModified": "2022-05-06T16:41:48+00:00",
            "ETag": "\"2f282b84e7e608d5852449ed940bfc51\"",
            "Size": 104857600
        }
    ],
    "ChecksumAlgorithm": null,
    "Initiator": {
        "ID": "arn:aws:iam::*********************",
        "DisplayName": "*********************"
    },
    "Owner": {
        "DisplayName": "*********************",
        "ID": "*********************"
    },
    "StorageClass": "STANDARD"
}
```
　**２-５．接着剤的な役割のJSONファイルを作成します。**
　下記のようにJSONファイルにアップロードした各ファイル部分の ETag 値をコンパイルします。

```json
{
    "Parts": [{
        "ETag": "\"2f282b84e7e608d5852449ed940bfc51\"",
        "PartNumber":1
    },
    {
        "ETag": "\"2f282b84e7e608d5852449ed940bfc51\"",
        "PartNumber":2
    },
    {
        "ETag": "\"2f282b84e7e608d5852449ed940bfc51\"",
        "PartNumber":3
    }]
}
```
　ファイル名をfileparts.json とし保存します。

### ３．マルチパートアップロードの終了
　**３-１．下記コマンドを実行し、マルチパートアップロードを終了させます。**

　`aws s3api complete-multipart-upload --multipart-upload file://fileparts.json --bucket (バケット名) --key (ファイル名) --upload-id (アップロードID)`

```console
PS C:\Users\g1322\multipartup> aws s3api complete-multipart-upload --multipart-upload file://fileparts.json --bucket ozaki-test2 --key testfile.dat --upload-id k_7lV1IWRNPKtdmEfQ9oBzRM8OJ3B2IuG_J4OcjF4ShYv0YPQdDCVXEVVvGk9aq37oUZPBkPxK5q_G3Q3TMG2ZtLDxeQAE0eAdrWyFgjJ7S0wGua2y5kFXlZjH_BW.gcmjfXEIjacDD.6zakHzZyQQ--
{
 　  "ServerSideEncryption": "AES256",
   　"Location": "https://ozaki-test2.s3.ap-northeast-1.amazonaws.com/testfile.dat",
   　"Bucket": "ozaki-test2",
   　"Key": "testfile.dat",
   　"ETag": "\"a67c1d71179e7552ad55fe9c0b755e76-3\""
}
```
　２-５で作成したJSONファイルを読み込むことで、分割ファイルが合体しオブジェクトとして正式にS3バケット上に認識されます。
　![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2611811/6839644d-21d5-532e-ce7d-fbe2ef6955af.png)

<font color="DarkViolet">**－【メモ】－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－**</font>
　アップロードがすべて完了すると、不完全なデータが0件となります。
```console
PS C:\Users\g1322\multipartup> aws s3api list-multipart-uploads --bucket ozaki-test2
PS C:\Users\g1322\multipartup> 
```
<font color="DarkViolet">**－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－**</font>

以上で、マルチパートアップロードのハンズオン終了となります。

# まとめ
マルチパートアップロードには
「アップロード処理が中断しても再開できる」という最大のメリットがある一方、
「分割数が多くなるにつれ繰り返し手順が多くなる」というデメリットがあります。（個人の見解です）

この一連の対応手順を自動化できたらいいなーと思っています。
開発得意な方、「マルチパートアップロードを自動化してみた」記事期待してます！