レイクハウスのテーブルフォーマット:Iceberg・Delta・Hudi
安いオブジェクトストレージ上で更新もタイムトラベルも効く理由が分かります。Iceberg/Delta/Hudi がメタデータ階層とスナップショット分離で ACID をどう実現するかを対比します。
- 1.3 形式とも、不変なデータファイル(多くは Parquet)を直接書き換えず、どのファイル群が現在のテーブルかを指すメタデータを差し替えることで ACID とタイムトラベルを実現する。
- 2.コミットは新しいスナップショットを作る操作で、読み手は開始時点のスナップショットだけを見るためスナップショット分離が成り立つ。並行更新はメタデータ・ポインタへのアトミックな入れ替え(楽観的並行制御)で直列化する。
- 3.Iceberg はマニフェスト階層でファイルをトラッキング、Delta は順序付きトランザクションログ、Hudi は主キー基盤の Copy-on-Write/Merge-on-Read という設計の重心が異なる。
なぜオブジェクトストレージで ACID が難しいのか
データレイクの実体は、S3 や GCS のような オブジェクトストレージに置かれた大量のファイル(多くは 列指向 の Parquet)です。安価で無限にスケールする一方、リレーショナル DB が当然のように持つ性質を欠きます。
- アトミックな複数ファイル更新がない: 100 個の Parquet を書き換える途中でジョブが落ちると、中途半端な状態が見えてしまう。
- 一覧(LIST)が即時に整合しない場合がある: 「ディレクトリ内の全ファイル=テーブル」という素朴な定義だと、書き込み途中のファイルや消し残しが読み手に混入する。
- 行単位の更新・削除がそもそも想定外: オブジェクトは原則 immutable で、追記や部分書き換えに向かない。
レイクハウスの テーブルフォーマット(Apache Iceberg・Delta Lake・Apache Hudi)は、この生のファイル集合の上に メタデータ層 をかぶせ、「いまのテーブルを構成するファイルはどれか」を明示的に管理することでこれらを解決します。狙いは データウェアハウス 並みの一貫性を、レイクの安さとオープンさのまま得ることです。
共通原理:不変ファイル + 差し替え可能なメタデータ
3 形式に共通する設計の核は次の通りです。
- データファイルは不変: 既存の Parquet を書き換えない。更新・削除は「新しいファイルを書き、メタデータで旧ファイルを論理的に外す」で表現する。
- テーブル状態=メタデータが指すファイル集合: テーブルの中身はディレクトリの実在ファイルではなく、メタデータが列挙するファイル集合(例
{f1, f2, f5})として定義される。 - コミット=スナップショットの生成: 書き込みは、新しいデータファイル群と、それを含む 新しいスナップショット(あるバージョンのファイル集合)を作り、テーブルの「現在地ポインタ」をそこへ進める操作。
- アトミック性は単一ポインタの入れ替えに帰着: 多数のファイル更新を、最終的に 1 箇所のメタデータ参照のアトミックな差し替え に集約する。これが成功して初めてコミットが可視化される。
この「不変ファイル + ポインタ差し替え」は、ストレージエンジンにおける LSM-Tree の追記思想や、MVCC のバージョン管理と発想を共有しています。古い版を消さずに残すからこそ、後述のタイムトラベルが自然に成立します。
現在のスナップショットを指すポインタを安全に入れ替えるには、その一手をアトミックにできる仕組みが要ります。Iceberg では Hive Metastore・AWS Glue・Nessie などの カタログ がテーブルの「最新メタデータの場所」を保持し、compare-and-swap(CAS)で更新します。Delta は後述のログの「次の番号のファイルを最初に作れた者が勝つ」(put-if-absent)で代替します。ポインタ入れ替えのアトミック性をどこが保証するか、が形式ごとの要になります。
スナップショット分離とタイムトラベルの原理
読み手は クエリ開始時点で「現在」を指していたスナップショット を 1 つ確定させ、その後ずっとそのファイル集合だけを読みます。クエリ実行中に別の書き込みが新スナップショットを作っても、読み手のファイル集合は変わりません。これが スナップショット分離(Snapshot Isolation) で、読み手と書き手が互いをブロックしないのは、両者が別バージョンのファイル集合を見ているからです。RDB の MVCC が行バージョンで実現するのと同じ理屈を、ファイル集合のバージョン で行っているわけです。
旧スナップショットを破棄せず保持しておけば、過去バージョンをそのまま読み直せます。これが タイムトラベル で、典型的には次のように指定します。
-- Iceberg:スナップショット ID やタイムスタンプで過去を読む
SELECT * FROM sales FOR VERSION AS OF 3821550127947089060;
SELECT * FROM sales FOR TIMESTAMP AS OF '2026-06-20 09:00:00';
-- Delta:バージョン番号で過去を読む
SELECT * FROM sales VERSION AS OF 42;
タイムトラベルは「過去を再現する特別な機能」ではなく、スナップショットを消さない設計の自然な帰結 だと捉えると見通しが良くなります。
保持されるスナップショットが増えるほど、参照されなくなった旧データファイルがストレージに溜まります。各形式は不要ファイルを物理削除する保守処理(Iceberg の expire snapshots/Delta の VACUUM/Hudi の cleaner)を持ちますが、これを走らせると その分のタイムトラベル可能範囲は失われます。保持期間とコストはトレードオフであり、進行中の読み取りより新しい版を消さないよう、保持期間は安全側に設定します。
並行コミットの直列化:楽観的並行制御
複数の書き手が同時にコミットしようとしたらどうなるか。3 形式とも基本は 楽観的並行制御(OCC) です。各書き手は「自分が読んだ基底スナップショット」を覚えてデータを書き、最後にポインタを進めます。このとき、自分が読んだ後に誰かが先にコミットしていないか を検査します。
- 現在のスナップショット
S_nを読み、新データファイルを書く。 - テーブルの最新がまだ
S_nであることを条件に、S_{n+1}への差し替えをアトミックに試みる。 - 先に別の書き手が
S_{n+1}を作っていたら、自分の試みは失敗。最新を読み直して リトライ(書いたデータファイルは再利用し、メタデータだけ作り直す)。
衝突検知の粒度が重要です。単純な「最新スナップショット一致」だけだと、無関係なパーティションへの並行書き込みでも片方が落ちます。そこで各形式は、追加・削除されたファイルやパーティションが 実際に重なるか を検査し、重ならなければ両方を活かす方向に作られています。とはいえ、同じデータに対する Update/Delete が交差すると一方は失敗するため、書き手のリトライ設計は実運用の勘所です。
3 形式のアーキテクチャ比較
メタデータ層の組み立て方に、各形式の設計思想がはっきり出ます。
| 観点 | Apache Iceberg | Delta Lake | Apache Hudi |
|---|---|---|---|
| メタデータの形 | メタデータファイル→マニフェストリスト→マニフェスト→データファイルの階層 | _delta_log の順序付きトランザクションログ(JSON + 定期チェックポイント) | タイムライン(コミット系列)+ メタデータテーブル |
| コミットの実体 | 新メタデータファイルを書き、カタログのポインタを CAS で進める | 次番号のログファイルを put-if-absent で作成(先着が勝つ) | タイムライン上の instant をアトミックに publish |
| 更新/削除の方式 | Copy-on-Write と Merge-on-Read(位置/等値の delete file) | Copy-on-Write(deletion vector で MoR 的な削除も可) | Copy-on-Write と Merge-on-Read を主キー前提で両対応 |
| 設計の重心 | エンジン非依存の仕様とパーティション進化 | ログ中心の単純さと Spark との密結合(現在は仕様公開) | 主キー upsert とインクリメンタル取り込み |
| タイムトラベル | スナップショット ID / タイムスタンプ | バージョン番号 / タイムスタンプ | コミット instant(タイムライン) |
Iceberg:マニフェストの階層でファイルを畳む
Iceberg のスナップショットは マニフェストリスト(複数マニフェストへの参照)を指し、各 マニフェスト が個々のデータファイルのパスと、各ファイルの列ごとの最小値・最大値・件数などの統計を持ちます。この統計を使い、クエリ条件に合わないファイルをマニフェスト段階で丸ごと枝刈り(プルーニング)できます。LIST に頼らずファイルを列挙できるため、大規模テーブルでもプラン生成が速いのが利点です。パーティションの定義を後から変えても過去データを書き直さずに済む パーティション進化 も、この間接層があるからこそ成立します。
Delta Lake:順序付きトランザクションログ
Delta はテーブル直下の _delta_log/ に 00000000000000000000.json, ...0001.json と 連番のログファイル を積みます。各ログは「このファイルを追加(add)」「このファイルを削除(remove)」というアクションの列で、現在のファイル集合は ログを順に畳み込んだ結果 です。ログが増えると再生が重くなるため、一定間隔で全状態を Parquet の チェックポイント に書き出して読み始めを早めます。コミットは「次番号の .json を最初に作れた書き手が勝つ」で直列化され、これが put-if-absent によるアトミック性の正体です。
Hudi:主キー upsert に最適化
Hudi は各レコードに レコードキー を要求し、キー単位の upsert/delete を一級市民として扱います。書き込みモードが 2 つあります。
- Copy-on-Write(CoW): 更新対象を含むファイルを読み、新しい値を反映した新ファイルを書き出す。読み取りは単純(最新ファイルを読むだけ)だが、少量更新でもファイル全体を書き直すため書き込みコストが高い。
- Merge-on-Read(MoR): 更新を行単位のログ(delta log)に追記し、読み取り時にベースファイルとログを マージ して最新像を作る。書き込みは軽いが読み取りでマージが要る。
これは LSM-Tree と同じ「書き込み増幅か読み取り増幅か」のトレードオフであり、Hudi が CDC やストリーミング取り込み(ログベース CDC の落とし先)で好まれる背景です。
Copy-on-Write は読み取りを最速にする代わりに更新ごとにファイルを書き直すので 書き込み増幅 が大きい。Merge-on-Read は更新を追記で軽くする代わりに読み取り時のマージという 読み取り増幅 を払う。更新頻度が低く分析読み取りが主なら CoW、頻繁な upsert やレイテンシ重視の取り込みなら MoR、という使い分けになります(Iceberg/Delta の Copy-on-Write/Merge-on-Read もこの軸で理解できます)。
効く範囲を見誤らない
テーブルフォーマットがアトミック性・分離性を保証するのは、原則として 1 テーブルへの 1 コミット の範囲です。複数テーブルにまたがる分散トランザクション(2 相コミット 相当)は標準機能ではありません。また、外部の保守処理が走るタイミングや、複数の独立した書き込みエンジンが同じテーブルを更新するケースでは、カタログ/ログのアトミック性をどのコンポーネントが担保するかを正しく構成しないと、コミットの取りこぼしや競合が起きえます。「ファイルを置くだけ」で安全になるわけではなく、ポインタ入れ替えのアトミック性を保証する層(カタログやストアの put-if-absent)が要であることを忘れないでください。
共通原理=不変データファイル + メタデータ・ポインタのアトミックな差し替えを即答できるように。スナップショット分離は「読み手が開始時点のファイル集合だけを見る」ことから、タイムトラベルは「旧スナップショットを消さない設計の帰結」から説明する。並行コミットは 楽観的並行制御 + リトライ。形式の違いは Iceberg=マニフェスト階層、Delta=順序付きログ、Hudi=主キー upsert(CoW/MoR)と対応づけて覚えるのが定石です。
まとめ
Iceberg・Delta・Hudi は、安価なオブジェクトストレージ上の 不変ファイル集合 に メタデータ層 をかぶせ、テーブルの実体を「メタデータが指すファイル集合」と定義することで ACID を実現します。コミットは新スナップショットの生成と ポインタのアトミックな差し替え に帰着し、読み手は開始時点のスナップショットだけを見るため スナップショット分離 が成り立ち、旧スナップショットを残すことが タイムトラベル になります。並行更新は 楽観的並行制御 で直列化します。違いはメタデータの組み立て方(マニフェスト階層/順序付きログ/タイムライン)と更新方式(CoW/MoR)にあり、ワークロードの読み書き比率で選び分けるのが要点です。
データベース Article
レイクハウスのテーブルフォーマット:Iceberg・Delta・Hudiを実務で読む
TL;DRは入口です。実際に選ぶ・使う段階では、何を解決するか、何と比較するか、導入後にどこで詰まるかまで見る必要があります。
解決すること
レイクハウス
比較で見る軸
難易度: advanced / カテゴリ: データベース / タグ数: 6
導入後に効く点
コミットは新しいスナップショットを作る操作で、読み手は開始時点のスナップショットだけを見るためスナップショット分離が成り立つ。並行更新はメタデータ・ポインタへのアトミックな入れ替え(楽観的並行制御)で直列化する。
先に潰すリスク
用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。
- 難易度
- advanced
- カテゴリ
- データベース
- タグ数
- 6
判断チェックリスト
- 自社の用途が「レイクハウス / Iceberg」に近いか確認する。
- 強みである「3 形式とも、不変なデータファイル(多くは Parquet)を直接書き換えず、どのファイル群が現在のテーブルかを指すメタデータを差し替えることで ACID とタイムトラベルを実現する。」が本当に評価軸になるか確認する。
- 注意点の「用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。」を運用で吸収できるか確認する。
- 公開値や仕様値は、対象プラン・対象機種・対象リージョンまで確認する。
- 既存システム、ID、ネットワーク、監視、バックアップとの接続方法を先に洗い出す。
- 小さく試してから、本番移行、権限設計、障害時手順、コスト監視を決める。