TT Lab
はじめる
学ぶ 学習パス コース

オブジェクトストレージとS3

マルチパートアップロードとETagの算数

TT Labで続きを見る

一言でいうと

マルチパートのETagは、内容のmd5ではありません。各パートのmd5のバイナリ値をつなげてもう一度md5し、-파트수(プレースホルダーはパート数です)を付けた値です。

なぜ必要なのか

5GBのファイルを1回のPUTでアップロードしていて、90%の地点で接続が切れると、最初からやり直しです。そして、1本のTCP接続では、帯域幅を使い切れません。

マルチパートアップロードが、この2つを解決します。アップロードを開始してUploadIdを受け取り、ファイルをパートに分けて並列でアップロードし、最後にパートの一覧で完了を通知します。途中で切れても、そのパートだけをやり直せばよいのです。

どう動くのか

ルールに数字があります。パートは最大10,000個で、最後のパートを除く各パートは、最小5MiBである必要があります。パートの最大サイズは5GiBです。この3つを掛け合わせると、単一オブジェクトの最大サイズが出てきます。5GiB x 10,000 = 約5TBです。

パートサイズの選択は、トレードオフです。小さいとリクエスト数が増え(リクエストごとに課金があるサービスではコスト)、大きいと再送のコストが大きくなります。実務では、8–64MiBの間がよく使われます。ファイルサイズを10,000で割った値よりは大きい必要があります。

ETagの計算が興味深いところです。単一パートなら、内容のmd5そのままです。マルチパートなら、次のように計算します。

1. 각 파트의 md5 를 이진(16바이트)으로 구한다
2. 그것들을 순서대로 이어 붙인다 (파트 5개면 80바이트)
3. 그 80바이트의 md5 를 구한다
4. 뒤에 "-5" 를 붙인다

そのため、ETagにハイフンがあれば、マルチパートでアップロードされたオブジェクトで、ローカルファイルのmd5とは絶対に一致しません。「ETagで完全性を検証したのに合わない」という問い合わせの大半が、これです。検証するには、同じパートサイズで分けて、同じ計算をする必要があります。

パートサイズを決める計算

パートサイズは、勘で決めません。2つの制約が、上限と下限を決めてくれます。

하한 = 파일 크기 ÷ 10,000      (파트 수 제한)
하한 = 최소 5 MiB              (마지막 파트 제외)
상한 = 5 GiB

100GB 파일이면 → 100GB ÷ 10,000 = 10MB 이상이어야 한다
                → 16MiB 로 잡으면 6,400 파트. 여유가 있다

そのあと、再送のコストを見ます。ネットワークが不安定で、平均2%のパートが失敗するなら、パートが大きいほど、1回の失敗で捨てるバイトが大きくなります。逆に、パートが小さいと、リクエスト数が増えて、オーバーヘッドとリクエスト課金が増えます。

ファイルサイズ 推奨パート パート数 備考
100MBまで マルチパート不要 1 単一PUTが簡単です
100MB–1GB 8–16 MiB 12–64 並列4–8で十分です
1GB–50GB 32–64 MiB 30–800 並列8–16
50GB以上 64–128 MiB 400–800 パート数の上限を先に確認

並列度は、帯域幅とメモリで決めます。パートサイズ × 並列度の分だけメモリに載るので、64MiB × 16 = 1GBです。コンテナのメモリ上限を超えると、OOMで死にます。

チェックサムで完全性を確認する正しい方法

ETagで完全性を確認しようとする試みは、マルチパートで崩れます。代わりに、S3が提供する追加のチェックサムを使います。アップロードするときにアルゴリズムを指定すると、S3がパートごとに検証し、オブジェクト全体のチェックサムも保管します。

s3.put_object(Bucket=b, Key=k, Body=data, ChecksumAlgorithm="SHA256")

r = s3.head_object(Bucket=b, Key=k, ChecksumMode="ENABLED")
print(r.get("ChecksumSHA256"))

マルチパートでは、この値も合成方式ですが(パートのチェックサムをつなげて、もう一度ハッシュ)、S3がパートを受け取るたびに検証するので、転送中の破損はその場で見つかります。ETagを手で再計算するより、はるかに安全です。

CRC32Cは計算が速く、大容量に有利で、SHA256は遅いですが、暗号学的に強力です。転送エラーの検出が目的なら、CRC32Cで十分です。

中断されたアップロードを見つけて整理する

# 이 버킷에 떠도는 미완료 업로드
aws s3api list-multipart-uploads --bucket my-bucket

# 하나 지우기 — 올라간 파트가 모두 사라진다
aws s3api abort-multipart-upload   --bucket my-bucket --key big.tar --upload-id <UploadId>

手動で探すのは忘れます。ライフサイクルルールで自動化するのが正解です(AbortIncompleteMultipartUpload、7日)。このルール1つが、バケットを作るときに、標準で入れておくべき設定です。

現場での姿

中断されたマルチパートアップロードは、静かにお金を食います。完了していないアップロードのパートは、ストレージ容量を占めますが、一覧にはオブジェクトとして見えません。クライアントが死んだり、デプロイ中に切れたりしたアップロードが数か月溜まると、請求書に理由のない数百GBが現れます。AWSでよくあるコスト漏れの項目です。

対応は2つです。アプリケーションが、失敗時に明示的に中止(abort)すること、そしてバケットのライフサイクルルールにAbortIncompleteMultipartUploadを7日ほどでかけておくことです。後者を標準としてかけておくのが安全です。

次のラボですること

24MiBのファイルを5MiBのパートに分けて、自分でマルチパートアップロードします。UploadIdを受け取り、パートをアップロードし、完了し、そのあとマルチパートETagを手で計算して、サーバーの値と合わせてみます。最後に、アップロードを開始だけして中止するところまでやってみます。