06. 運用・メンテナンス・性能
調査基準日: 2026-07-17 / Iceberg 1.11.0 基準。プロパティ名・デフォルト値・シグネチャはすべて main の原文から逐語確認
公式の分類: “Recommended” と “Optional”
最初にこれを知ってください。一般的な認識と逆です。
maintenance.md の分類:
| 分類 | 作業 |
|---|---|
| Recommended Maintenance | Expire Snapshots / Remove old metadata files / Delete orphan files |
| Optional Maintenance | Compact data files / Rewrite manifests |
公式は compaction を「オプション」、スナップショット期限切れと metadata 掃除を「推奨(必須級)」に位置づけています。
そしてこの因果関係が決定的です(maintenance.md の !!! info):
データファイルは、タイムトラベルやロールバックに使われうるスナップショットから参照されなくなるまで削除されない。定期的なスナップショット期限切れが未使用データファイルを削除する。
→ expire_snapshots を回さない限り、compaction は容量を一切解放しません。むしろ増えます(新しいファイルを書いた上で、古いファイルもスナップショットに参照されたまま残るため)。運用上もっとも誤解されやすい点です。
A. メンテナンス作業
A-1. compaction / rewrite_data_files
なぜ必要か(公式の理由): 「Iceberg はテーブル内の各データファイルを追跡する。データファイルが増えるほど manifest に格納されるメタデータが増え、小さなデータファイルはファイルオープンコストにより不要な量のメタデータと非効率なクエリを引き起こす」
シグネチャ:
| 引数 | 必須 | 型 | 説明 |
|---|---|---|---|
table |
必須 | string | |
strategy |
string | binpack または sort。デフォルトは binpack |
|
sort_order |
string | Zorder は zorder(c1,c2,c3) 形式。それ以外は (ColumnName SortDirection NullOrder) のカンマ区切り |
|
options |
map<string,string> | ||
where |
string | フィルタに合致するデータを含みうる全ファイルが書き換え対象に選ばれる点に注意 |
3戦略の違い:
- bin-pack(デフォルト): 小ファイルを結合し、大ファイルは分割する。行の並べ替えを伴わないため最も安価。
- sort: 指定した sort order で全データをソートして書き直す。専用オプション
compression-factor(既定 1.0)、shuffle-partitions-per-file(既定 1)。 - z-order:
strategy => 'sort'かつsort_order => 'zorder(c1,c2)'として指定します(独立した strategy 名ではありません。実装上重要)。専用オプションvar-length-contribution(既定 8)、max-output-size(既定 2147483647)。
主要オプションとデフォルト値:
| オプション | デフォルト | 意味 |
|---|---|---|
target-file-size-bytes |
536870912(512MB) | write.target-file-size-bytes の既定値 |
min-file-size-bytes |
目標サイズの 75% | これ未満は他条件に関わらず書き換え候補 |
max-file-size-bytes |
目標サイズの 180% | これ超過は他条件に関わらず書き換え候補 |
min-input-files |
5 | この数以上のファイルを持つグループは書き換え(最低2ファイル必要) |
max-concurrent-file-group-rewrites |
5 | |
partial-progress.enabled |
false | 全体完了前に部分コミットを許可 |
partial-progress.max-commits |
10 | |
partial-progress.max-failed-commits |
max-commits の値 |
|
use-starting-sequence-number |
true | |
rewrite-job-order |
none | bytes-asc/bytes-desc/files-asc/files-desc/none |
rewrite-all |
false | 全ファイル強制書き換え |
max-file-group-size-bytes |
107374182400(100GB) | 巨大パーティションをリソース制約内で分割処理するため |
delete-file-threshold |
2147483647 | |
delete-ratio-threshold |
0.3 | |
output-spec-id |
現在の spec id | |
remove-dangling-deletes |
false | 追加コミットが1回発生 |
max-files-to-rewrite |
null |
min-file-size-bytes=75% /max-file-size-bytes=180% は、実質的に「384MB〜922MB の範囲外のファイルは書き換え候補」という公式のヒステリシス帯を意味します。これが「望ましいファイルサイズ帯」について公式から導ける最も具体的な指針です。
remove-dangling-deletes の公式注記: dangling delete はデータシーケンス番号のみに基づいて削除され、グローバル equality delete、無効な equality delete、生存データファイルに合致しなくなった position delete を含む position delete ファイルには適用されません。
A-2. rewrite_manifests
compaction とは目的が異なります。 これはデータではなくインデックスの再編成です。
Iceberg は manifest list と manifest 内のメタデータを使ってクエリプランニングを高速化し、不要なデータファイルを枝刈りする。メタデータツリーはテーブルのデータに対するインデックスとして機能する。
manifest は追加された順に自動的にコンパクションされるため、書き込みパターンが読み取りフィルタと整合している場合にクエリが速くなる(例: 時間範囲クエリに対する時間到着順の書き込み)。テーブルの書き込みパターンがクエリパターンと整合しない場合、
rewriteManifestsでメタデータを書き直してデータファイルを manifest に再グループ化できる。
→ 必要になるのは「書き込み順とクエリ述語がずれているとき」です。
| 引数 | 必須 | 型 | 説明 |
|---|---|---|---|
table |
必須 | string | |
use_caching |
boolean | 既定 false。有効化すると executor のメモリ使用量が増える | |
spec_id |
int | ||
sort_by |
array |
頻繁にクエリされる transform を選ぶと不要な manifest をスキップして planning 時間を削減できる |
公式注記: 「この procedure は対象テーブルを参照する全てのキャッシュ済み Spark プランを無効化する」
A-3. expire_snapshots
なぜ必要か: 「Iceberg の各 write/update/delete/upsert/compaction は新しいスナップショットを生成し、スナップショット分離とタイムトラベルのために古いデータとメタデータを残し続ける」
放置時: 「スナップショットは expire されるまで蓄積し続ける」→ ストレージ課金の無限増加とメタデータサイズ増大。
この procedure は、未期限のスナップショットがまだ必要としているファイルを削除することはありません。
| 引数 | 必須 | 型 | デフォルト |
|---|---|---|---|
table |
必須 | string | |
older_than |
timestamp | 5日前 | |
retain_last |
int | 1 | |
max_concurrent_deletes |
int | デフォルトではスレッドプールを使わない | |
stream_results |
boolean | 大容量時の Spark driver OOM を防ぐため true 推奨 | |
snapshot_ids |
array of long | ||
clean_expired_metadata |
boolean | 参照されなくなった partition spec / schema も整理 |
運用上の罠: 「ブランチやタグから参照されているスナップショットは削除されません。 デフォルトではブランチとタグは決して expire しませんが、
history.expire.max-ref-age-msで変更できます。mainブランチは決して expire しません。」
→ タグを1つ付けただけで、そのスナップショットと依存ファイルが無期限に残ります。 Amazon S3 Tables ではこれがさらに深刻な挙動になります(C-1)。
A-4. remove_orphan_files ※ 最も危険な操作
なぜ必要か: 「Spark やその他の分散処理エンジンでは、タスクやジョブの失敗によりテーブルメタデータから参照されないファイルが残ることがあり、また通常のスナップショット期限切れではファイルが不要と判断できず削除できない場合がある」
Iceberg の書き込みは「先にデータファイルを書く → 最後にメタデータをコミットする」順序なので、コミット前に落ちたジョブが書いたデータファイルは、どのメタデータからも参照されないまま物理的に残ります。 これは設計上不可避であり、だからこそ専用の掃除機構が必要です。
公式が明記する危険性(逐語):
書き込みが完了するのに要すると見込まれる時間より短い保持間隔で orphan ファイルを削除するのは危険である。進行中のファイルが orphan とみなされて削除されると、テーブルを破壊する可能性があるからである。デフォルトの間隔は3日である。
Iceberg はどのファイルを削除すべきか判断する際にパスの文字列表現を使う。一部のファイルシステムではパスが時間とともに変化しうるが、それでも同じファイルを表す。例えば HDFS クラスタの authority を変更した場合、作成時に使われた古いパス URL はどれも現在のリスティングに現れるものと一致しない。これは RemoveOrphanFiles を実行した際にデータ損失につながる。
実務結論:
- 必ず
dry_run => trueで先に確認する(公式の最初の例がこれです) older_thanをデフォルトの3日より短くしない。 特に長時間バッチや Flink の長時間チェックポイントがある環境では、最長書き込み時間より確実に長く取るprefix_mismatch_modeのデフォルトはERROR(例外送出)であり、これは安全側の設計です。 これをDELETEにするのは、スキーム/authority の不一致が本当に同一ファイルを指すと確証がある場合のみ。安易なDELETE指定はまさに上記のデータ損失シナリオそのものですgc.enabled(既定 true)を false にすると GC 操作自体が禁止されます。外部でファイルを共有している場合の防御策になります
| 引数 | デフォルト | 説明 |
|---|---|---|
older_than |
3日前 | |
dry_run |
false | |
location |
テーブルの location | |
stream_results |
有効時、出力は最大 20,000 パスのサンプルを含む | |
equal_schemes |
map('s3a,s3n','s3') |
等価とみなすスキーム |
equal_authorities |
||
prefix_mismatch_mode |
ERROR | ERROR(例外)/ IGNORE / DELETE |
prefix_listing |
false |
1.11.0 の関連改善: 「Unique table locations(新カタログプロパティによりテーブルストレージパスに UUID を付与)は、DeleteOrphanFiles がリネームされたテーブルのファイルを削除しうるデータ損失シナリオを防ぐ」— orphan 削除のデータ損失リスクが実在の問題として認識され、構造的に対処された証左です。
A-5. rewrite_position_delete_files と v3 deletion vector
2つの目的:
- Minor Compaction: 小さな position delete ファイルを大きなものに結合
- Remove Dangling Deletes: 「
rewrite_data_filesの後、書き換えられたデータファイルを指す position delete レコードは常に削除対象としてマークされるとは限らず、テーブルの生存スナップショットメタデータに追跡され続けることがある。これが ‘dangling delete’ 問題として知られる」
オプションは rewrite_data_files とほぼ同じですが、target-file-size-bytes の既定が 67108864(64MB)(データファイルの 512MB ではない)。
v3 でどう変わるか
02-table-spec.md で見た通り、v3 では:
- position delete ファイルの新規追加が禁止
- 「1データファイル = 高々1つの DV」が spec レベルで保証
- DV は既存 position delete を置き換える
→ rewrite_position_delete_files の存在意義が構造的に縮小します。 小さな delete ファイルが際限なく積み上がるという v2 の主要な病理が消え、「minor compaction」の対象そのものが生まれません。
ただし完全には消えません:
- Puffin ファイル自体の書き直しは spec 上不要なので、「参照されない DV blob を含む Puffin ファイル」が残りえます →
remove_orphan_filesの領分 - v2 テーブルには依然として必要です。v2→v3 アップグレード後も既存の position delete ファイルは有効なまま残るため、移行期は両方の考慮が必要
【未確認】: v3 の DV に対する専用の compaction procedure(DV 版
rewrite_position_delete_filesに相当)はspark-procedures.mdに見つかりませんでした。rewrite_data_filesのremove-dangling-deletesは「position and equality deletes」を対象とする記述で、DV に対する挙動は docs から読み取れません。
A-6. 古い metadata ファイルの削除 — 後戻りできない設定
これは公式分類で “Recommended” です。
各 metadata ファイルは
metadata-logフィールドで古い metadata ファイルを追跡する。追跡される metadata ファイル数はwrite.metadata.previous-versions-maxで定義される。
write.metadata.delete-after-commit.enabled=trueを設定すると、追跡されている(tracked) metadata ファイルを保持しつつ、新しいものが作られるたびに最古のものを削除する。これは metadata log に追跡されている metadata ファイルのみを削除し、orphan 化した metadata ファイルは削除しない。
公式の具体例が罠の本質を示しています(逐語):
write.metadata.delete-after-commit.enabled=falseかつwrite.metadata.previous-versions-max=10で100コミット後、10個の tracked metadata ファイルと 90個の orphaned metadata ファイルを持つことになる。これら90個の orphaned metadata ファイルは、後からwrite.metadata.delete-after-commit.enabled=trueに設定しても削除できない。既に untracked だからである。orphan file deletion procedure でしかクリーンできない。
→ write.metadata.delete-after-commit.enabled は既定 false であり、テーブル作成時に有効化しておかないと後戻りできません。 ストリーミング/高頻度コミットのテーブルでは作成時点で必ず有効化してください。既に肥大したテーブルでは remove_orphan_files(= A-4 の危険な操作)が唯一の手段になります。
B. 重要なテーブルプロパティ
すべて docs/docs/configuration.md(main)から逐語確認。
Write properties
| プロパティ | デフォルト | 説明 |
|---|---|---|
write.format.default |
parquet |
parquet / avro / orc |
write.target-file-size-bytes |
536870912(512MB) | |
write.delete.target-file-size-bytes |
67108864(64MB) | |
write.parquet.row-group-size-bytes |
134217728(128MB) | |
write.parquet.compression-codec |
zstd |
zstd / brotli / lz4 / gzip / snappy / uncompressed |
write.distribution-mode |
未設定(エンジン依存) | none / hash / range |
write.delete.distribution-mode 等 |
未設定 | delete/update/merge 用に独立に存在 |
write.metadata.metrics.default |
truncate(16) |
none / counts / truncate(length) / full |
write.metadata.metrics.max-inferred-column-defaults |
100 | メトリクスを収集する最大列数 |
write.metadata.delete-after-commit.enabled |
false | ← A-6 参照 |
write.metadata.previous-versions-max |
100 | |
write.object-storage.enabled |
false | ファイルパスにハッシュ要素を加える |
write.delete.mode / write.update.mode / write.merge.mode |
copy-on-write |
v2 以降で merge-on-read 可 |
write.*.isolation-level |
serializable | serializable / snapshot |
write.delete.granularity |
partition | partition / file |
write.spark.fanout.enabled |
false | メモリ使用量が増える |
metrics モードの定義:
none: 永続化しないcounts: カウントのみtruncate(length): カウント + 切り詰めた境界値。切り詰めは string と binary にのみ適用full: 完全な境界値
Table behavior properties
| プロパティ | デフォルト |
|---|---|
commit.retry.num-retries |
4 |
commit.retry.min-wait-ms |
100 |
commit.retry.max-wait-ms |
60000(1分) |
commit.retry.total-timeout-ms |
1800000(30分) |
commit.status-check.num-retries |
3 |
commit.manifest.target-size-bytes |
8388608(8MB) |
commit.manifest.min-count-to-merge |
100 |
commit.manifest-merge.enabled |
true |
history.expire.max-snapshot-age-ms |
432000000(5日) |
history.expire.min-snapshots-to-keep |
1 |
history.expire.max-ref-age-ms |
Long.MAX_VALUE(無期限)。main は決して expire しない |
gc.enabled |
true |
Read properties
| プロパティ | デフォルト |
|---|---|
format-version |
2(1.4.0 以降) |
read.split.target-size |
134217728(128MB) |
read.split.open-file-cost |
4194304(4MB) |
read.parquet.vectorization.enabled |
true |
read.orc.vectorization.enabled |
false |
矛盾の指摘:
configuration.mdはwrite.distribution-modeを「not set, see engines for specific defaults」とする一方、spark-writes.mdは「Iceberg 1.2.0 以降、hashが新しいデフォルト」と記述します。
これは矛盾ではなく階層の違いです — テーブルプロパティとしては未設定で、Spark エンジンが実行時に hash を要求するという構造です。したがって他エンジン(Flink 等)では挙動が異なりえます。また「Spark は 3.5.0 より前では CTAS/RTAS で distribution mode を尊重しない」という重要な例外も明記されています。
C. アンチパターン(仕様レベルの発生機序)
1. Over-partitioning
なぜ小ファイルが生まれるのか(仕様上の根拠):
spark-writes.md が明記する通り「ファイルは Iceberg のパーティション境界をまたげない(a file cannot span an Iceberg partition boundary)」— これが over-partitioning が必ず小ファイルを生む理由です。パーティションを細かくすると、各パーティションのデータ量が 512MB を大きく下回り、パーティション境界がそのままファイル境界になります。
さらに write.distribution-mode=hash(Spark 既定)ではパーティション値でハッシュ分散されるため、パーティション数が多いほどタスクあたりのデータが薄くなり、1パーティション×1タスクごとに小ファイルが1つ生まれます。
公式な「パーティションあたり推奨サイズ」の数値は、
partitioning.md・performance.md・configuration.mdを確認した範囲で見つかりませんでした(未確認)。 「1パーティション最低1GB」等の一般に流布する目安には公式の裏付けが確認できません。
2. 高頻度小コミットによるメタデータ肥大
なぜ起きるか: 「Iceberg はテーブルメタデータを JSON ファイルで追跡する。テーブルへの各変更は、原子性を提供するために新しい metadata ファイルを生成する」
つまりコミット1回 = metadata JSON 1個 + スナップショット1個 + manifest list 1個が確定的に生成されます。これは最適化ではなく原子性のための構造的要件です。
write.metadata.delete-after-commit.enabled が既定 false なので、何も設定しなければ metadata JSON は無限に蓄積し、previous-versions-max=100 を超えた分は untracked(=後から自動削除不能)になります(A-6)。
緩和策の commit.manifest-merge.enabled(既定 true)は manifest レベルの話であり、metadata JSON とスナップショットの蓄積は別途対処が必要です。
3. 複数ライタ競合と commit retry
なぜ起きるか: 01-architecture.md の通り、Iceberg は楽観的並行制御です。コミットは「現在のスナップショットを読む → 新スナップショットを作る → アトミックにスワップ」という CAS 的操作で、他のライタが先にスワップすると失敗し commit.retry.* に従いリトライします(既定4回、100ms〜60秒のバックオフ、合計30分)。
実務上の帰結: compaction ジョブ自体が最も競合を起こしやすいライタです。 大きなパーティションを書き換える compaction は長時間データファイルを保持し、その間の並行書き込みと衝突します。
対策:
partial-progress.enabled=true+partial-progress.max-commitsで小さく刻んでコミットし、1コミットあたりの競合窓を狭めるmax-file-group-size-bytes(既定 100GB)を下げ、where句で書き込みが起きていないパーティションに限定する
write.*.isolation-level を serializable から snapshot に下げると競合検出が緩和されますが、これは分離レベルを落とす判断であり、正しさへの影響を理解した上でのみ行ってください。
4. Orphan files
2つの独立した機序があります:
- タスク/ジョブ失敗: 書き込み順序(データ先、メタデータ後)から構造的に不可避(A-4)
- untracked metadata:
previous-versions-maxを溢れた metadata JSON(A-6)
5. メタデータ JSON 肥大
上記 2 と 4 の合流点です。設計時に決めるべき設定であり、運用開始後の是正はコストが高い(唯一の手段が A-4 の危険な操作)というのが結論です。
D. 性能
D-1. 多段プルーニングと劣化条件
プルーニングの仕組み自体は 01-architecture.md を参照してください。ここでは劣化する条件を扱います。
performance.md: 「プランニング時に上限・下限でデータファイルをフィルタすることにより、Iceberg はクラスタ化されたデータを使ってタスクを実行せずにスプリットを排除する。場合によってはこれは10倍の性能改善になる」(出典: Strata NY 2018)
劣化条件(仕様から導かれるもの)
field_summaryの範囲が広がる(段1の失効): manifest 内のデータファイルが多数のパーティション値にまたがると lower/upper bound が広範囲になり、どの述語にも「合致しうる」と判定されて manifest スキップが効かなくなります。これがまさにrewrite_manifestsのsort_byが対処する問題です。書き込み順とクエリ述語がずれているテーブルで発生します。列メトリクスが存在しない(段3の失効):
write.metadata.metrics.max-inferred-column-defaultsが 100 なので、101列目以降の列にはデフォルトでメトリクスが収集されません(pre-order 走査順)。ワイドテーブルで後方の列に述語をかけても bounds プルーニングが効かず全ファイル読み込みになります。write.metadata.metrics.column.<col>で個別指定が必要です。truncate(16)による境界値の粗さ: デフォルトは string/binary の境界値を16文字で切り詰めます。長い共通プレフィックスを持つ文字列列(URL、UUID 文字列等)では境界値が実質同一になり、プルーニングが効きません。未知の partition transform: inclusive projection が
trueになるため、そのフィールドでの枝刈りが完全に無効化されます。小ファイル大量発生: manifest エントリ数に比例してプランニングコストが増大します。
D-2. メタデータ肥大とサーバサイド scan planning
performance.md の設計目標は「マルチペタバイトのテーブルでも、テーブルメタデータをふるいにかける分散 SQL エンジンを必要とせず、単一ノードから読める」です。
しかしこの前提はメタデータが単一ノードのメモリ・I/O 帯域に収まる限りで成立します。スナップショットが expire されず manifest が肥大すると前提が崩れ、Spark driver が全 manifest を読む際のボトルネック(および OOM)になります。
expire_snapshots / remove_orphan_files の stream_results オプションが「Spark driver の OOM を防ぐため true 推奨」と明記されているのは、この問題が実在することの公式な証左です。
server-side scan planning はこれへのプロトコルレベルの解です。仕様・実装ともに存在します(03-rest-catalog.md 参照):
- 仕様: 1.7.0(2024-11-08、PR #9695)
- 実装: 1.11.0(2026-05-19、PR #13400 + Spark 統合 #14963)
1.11.0 リリースブログ:
以前は、全てのクライアントがどのファイルを読むか判断するために manifest list と manifest を自分で取得しなければならなかった。server-side planning により、クライアントは関連する scan task のみを受け取り、driver のメモリ圧迫を軽減し、クエリエンジンに透過的なサーバサイド最適化を可能にする。
ただしカタログサーバ側の実装が必須であり、その最適化の質はカタログ実装に依存します。
【未確認】: Spark/Flink 等の各エンジン統合層がどのバージョンで remote scan planning を実際に使うようになるかについて、公式 docs 上の明確な記述は確認できませんでした。「Spark(Iceberg 1.11+)が Scan API をサポート」とする第三者ブログがありますが、公式 docs で裏付けが取れなかったため採用しません。
D-3. ファイルサイズの指針(出典のある数値のみ)
| 数値 | 値 | 出典 |
|---|---|---|
| データファイル目標サイズ | 512MB | write.target-file-size-bytes 既定 |
| delete ファイル目標サイズ | 64MB | write.delete.target-file-size-bytes 既定 |
| Parquet row group | 128MB | write.parquet.row-group-size-bytes 既定 |
| 読み取りスプリット目標 | 128MB | read.split.target-size 既定 |
| ファイルオープン推定コスト | 4MB | read.split.open-file-cost 既定 |
| compaction 書き換え下限 | 目標サイズの 75% | min-file-size-bytes 既定 |
| compaction 書き換え上限 | 目標サイズの 180% | max-file-size-bytes 既定 |
| compaction 発火ファイル数 | 5 | min-input-files 既定 |
| manifest マージ目標サイズ | 8MB | commit.manifest.target-size-bytes 既定 |
| manifest マージ発火数 | 100 | commit.manifest.min-count-to-merge 既定 |
| compaction 例での明示値 | 500MB | maintenance.md の Java 例 |
| S3 Tables compaction 目標 | 512MB(設定範囲 64–512MB) | AWS S3 Tables docs |
公式な推奨値が「存在しない」もの(未確認)
以下は partitioning.md、performance.md、configuration.md を確認しましたが見つかりませんでした:
- 1パーティションあたりの推奨データ量
- 1テーブルあたりの推奨パーティション数上限
- manifest あたりの推奨エントリ数
- スナップショット保持数の推奨値(デフォルト以外)
出典のない数値目安は書かない方針のため、ここは「公式な推奨値は見つからなかった」と記します。 代わりに使える公式由来の指針は上記の「384MB〜922MB のヒステリシス帯」です。
Spark 固有の重要な制約
spark-writes.md(逐語):
Spark で Iceberg にデータを書く際、Spark は Spark タスクより大きいファイルを書けず、ファイルは Iceberg のパーティション境界をまたげない点に注意することが重要である。これは、Iceberg は常に
write.target-file-size-bytesに成長した時点でファイルをロールオーバーするが、Spark タスクが十分に大きくない限りそれは起こらないことを意味する。ディスク上に作られるファイルサイズは Spark タスクよりずっと小さくなる。
→ write.target-file-size-bytes は「上限(ロールオーバー閾値)」であって「保証値」ではありません。 512MB に設定しても、Spark タスクが小さければ小さいファイルしかできません。設定だけで解決しない、実務で最も頻繁に踏まれる誤解です。
D-4. write.distribution-mode の選択
設計上の背景(spark-writes.md 逐語):
Iceberg のデフォルト Spark writer は、各 Spark タスク内のデータがパーティション値でクラスタ化されていることを要求する。この分散は、書き込み中に開いたまま保持されるファイルハンドルの数を最小化するために必要である。 Iceberg 1.2.0 以降、デフォルトで Iceberg は Spark にこの分散に合うようデータを事前ソートすることも要求する。
| モード | シャッフル | ファイル数への影響 | コスト | 位置づけ |
|---|---|---|---|---|
none |
なし | 最悪。手動ソートしないとタスク×パーティションの組み合わせ数だけファイルが生成される | 最安 | 「Iceberg の以前のデフォルト」 |
hash |
ハッシュ交換 | 良好。1パーティションのデータが特定タスクに集約 | 中 | 「新しいデフォルト」(1.2.0〜) |
range |
レンジ交換(2段階) | 良好 + グローバルソート | 最高 | 「テーブルが sort-order 付きで作られた場合にデフォルトで使われる」 |
実務判断のポイント:
noneは「速い」のではなく「Spark に仕事をさせない」だけです。 データが既にパーティション値でクラスタ化されている場合にのみ最適。そうでなければ小ファイルの温床になり、シャッフルコストを払わないぶん、compaction で払うことになります。none+ fanout writer の罠: 「ソートはwrite.spark.fanout.enabledで回避できるが、これは各書き込みタスクが完了するまで全ファイルハンドルが開いたままになる」。「ソートを避ける」代償はメモリです。 パーティション数が多いと executor の OOM に直結します。rangeは sort-order 付きテーブルの暗黙のデフォルト。「より高価だが、ソート列がクエリで使われる場合、グローバル順序は読み取り性能に有益になりうる」。D-1 の段3(列 bounds プルーニング)を効かせたいなら、書き込みコストを払って読み取りの枝刈り効率を買う取引です。AQE との相互作用:
hashとrangeの両方で「Spark の Adaptive Query planning によりタスクのさらなる分割と結合が起こりうる」。distribution-mode だけでファイル数は決まりません。delete/update/merge には別プロパティが独立に存在し、すべて既定未設定です。MoR で delete を多用する場合、これらが未設定のままだと delete ファイルの分散が制御されません。
D-5. MoR と CoW のトレードオフ
| 観点 | CoW(デフォルト) | MoR(v2 position delete) | MoR(v3 deletion vector) |
|---|---|---|---|
| 書き込みコスト | 高。ファイル全体を書き直す | 低。delete ファイルを追加するだけ | 低。DV を書くだけ |
| 読み取りコスト | ゼロ | 高。合致する delete ファイル群を読みマージ | 中。1データファイルにつき高々1つなので照合対象が確定的 |
| メタデータ増加 | スナップショット分のみ | delete ファイルが際限なく蓄積しうる | 蓄積しない(1:1 の上限が spec で保証) |
| 追加メンテナンス | compaction のみ | rewrite_position_delete_files が事実上必須 |
構造的に不要 |
| dangling delete | なし | あり | 緩和(ただし Puffin は残りうる) |
equality delete が最も危険: 「delete ファイルの partition spec が unpartitioned」の場合にも適用対象になるため、グローバル equality delete は全データファイルに対して照合が必要になります。MoR の最悪ケースです。しかも remove-dangling-deletes は公式注記の通りグローバル equality delete には適用されないため、自動的な掃除も効きません。
DV の最大の改善は「不変条件による予測可能性」です。v2 では読み取りコストが「これまで何回 delete したか」に依存して単調悪化しました。v3 ではこれが構造的に不可能になります。定量的改善ではなく漸近的な性質の変化です → 02-table-spec.md
v3 採用の前提: format-version の既定は 2 のままで、v3 にしても write.delete.mode の既定が CoW なので DV は自動では使われません。 エンジン側の v3 対応も前提です → 05-engine-support.md
E. マネージドサービスの自動メンテナンス
C-1. Amazon S3 Tables
「以下のオプションはテーブルバケット内の全テーブルでデフォルトで有効である」
| 操作 | プロパティ | バケット単位 | テーブル単位 | デフォルト | 最小 |
|---|---|---|---|---|---|
| Compaction | targetFileSizeMB |
不可 | 可 | 512MB | 64MB |
| Snapshot management | minimumSnapshots |
不可 | 可 | 1 | 1 |
| Snapshot management | maximumSnapshotAge |
不可 | 可 | 120時間(5日) | 1時間 |
| Unreferenced file removal | unreferencedDays |
可 | 不可 | 3日 | 1日 |
| Unreferenced file removal | nonCurrentDays |
可 | 不可 | 10日 | 1日 |
compaction 戦略(4種): Auto(既定。sort order があれば sort、なければ binpack)/ Binpack / Sort / Z-order。「z-order と sort は binpack より高いコストを発生させうる」
「compaction はオブジェクトを結合する際、テーブルの行レベル delete の効果も適用する」
※ 最重要の落とし穴: Snapshot management が fail-closed する
公式逐語:
Snapshot management は、
metadata.jsonファイルまたはALTER TABLE SET TBLPROPERTIESで設定した retention 値をサポートしない。以下のいずれかの条件が存在する場合、snapshot management はテーブル全体で失敗し、Amazon S3 はスナップショットを一切 expire も削除もしない:
- ユーザー定義のタグまたはブランチ — テーブルにユーザー定義のタグやブランチが存在する場合、snapshot management はテーブル全体で失敗する。これは、そのタグやブランチの retention 期間が短くても適用される。
- Iceberg snapshot retention テーブルプロパティ —
history.expire.max-snapshot-age-msまたはhistory.expire.min-snapshots-to-keepがテーブルプロパティとして設定されている場合、設定値に関わらず snapshot management はテーブル全体で失敗する。
これは極めて重大です。 Iceberg のベストプラクティスに従って history.expire.* を設定する、あるいはリリースタグを打つといったごく自然な運用が、自動メンテナンスをサイレントに全面停止させます。 しかも失敗はストレージ課金の増加としてしか現れません。
→ GetTableMaintenanceJobStatus API による能動的な監視が必須です(失敗時は FAILED ステータスと原因メッセージが返ります)。CloudTrail でも追跡可能です。
その他の制約
- 削除は復旧不能: 「noncurrent オブジェクトの削除は永久的で、これらのオブジェクトを復旧する方法はない」「閲覧・復旧には AWS Support への問い合わせが必要」
- 外部参照は保護にならない: 「テーブル外部からのこれらオブジェクトへの参照は、snapshot management による削除を妨げない」
- 形式・型の制約: compaction は Parquet/Avro/ORC 対応だが既定で Parquet を書く。データ型
Fixed非対応。圧縮brotli、lz4非対応。Parquet の row-group サイズ 128MB が強制される - 楽観的並行の競合は消えない: 「楽観的並行では、ユーザーと compaction のトランザクションが競合してトランザクションが失敗しうる」「compaction ジョブは失敗時にリトライする。パイプライン側でもリトライロジックを使うことを推奨する」
- 【未確認】
rewrite_manifestsに相当する自動操作は、確認した範囲の docs に存在しません。提供されるのは3種のみです。D-1 の manifest レイアウト起因の劣化は S3 Tables では自動的に解決されません
C-2. Databricks Predictive Optimization
自動実行: OPTIMIZE / VACUUM / ANALYZE 対象: Unity Catalog managed tables(Delta Lake および Iceberg)
限界(公式に「サポートしない」と記載):
- 外部テーブル — 対象外
- OpenSharing recipient としてロードされたテーブル — 対象外
- 「Predictive Optimization が実行する OPTIMIZE は ZORDER 操作を実行しない」 — S3 Tables が z-order 戦略を提供するのと対照的
保持期間: VACUUM は delta.deletedFileRetentionDuration を尊重し、既定の窓は7日。
有効化: Premium プラン以上。「2024年11月11日以降に作成されたアカウントではデフォルトで有効」。既存アカウントへの段階的ロールアウトは 2026年8月完了予定(調査時点で進行中)。
【未確認】:
delta.deletedFileRetentionDurationは Delta Lake のプロパティです。UC managed Iceberg テーブルに対する VACUUM の保持期間が同じプロパティで制御されるのか、Iceberg のhistory.expire.*が尊重されるのかは、参照した docs から判別できませんでした。 Iceberg テーブルを Predictive Optimization に委ねる場合、この点は事前検証が必要です。
C-3. マネージド自動メンテナンスの共通の限界
「肩代わり」されるのは compaction と GC のみで、設計判断は肩代わりされません。 パーティション設計、sort order の選択、
write.metadata.delete-after-commit.enabledの初期設定(A-6 の後戻り不能問題)はユーザーの責任のまま残ります。S3 Tables のsort/z-orderが「sort_orderの定義が必須」であることは、この境界を明確に示しています — サービスは実行を代行するが、何でソートすべきかは決めてくれません。楽観的並行の競合はマネージドでも消えません。 AWS が明示的に「パイプライン側でもリトライロジックを使うことを推奨」と書く通り、これは仕様に由来する本質的性質であり、サービス層で隠蔽できません。むしろ自動 compaction が追加のライタとして競合を増やす側面があります。
自動化と Iceberg 標準機能が衝突しうる。 S3 Tables の snapshot management が fail-closed する仕様はその最も鋭い例です。「Iceberg のベストプラクティスに従うこと」と「マネージドサービスの自動メンテナンスを機能させること」が両立しないケースが実在します。
監視は依然として必要。 「サイレント失敗」を検出する手段を運用に組み込まない限り、課金増加として顕在化するまで気づけません。
【未確認】
rewrite_manifests相当は、確認した範囲ではどちらのサービスも自動提供していません。
出典
Apache Iceberg 公式(生ソース)
- Spark Procedures: https://raw.githubusercontent.com/apache/iceberg/main/docs/docs/spark-procedures.md
- Configuration: https://raw.githubusercontent.com/apache/iceberg/main/docs/docs/configuration.md
- Maintenance: https://raw.githubusercontent.com/apache/iceberg/main/docs/docs/maintenance.md
- Performance: https://raw.githubusercontent.com/apache/iceberg/main/docs/docs/performance.md
- Spark Writes: https://raw.githubusercontent.com/apache/iceberg/main/docs/docs/spark-writes.md
- Table Spec: https://raw.githubusercontent.com/apache/iceberg/main/format/spec.md
- 1.11.0 リリースブログ: https://raw.githubusercontent.com/apache/iceberg/main/site/docs/blog/posts/2026-05-19-iceberg-1.11.0-release.md
- Remote scan planning: PR #13400
AWS
- Maintenance for tables (S3 Tables)
- Considerations and limitations
- PutTableMaintenanceConfiguration API
Databricks
採用しなかった情報: 「Spark(Iceberg 1.11+)が Scan API をサポート」等の第三者ブログ(Medium/Dremio、DEV Community、Substack)の記述は、公式 docs で裏付けが取れなかったため採用していません。