レイクハウスのテーブル形式 — Apache Iceberg をメタデータで理解する
Iceberg は自分では何も消さない — 圧縮、失効、孤立ファイル整理
一言でいうと
Icebergは、コミットのたびにファイルとmetadataを増やすだけなので、小さなファイルはコンパクション(rewrite_data_files)でまとめ、古いスナップショットとそれだけが使っていたファイルは期限切れ処理(expire_snapshots)で消し、どのスナップショットも指したことがないファイルは孤立ファイルの整理(remove_orphan_files)で片づけます。3つとも元に戻せないので、順序と余裕が正解です。
なぜ整理が必要なのか
ストリーミングのロードが5分ごとにコミットするテーブルを思い浮かべてください。1日で、コミットが288回、パーティションごとに小さなファイルが数百個、スナップショットが288個、metadata.jsonが288個、たまります。Maintenanceドキュメントが言うとおり、小さなファイルはマニフェストに書くものを増やし、クエリのたびにファイルを開くコストを大きくします。そして、これらのどれも、自然には消えません。
コンパクションをしても容量が減らないことが、最初は不思議に感じられます。コンパクションは、小さなファイルを大きなファイルに書き直す新しいコミットで、古い小さなファイルは、古いスナップショットがまだ指しているので残ります。タイムトラベルを可能にしたまさにその性質です。容量が戻ってくるのは、古いスナップショットを期限切れにしたあとです。
どう動くのか: 3つの別の仕事
| 処理 | 役割 | 元に戻せるか |
|---|---|---|
| rewrite_data_files | 小さなファイルを読んで大きなファイルに書き直し、replaceスナップショットでコミット | コミットなので、ロールバックできる |
| expire_snapshots | 基準より古いスナップショットをmetadataから外し、それだけが使っていたファイルを削除 | できない |
| remove_orphan_files | どのmetadataも指していないファイルを、テーブルの場所から探して削除 | できない |
コンパクションのrewrite_data_filesは、デフォルトの戦略がbinpackで、sort(並べ替えながらの書き直し)もあります。目標サイズtarget-file-size-bytesはテーブルプロパティwrite.target-file-size-bytes(デフォルト512MB)に従い、目標の75%より小さいファイルが、書き直しの候補になります。min-input-files(デフォルト5)は、ファイルがその数だけ集まったグループを、ほかの条件に関係なく書き直させます。結果は、仕様が言うreplaceスナップショットです。テーブルのデータはそのままで、ファイルだけが変わります。
期限切れ処理のexpire_snapshotsは、older_than(デフォルトは5日前)より古いスナップショットを削除しますが、retain_last(デフォルト1)個は残します。ドキュメントは2つのことをはっきり述べています。期限切れ処理は、まだ有効なスナップショットが使っているファイルは決して消さず、ブランチやタグが指すスナップショットも消しません。そのため、戻る必要がある時点にタグを付けておけば、そのスナップショットとファイルは期限切れ処理に耐えます。逆に、期限切れ処理のあとは、そのスナップショットにタイムトラベルできません。
孤立ファイルの整理の場面です。ジョブが失敗したりコミットの競合に負けたりすると、データファイルがどのスナップショットにも入れないまま残ります。remove_orphan_filesは、テーブルの場所のファイルを列挙して、metadataが指していないものを削除します。ここに落とし穴があります。現在使われているファイルも、コミットされるまでは孤立ファイルのように見えます。Maintenanceドキュメントは、書き込みが終わるまでにかかる時間より短い間隔で孤立ファイルを削除すると、進行中のファイルを消してテーブルが壊れることがあると警告し、デフォルトの間隔を3日にしています。ラボイメージのSparkプロシージャは、さらに一歩進んで、24時間より短い間隔を最初から拒否します。まずdry_run => trueで、何が削除されるかを確認することを、習慣にしなければなりません。
metadataファイルも、たまります。テーブルプロパティwrite.metadata.delete-after-commit.enabled(デフォルトfalse)を有効にすると、write.metadata.previous-versions-max(デフォルト100)個だけを残して、最も古いmetadataファイルをコミットのたびに削除します。
現場での姿
順序を逆にした場合です。月末の締めのスナップショットに戻る必要があるとわかるのは、期限切れ処理を動かした翌日です。元に戻す方法はありません。締めやデプロイのような時点では、期限切れ処理の前にタグを付けることを、手順に入れます。
孤立ファイルの整理が、問題のないロードを壊した場合です。誰かが「昨日の分までだけ残そう」と言ってolder_thanを1時間前に短くしたところ、ちょうど時間のかかっていたロードジョブのファイルが消えました。そのジョブがコミットに成功すると、テーブルは存在しないファイルを指します。間隔は、最も時間のかかる書き込みよりも余裕を持たせます。
整理処理がテーブルを揺さぶる場合です。コンパクションもコミットなので、ロードと競合します。ロードが少ない時間に動かし、失敗したら次の周期でやり直せば済みます。
実務で本当に大切なこと
- コンパクションは今を速くし、期限切れ処理は容量を返します。コンパクションだけでは容量は減りません。
- 期限切れ処理の前にタグを付けます。タグやブランチが指すスナップショットは、期限切れ処理が消しません。
- 孤立ファイルの整理の時間的な余裕は、縮めません。デフォルトは3日で、まずdry_runを使います。
- 3つを決まった順序で定期的に動かします。タグ → コンパクション → 期限切れ処理 → 孤立ファイルの整理です。
次のラボですること
小さなバッチ30個を、コミット30回で入れて、小さなファイルとスナップショットの山を作り、整理前の数字を記録します。10回目のコミットにタグを付けてからrewrite_data_filesでコンパクションし、expire_snapshotsで現在のスナップショットだけを残しながら、タグが守ったスナップショットとファイルが生き残ることを確認します。古い孤立ファイルと、作ったばかりのファイルを1つずつ置いて、remove_orphan_filesをdry_runから動かし、古いものだけが削除されることを確認したあと、整理後にディスク上のファイル数が現在のスナップショットのファイル数より多い理由を説明します。