大きなファイルをS3にawscliでMultipart Upload
S3にPUTするときの最大サイズは5GBだそうです。これを超えるサイズをアップロードする場合にはMultipart Uploadが必要です。
aws s3 cp
コマンドでは大きいファイルをアップロードする際には自動でMultipart Uploadになりますが、Multipart Uploadの処理の中身を理解するために、aws s3api
コマンドで手動で動かしてみました。
手順概要
aws s3api create-multipart-upload
コマンドでMultipart Upload開始を宣言し、UploadIdを取得aws s3api upload-part
コマンドで分割したファイルをアップロード。分割した数だけこのコマンドを実行- UploadIdは全部同じものを指定
- 1から始まる整数をパーツの番号として指定
- パートごとにETagが返却されるので、それを記録しておく
aws s3api complete-multipart-upload
コマンドでUploadIdとETagのリストを渡すことで完了
手順詳細
set -Ceu set -o pipefail # アップロード先を指定するパラメータ profile=default bucket=SAMPLE_BUCKET key=movies/sample.mov # アップロードするローカルにあるサンプルファイル localfile=sample.mov # 分割したファイルを保存する一時ディレクトリ mkdir -p parts # part 1つあたりのサイズ partsize=6000000 # AWSの仕様によりpart 1つあたりのサイズは5MB以上が必要です # ファイルサイズから分割数を計算 part_count=$(( ($(ls -l sample.mov | awk '{print $5}') + $partsize - 1) / $partsize)) ################################ # 手順1 ################################ # UploadId を取得 upload_id=$(aws --profile $profile s3api create-multipart-upload --bucket $bucket --key $key --query UploadId --output text) ################################ # 手順2 ################################ # 分割したファイルを順番に aws s3api upload-part コマンドによりアップロード。 # 手順3で必要なJSONファイルも同時に作成します。 echo '{"Parts":[' >| parts.json for i in $(seq $part_count); do # 1から分割数までをループ echo $i # アップロードするファイルの$i番目のpartを抜き出す ((cat $localfile | tail -c+$(( ( $i - 1) * $partsize + 1 ))) || true) | head -c $partsize >| parts/$i # headがあるとその前のcatやtailは異常終了してしまいますが、 # 冒頭で set -e しているので、スクリプトが中断しないように true を書いています。 # シンプルに書くと$iが2ならば次のようなコマンドです。 # cat $localfile | tail -c+6000001 | head -c 12000000 > parts/2 echo -n '{"ETag":' >> parts.json # part 1つをアップロード # ETagが出力されるので、そのままJSONに書き出す aws --profile $profile s3api upload-part --bucket $bucket --key $key --part-number $i --body parts/$i --upload-id $upload_id --query ETag --output text >> parts.json # JSONに書き出すPartNumberは1から始まる連番 echo -n ', "PartNumber":'$i'}' >> parts.json if [[ $i < $part_count ]]; then echo ',' >> parts.json else echo >> parts.json fi done echo ']}' >> parts.json # parts.json には以下のようなJSONが書き出されます # {"Parts":[ # {"ETag":"03c58c6387cd642d23657231feb1044f", "PartNumber":1}, # {"ETag":"d18f4e61324478f2b47f907e2b1367b3", "PartNumber":2}, # {"ETag":"52e280adbaa9afcfd30d071255a5b452", "PartNumber":3}, # {"ETag":"144e843c6ad29b05f5faedaf464a3a9a", "PartNumber":4}, # {"ETag":"2251deec7a0d3bad59df68114b18d27d", "PartNumber":5} # ]} ################################ # 手順3 ################################ # ETagのリストをJSONで渡して完了 # これをするまでは aws s3 ls コマンドで見てもアップロード中のファイルは見えない aws --profile $profile s3api complete-multipart-upload --bucket $bucket --key $key --upload-id $upload_id --multipart-upload file://parts.json # aws s3api complete-multipart-uploa コマンドは以下のようなレスポンスをします # { # "VersionId": "Z4epmUvPw5tDhUd5ctQ6hqtbJRABcND8", # "Location": "https://SAMPLE_BUCKET.s3.ap-northeast-1.amazonaws.com/movies%2Fsample.mov", # "Bucket": "SAMPLE_BUCKET", # "Key": "movies/sample.mov", # "ETag": "\"af82619f75aff5484a77ab040f516057-2\"" # }
メモ
分割サイズ
上記スクリプトでは6000000バイト(6MB弱)ずつに分割しています。分割サイズが5MBを下回ると次のようなエラーになってしまいます。
An error occurred (EntityTooSmall) when calling the CompleteMultipartUpload operation: Your proposed upload is smaller than the minimum allowed size
最後のパートだけはサイズが小さくても大丈夫です。どうしようもないですからね。
参考 UploadPart - Amazon Simple Storage Service
list-parts コマンド
手順2のあと以下のようなコマンドを実行すると
aws --profile $profile s3api list-parts --bucket $bucket --key $key --upload-id $upload_id
このようなJSONが出力されます。
{ "Parts": [ { "PartNumber": 1, "LastModified": "2021-02-02T06:05:03.000Z", "ETag": "\"5ec31e0a715293b7512d178890908310\"", "Size": 6000000 }, { "PartNumber": 2, "LastModified": "2021-02-02T06:05:04.000Z", "ETag": "\"9b41c50e2d76cc7959f07cffbafaedd6\"", "Size": 6000000 }, ... ], "Initiator": { ... }, "Owner": { ... }, "StorageClass": "STANDARD" }
手抜きして、これをそのまま手順3のaws s3api complete-multipart-upload
コマンドに渡してしまおうかと思ったのですが、aws s3api complete-multipart-upload
コマンドはETag
とPartNumber
のみが必要で、それ以外の要素がJSONに含まれるとエラーになってしまいました。
それにaws s3api list-parts
コマンドのレスポンスにあるETagはなぜか値自体にダブルクオーテーションが含まれていました。
Pythonのboto3で試すと
同じことをPythonのboto3で試したら、aws s3api list-parts
コマンドと同じく、ETagの値自体にダブルクオーテーションが含まれており、手順3に相当する箇所でもダブルクオーテーションを含めたままで動作しました。違和感・・・