Iceberg 1.10以降のS3チェックサム問題とLegacyMd5Plugin
Iceberg 1.10 以降に上げたら、S3互換ストレージからエラーが返ってきたという話の原因を掘り下げました。 あまり同じ問題でハマる人は少ないのかなと思いつつ、書き残しておきます。
原因は、Iceberg が依存しているAWS SDK for Java v2のS3クライアントが「新しいチェックサムの仕組み」対応になった事と、それに伴ってHTTPヘッダの変更や今まで通常のリクエスト形式だったエンドポイントへも aws-chunked なリクエストが送信されるようになった事でした。
この記事は Iceberg Advent Calendar 2025 5日目の記事です。 Qiita Iceberg Advent Calendar 2025
S3の新しいチェックサム
変更前: MD5時代
昔からS3では、オブジェクトの整合性チェックに MD5 が使われていました。
- リクエストヘッダの
Content-MD5 - レスポンスの
ETag(ただしマルチパートだと必ずしも MD5 ではない)
多くの場合、チェックサムが要求される場面ではMD5が用いられていました。 クライアント側で計算されたチェックサムとサーバ側で計算されたチェックサムを比較することで、データの整合性を確認していました。
変更後: CRCベース
ここ数年のアップデートで、S3本体もSDKもCRC系のチェックサム(CRC32/CRC32C/CRC64NVMEなど)を標準的な扱いとしたようです。 AWSは Data Integrity Protections for Amazon S3 等のドキュメントで概要を公開しています。
- SDK や CLI の「最新バージョン」は、
- アップロード時に CRC ベースのチェックサムを自動計算してヘッダ / トレーラに付ける
- ダウンロード時にもレスポンスのチェックサムを検証する
例えばJavaだと以下のフラグで制御が可能で、デフォルト値の WHEN_SUPPORTED を WHEN_REQUIRED に変えることで、チェックサムを付与・検証する場面を限定できます。
- リクエスト側(アップロード時のチェックサム付与)
request_checksum_calculation(共有 config)AWS_REQUEST_CHECKSUM_CALCULATION(環境変数)aws.requestChecksumCalculation(JVM system property)
- レスポンス側(ダウンロード時のチェックサム検証)
response_checksum_validationAWS_RESPONSE_CHECKSUM_VALIDATIONaws.responseChecksumValidation
しかしながら、このフラグによって挙動が変化するのはSDK内にリストされている一部のエンドポイントへのリクエストのみに限定されます。 いずれの場合もチェックサム必須のエンドポイントに対してはCRCチェックサムを送信するという仕様になっています。
割とIssueのコメントとかブログとかを見ていると、これで解決みたいに書かれていることが多いですが、部分的な正解でしかないようです。
例えば、IcebergでマニュフェストやDataFileを一括で削除する際に呼び出される DeleteObjects API は「チェックサム必須なエンドポイント」に分類されており、 AWS_REQUEST_CHECKSUM_CALCULATION=WHEN_REQUIRED にしてもCRCベースのチェックサムが付与されてしまいます。
そして、チェックサムが必須のエンドポイントは公式でリスト化されている資料は恐らくなく、呼び出しAPIごとにドキュメントや実装を確認する必要があります。
HTTP ヘッダと aws-chunked に何が起きているか
チェックサムの話はHTTPヘッダを中心に掘り下げると変更内容が分かりやすいです。
MD5時代
今までのスタイルのPutObjectリクエストは以下のような形でした。
PUT /bucket/key HTTP/1.1
Host: s3.amazonaws.com
Content-Length: 12345
Content-MD5: Q2hlY2tzdW0hISE=
x-amz-content-sha256: UNSIGNED-PAYLOAD
Authorization: AWS4-HMAC-SHA256 Credential=...
<生のボディ>
ペイロードは通常の HTTPボディで、必要に応じて Content-MD5 ヘッダを付与する形式でした。
新しいスタイル: aws-chunked + トレーラ
SDK 2.30.0 以降、特にストリーミング扱いとなるリクエストは以下のような形になります。
PUT /bucket/key HTTP/1.1
Host: s3.amazonaws.com
Content-Encoding: aws-chunked
x-amz-decoded-content-length: 12345
x-amz-content-sha256: STREAMING-AWS4-HMAC-SHA256-PAYLOAD-TRAILER
x-amz-trailer: x-amz-checksum-crc32
x-amz-sdk-checksum-algorithm: CRC32
Authorization: AWS4-HMAC-SHA256 Credential=...
<チャンク付きのボディ>
[終端チャンク]
x-amz-checksum-crc32: AbCd...
[空行]
今まで生のBodyだった部分が Content-Encoding: aws-chunked 形式でチャンク化され、末尾にチェックサムがトレーラとして付与される形になります。
性能を考慮すると、ストリーミングで送信しながらチェックサムを計算するのは非常に理にかなっていますね。
しかしながら、S3互換ストレージの実装状況によっては以下のような問題が発生する可能性があります。
- 今まで
Content-Encoding: aws-chunked形式でリクエストされていなかったエンドポイントにも適用され、チャンク化を正しく認識せずBodyが壊れる - MD5以外のチェックサムに対応しておらず、
Content-MD5ヘッダが無くなったことで検証に失敗する - 未知の
x-amz-sdk-checksum-*ヘッダに対応しておらず、400エラーになる
利用者の多いOSS等については、既に修正が入っているものが多いようです。
sigv4での詳細な仕様はこちらに記載されています。 https://docs.aws.amazon.com/AmazonS3/latest/API/sigv4-streaming-trailers.html
LegacyMd5Plugin
MD5ベースのチェックサムを付与するというのが、 LegacyMd5Plugin です。
AWS SDK for Java 2.x のドキュメントでは、LegacyMd5Pluginは従来通りのMD5チェックサム計算を行うと書かれています。
具体的にはHTTPヘッダに Content-MD5 を付与するということです。
Data integrity protection with checksums
ドキュメントに記載はないですが、同時に一部のエンドポイントでの Content-Encoding: aws-chunked 形式での送信も抑制されるようです。
Icebergから呼び出される DeleteObjects APIの場合も、LegacyMd5Pluginを有効化することで、トレーラーを含まない通常のペイロード送信に戻ることが確認できます。
直接AWS SDK S3クライアントを利用している場合は、以下のような形でプラグインを追加します。
import software.amazon.awssdk.services.s3.S3Client;
import software.amazon.awssdk.services.s3.LegacyMd5Plugin;
S3Client s3Client = S3Client.builder()
.addPlugin(LegacyMd5Plugin.create())
.build();
Iceberg 1.10以降では、この LegacyMd5Plugin を有効化するためのフラグが用意されています。 例えば以下のような形で設定できます。
- System property:
System.setProperty("client.legacy-md5-plugin-enabled", "true"); - REST Catalog環境変数:
export CLIENT_LEGACY__MD5__PLUGIN__ENABLED=true
Icebergの依存AWS SDKバージョン
Icebergから依存しているAWS SDKのバージョンを確認して影響を整理します。
| Iceberg | AWS SDK for Java v2 |
|---|---|
| 1.9.2 | 2.29.52 |
| 1.10.0 | 2.33.0 |
https://github.com/apache/iceberg/blob/apache-iceberg-1.10.0/gradle/libs.versions.toml#L34C15-L34C21
AWS 側の「デフォルト整合性保護」の変更が入ったのは 2.30.0 なので、1.10.0以降の Iceberg を使う場合は影響を受ける可能性があります。
Trinoでの対応
分散クエリエンジンのTrinoにおいては、キャッシュを効率的に制御するためにIcebergのS3クライアントではなく、Trino側に持つS3クライアントを利用することが多いです。 こちらも同様にAWS SDK for Javaを利用しており、同じ影響を受けます。
利用しているAWS SDKのバージョンは以下の通りです。 ですので、469以降のバージョンを利用している場合は影響を受ける可能性があります。
| Trino | AWS SDK for Java v2 |
|---|---|
| 468 | 2.29.35 |
| 469 | 2.30.3 |
| 470 | 2.30.12 |
| 471 | 2.30.23 |
| 省略 | ~~~~~~ |
| 476 | 2.31.57 |
| 477 | 2.34.1 |
しかしながら、TrinoではデフォルトでLegacyMd5Pluginが有効になっています。 この対応はTrino 476で行われています。
https://github.com/trinodb/trino/commit/d16f83f1e4a317e75e77cbca13f4ee29efcbc130
ですので、TrinoでNative FSを利用している際は、476以降のバージョンを利用していれば特に追加対応を行わずともS3互換ストレージで動作する可能性が高いです。 オプション等は提供されていないので、これで動作しないとなると結構面倒そうです。
以上、Iceberg 1.10 へのアップグレードをきっかけに調べたAWS S3チェックサム周りとLegacyMd5Pluginの挙動に関するメモでした。 利用しているIcebergのバージョンやクエリエンジンの実装や依存AWS SDKのバージョンによって挙動がかなり異なりますので、社内でS3互換ストレージを利用している場合は注意が必要そうです。