はじめに
Open Table Format、Apache Iceberg、名前は聞くけれど、結局それが何をしてくれるものなのか。構成要素の名前——catalog / metadata / manifest list / manifest / data file——を並べられると、急に複雑に思えて身構えてしまいます。
この記事は、これからOpen Table FormatやIcebergに触れる人の 「はじめの一歩」 としてまとめたものです。細かい機能や仕様を網羅することではなく、"どういうものか"の解像度を上げることを目的にしています。
Open Table Formatが初見の方でも読み進めやすいよう整理していますので、基礎を押さえるきっかけとして、少しでも参考になれば嬉しいです。
この記事で分かること
- なぜ今、Open Table Formatが必要なのか — AI活用に向けたデータ基盤に求められる「1つのデータを、複数のエンジンで共有する」というニーズと、その背景
- レイクハウス(Lakehouse)が重要になっている理由 — データレイクの低コスト・開放性と、DWHの管理性・信頼性を両立する構成。その中核がOpen Table Format
- 「保存できる」と「使える」の差 — 置いてあるファイルを、エンジンが"テーブル"として読み書きできるようにするには何が要るのか
- Apache Icebergの特徴と、仕組みの基礎 — catalog・metadata・snapshot・commitの役割と、データの一貫性やTime travel・スキーマ進化を実現する設計
- その仕組みが、実際にどう動くのか — 複数エンジンから同じテーブルを読み書き、Time travelを試すデモ。更新中の読み取り一貫性とTime travelを実機で試し目で見て確認。
- AI活用のイメージ — 同じIcebergテーブルを、Time travel × ベクトル検索 × 生成AIの分析に使う例
書き方について:この記事は「まず全体の輪郭をつかむこと」を優先しているので、わかりやすさを重視している表現や、細部を省いている箇所があるかもしれません。実務に向けた正確な情報が必要な際には、参考に載せている一次情報をご確認ください。
目次
内容が多めなので、気になる項目からどうぞ!
- 第1章:なぜ今、Open Table Formatなのか
- 第2章:Open Table Formatが解決する課題
- 第3章:Apache Icebergの仕組み — 4つの問いで理解する
- 全体アーキテクチャ — 構成要素は5種類
- 実体 — CREATE TABLE → INSERT → INSERT で何が増えるか
- Q1. 複数エンジンは、どうやって同じテーブルを見つける?
- Q2. どのファイルが"今のテーブル"?
- Q3. 更新中の未完成データを、なぜ読み手は読まない?
- Q4. 過去のデータをどう再現する?
- 4つの問いに出てこなかった、もう2つの柱
- 第4章:デモ — 3つのエンジンで同じテーブルを触ってみた
- 第5章:オープンな基盤から、AIへ
- まとめ
- おわりに
- 参考
第1章:なぜ今、Open Table Formatなのか
技術の話に入る前に、"なぜ今これが必要になったのか"から始めます。
背景を押さえておくと、この後に出てくる用語や仕組みが「よく分からない言葉の集まり」にならず、つながりを持って理解しやすくなります。
従来のデータ活用 — 集めて、決まった人が、決まった用途で使う
出発点として、AI以前の"定番の形"を確認しておきます。
企業の中には、業務DB、ERP、CRMやSaaS、ログ、ファイルと、たくさんのデータソースがあります。従来のデータ活用は、これらを DWHやデータマートに集約して、経営層向けのレポート、営業、マーケティングといった利用者がBIやレポートで使う、という形でした。
この構成には、特徴があります。データ活用の単位がはっきりしており、比較的明確だったことです。業務処理のトランザクショナルなデータはSoR(System of Record)、つまり業務データベース。それらを分析に使うなら、集計やレポートはDWH。生データやログはデータレイク。それぞれの担当が明確で、データの使い道もBI・レポート・バッチ処理くらいに限られていました。
AIで変わったこと① 扱いたいデータが増える
ところが生成AIが登場して、まず入り口が変わります。
これまで分析対象になるのは、業務DBやERPのような構造化データが中心でした。そこに、センサーやIoT、画像、音声、Webアクセスログ、手書きやFAXの書類まで——「AIに読ませたい」対象として、半構造化・非構造化データが一気に加わります。議事録も契約書もコールセンターの応対履歴も、全部候補になり得ます。生成AIの活用を前提にすると、今まで見えていなかった利用用途が視野に入り、それらがどんどん拡大していきます。
データの種類が増えるだけなら、従来の「集める」の延長で対応できるかもしれません。さらに重要となる変化は、次にあります。
AIで変わったこと② 同じデータを読むエンジンが増える
出口側です。同じデータを読みたがる処理エンジンが、一気に増えました。
これまでのBIやSQL分析だけでなく、SparkのETLや特徴量作成、機械学習のモデル学習、Trinoでの横断アドホック分析、RAGやベクトル検索。これからはAIエージェントも読みに来ます。かつて「分析」といえばBIとバッチ処理が中心だったことを踏まえると、データを読む主体は大きく多様化しています。
この読み手の急増こそが、AI時代のデータ基盤における大きな変化です。 この記事も、この変化を前提に話を進めていきます。
現場で実際に起きること — エンジンごとにコピーが増える
では、1つのデータをこれだけ多くのエンジンが読みたがると、現場では何が起きるか。
多くの場合、特にこれまでのアプローチでは、活用目的ごとにコピーを作って配ることが一般的でした。DWHからデータを抜き出して、ある部門の分析用に利用できる形にする。Spark用にも新たに作る。機械学習用にも作る。エンジンが増えるたびに、同じデータのコピーが1つ、また1つと分岐していきます。
データソースは1つのはずなのに、下流でコピーがどんどん枝分かれする。小規模なら成立する場合もありますが、データや利用用途が増えていくほど、現実的ではなくなっていきます。
「数字が合わない」の正体
コピーを増やすほど、「同じデータ」のはずが、だんだん同じでなくなっていきます。管理の負担を整理すると、4つに集約されます。
| 負担 | 何が起きるか |
|---|---|
| ** 鮮度** | どれが最新か分からない。コピーには時間差があるので古い数字を見てしまう |
| コスト | 保管・転送・ETL維持が積み上がり続ける |
| ガバナンス | 権限やマスキングの基準がコピーごとにバラつく |
| 再現性 | "あの日のデータ"をもう一度作れない。数字の根拠を求められても答えられない |
例えば、別の部署が別のエンジンで同じ対象を集計しているのに、一方は朝8時時点、他方は10時時点のデータを見ている。同じ指標のはずなのに結果が食い違う——参照しているデータの時点がズレています。
現場ではこうした「数字が合わない問題」がしばしば起こりますが、その原因のひとつが、このコピーの分岐です。
そしてこれは、個々の現場の運用がまずいという話ではありません。後ほど紹介する「レイクハウス」の分野で代表的な論文(Armbrustら "Lakehouse"、CIDR 2021)でも、同じ課題が指摘されています。
では全部DWHに集めれば? — 「集める」の限界
コピーが問題なら、全部を1つのDWHに集めればいいのでは、と思うかもしれません。
DWHそのものは、今も有効な選択肢です。ただ、あらゆるデータと用途を受け止める"唯一の置き場"とするには、限界が見えてきました。
- コスト — ログやセンサーデータのような何百GB〜テラバイト級のデータまで入れようとすると、コストが膨らむ
- データの種類 — 半構造化データや機械学習用データなど、DWHが得意としない種類のデータが増えてきた
- 処理の偏り — 処理が特定のDWHエンジンに寄りやすくなる
- コピーの再発 — 別のエンジンで使うにはデータを持ち出すことになり、結局コピーが増える(先ほどの問題に戻る)
- ロックイン — 一度その基盤に乗ると、将来ほかの製品へ移りづらくなる
先ほど触れた "Lakehouse" 論文(Armbrustら、CIDR 2021)も、DWH+データレイクの二層構成の課題として 信頼性・鮮度・二重コスト・ロックイン を挙げています。
✓ ここまでの整理
AIがデータ基盤に持ち込んだ要求は、2つでした。
- もっと多様なデータを扱いたい — 画像・ログ・文書・音声までAIの入力になる。置き場は「安くて無限に置ける」クラウドストレージが適する
- 同じデータを、多くのエンジンで使いたい — BI・Spark ETL・ML学習・RAG…読み手が一気に増えた
大事なのはここです。コピーの増殖は"原因"ではなく"結果"。 ニーズに素直に応えた結果、コピーが増えている。
つまり問いはこうなります。コピーを増やさずに、1つのデータのまま多くのエンジンから使うには、どうするか。
目指す形 — データは動かさず、エンジンが読みに来る
ここでどうしたいのかというと、コピーを配るのをやめて、置き場を1つにして、いろんなエンジンの側に読みに来てもらう。 このような発想が出てきます。
Object Storageにデータを1つだけ置き、SQLもSparkもTrinoも、同じ場所に読みに来る形です。利点は3つあります。
- ① 安価に大量に置ける
- ② コピーを配らなくていい
- ③ どのエンジンから触っても不整合が起きない。
そして、このObject Storageの上でファイル群を"テーブル"として管理する層として出てくるのが、Open Table Format——その代表格がApache Icebergです。
データの置き場は1つに集約し、それを利用するエンジンは自由に選べる——これが目指す構成です。
この構成の名前 — レイクハウス
オブジェクト・ストレージの上をテーブル管理の層が支え、多様なエンジンが同じデータを利用する——この構成には名前があります。レイクハウスです。
さきほど引用したCIDR 2021の論文が、まさにこのレイクハウスを提唱した論文でした。データレイクの開放性・低コストと、DWHの信頼性・管理性を両立させる考え方で、その管理層の実装こそがOpen Table Formatです。
位置づけ — 置き換えではなく、共有レイヤー
ここは誤解が生まれやすいポイントなので、先に整理しておきます。
Icebergは、SoRやDWHをすべてを対象にすべてを置き換えるようなものではありません。
SoRは「記録する」領域です。業務トランザクションの正本を管理し、業務整合性や厳格な更新制御が求められる。これは今後も必要です。
DWHは「分析する」領域で、整備されたデータの高速な集計・参照、BIやダッシュボードに強い。
データレイクは「保持する」領域で、低コストに大量データを保存できる。
Open Table Formatは、その下に敷く 「状態を管理する」共通レイヤー です。DWHもSparkも同格の"利用者"として乗れる土台を足す、が正しい理解です。
ここまでで「置き場を1つにして、エンジンの側が読みに来る」という形までたどり着きました。
では、Object Storageにファイルを置けば、それで終わりでしょうか。
保存はできます。ただ、それを別々のエンジンが"テーブル"として読み書きできるかは、別の話です。 次の章は、この2つの間にある距離の話です。
第2章:Open Table Formatが解決する課題
「保存する」と「エンジンが読める」の間には、距離がある
Object Storageを置き場にすることには、明確な利点があります。大容量でも安価に保存できて、容量計画から解放される実質無限のスケールがあり、複製や冗長化はサービス任せにでき、HTTPでどこからでもアクセスできる。
ただ、これらはあくまで"保存"の利点です。では、置いたデータをエンジンから利用しようとすると、何が起きるでしょうか。
人間がバケットの中を覗けば、「このフォルダが注文データで、たぶんこのファイル群でひとまとまりだな」と見当がつきます。
ファイル名や更新日時から、なんとなく察しがつく。
でも、SQLを投げてくるエンジンには、その"察し"がありません。エンジンが動くために必要なのは、こういう情報です。
- どのファイルの集まりで、1つの「注文テーブル」になるのか
- そのうち、いま読んでいいのはどれか(さっき書き換えられた途中のものが混じっていないか)
- 列は何で、型は何か
- 先週の状態を読みたいとき、どのファイルを開いてを読めばいいのか
Object Storageが答えられるのは「このprefixの下に、こういう名前のオブジェクトがあります」までです。構造も、状態も、履歴も持っていません。
さらに、置き場としての操作特性も制約になります。Object Storageのオブジェクトは部分的な書き換えができないため、更新はファイル全体の置き換えになります。リネームも内部的にはコピーです。ファイル一覧の取得(LIST)にも時間がかかります。ファイルシステムなら当たり前にできることが、クラウドのObject Storageでは重い操作になります。
この違いは、この後見るHive方式の限界に直結します。
つまり、"保存"は十分に担われている一方、ファイル群をテーブルとして解釈するための情報と仕組みが欠けています。この章では、それを何が補うのかを整理します。
全体像 — データ分析スタックの5層
用語が増えてくるので、先に全体像を整理しておきます。データ分析のスタックは5層。
- クエリエンジン — 計算。データの読み書き・集計を実行する
- カタログ — 名前解決。テーブル名から現在の場所を引く
- テーブルフォーマット — テーブルの定義。ファイル群を1つのテーブルとして扱う
- ファイルフォーマット — 中身の持ち方。1ファイルの中のデータの格納形式
- ストレージ — 置き場。ファイルを保存する
この記事の中心テーマは、真ん中のテーブルフォーマット層です。クエリエンジンとの読み書きを仲介したうえで、メタデータの管理や効率的なデータ操作を担う位置づけになります。以降の章で扱う内容は、いずれもこの層の中の仕組みの話です。
その前に、1つ下のファイルフォーマット層に少しだけ触れておきます。「どこまでを下の層がやってくれて、どこからが空白なのか」がはっきりするからです。
Parquetが管理できるのは「ファイル1個の内側」まで
ファイルフォーマットの代表例が、Parquetです。
CSVのような一般的な形式は行指向で、1行ずつ順番にデータを格納します。業務アプリケーションのように「1件のレコードをまるごと読み書きする」用途には、この形が適しています。一方、分析では「国名の列だけを全行分集計したい」のように、特定の列だけを大量に読む使い方が中心になります。行指向のままでは、1列だけ必要な場合でも、すべての列を読み込むことになります。
Parquetはこれを逆にした列指向の形式で、データを列ごとにまとめて格納します。必要な列だけを読み取れるうえ、同じ種類のデータが連続して並ぶため、圧縮も効きやすくなります。この2点が、分析用途でParquetが広く使われている理由です。
もう一つのポイントが、ファイルの中に列のmin/max(最小値・最大値)といった統計を持っていることです。これがあると、ファイルを開く前に「このファイルに欲しいデータが入っているか」を判断できます。「東京エリアのデータが欲しい」というとき、統計を見て範囲外だと分かれば、そのファイルは開かずに飛ばせるわけです。
ただし——ここが本題につながるところですが——これはあくまで**「1ファイルずつ見れば」の話**です。統計を持っているのも、その1ファイルの中の話にとどまります。Parquetが管理できるのは、ファイル1個の内側まで。 たくさんのファイルを"群"としてまとめて、「今どれが正しいテーブルなのか」を束ねる仕組みは、この層にはありません。
では、その"束ねる仕組み"が無いと何が起きるのか。
ファイル群を"束ねる仕組み"が無いと、整合性が崩れる
10個のファイルから成るテーブルを書き換えるとします。対象のデータが多ければ修正はすぐには終わらず、3個だけ書き換え済み・残り7個はこれから、という瞬間が必ず出てきます。
そこに、更新中とは知らない別のエンジンが読みに来ると——新しい3ファイルと古い7ファイルの混合を読んでしまいます。件数も合計金額も、過去にも未来にも存在しない値になる。
ディレクトリには「書き込み中」という札を出せません。読み手は今そこにあるファイルを素直に読むだけ。「半分だけ新しい世界」が観測されてしまうんです。
足りない4つ = OTFが足すもの
さきほど挙げた「エンジンが動くために必要な情報」を、課題として整理すると4つになります。
| 欠落 | 具体的には |
|---|---|
| ① 一貫性 | 更新途中の不整合が見える/複数書き手・読み手の整合が取れない |
| ② 現在と履歴 | どのファイル集合が"今の正"か分からない/過去の状態・変更履歴を保持できない |
| ③ 変化への対応 | スキーマ変更(列の追加・改名)が危険/パーティション構成を後から変えられない |
| ④ 発見と権限 | テーブル名・所在を引く台帳がない/アクセス権限を一元管理できない |
これらは一見、別々の課題ですが、いずれも「ファイル群をテーブルとして管理する機能が無い」ことに起因しています。そのため、この4つをまとめて担う層があれば、ファイルの集まりを"テーブル"として扱えるようになります。
そして、この4つを提供するレイヤーこそがOpen Table Formatです。
第3章以降は、この4つをIcebergがどう埋めるかの話になります。
原点 — Hiveテーブル形式は何を切り開き、なぜ限界が来たか
Icebergの設計の理解を深めるうえで、前身にあたるHiveテーブル形式に少しだけ触れておきます。Hive形式の課題を改善する形で、Icebergをはじめとする新しいOpen Table Formatが開発されているためです。
Hiveは2009年ごろに開発されたフレームワークです。当時、大量のデータはHadoopという分散処理の仕組みの上にあって、活用するには複雑なJavaプログラムを書く必要がありました。Hiveを使うと、普通のSQLからそのプログラムへ変換してくれる。いわば "通訳"の役目を果たしました。
このときHiveが採用したのが、「ディレクトリ=パーティション」 という仕組みです。
sales/
year=2025/part-0001.parquet
year=2026/part-0001.parquet
year=2025 というフォルダを作ると、Hiveはそれをパーティションとして解釈します。「2025年のデータが欲しい」というSQLには、そのフォルダだけ読めばいいと判断できる。フォルダの形が、そのままテーブルの形を表しているわけです。パーティションの場所はメタストア(RDB)に登録し、個々のデータファイルまでは追跡しません。
この方式はビッグデータのSQL化を切り開きました。ただ、設計の前提はHDFS時代のものでした。テーブルの状態がメタストアと実ファイルの2箇所に割れてズレうる。クエリのたびに大量のLIST操作が走る。WHERE dt=… を人が書き忘れるとフルスキャンになる。そして何より——テーブルの姿がフォルダで決まるということは、更新のたびにフォルダを差し替える必要があるということです。
この差し替えは、ファイル名を付け替える"rename"操作が、瞬時かつ安全に(アトミックに)完了することを前提にしていました。HDFSのようなファイルシステムでは、この前提が成り立ちます。しかし、クラウドのオブジェクトストレージにはアトミックなrenameが存在せず、リネームは実質的にコピーと削除の組み合わせになります。ストレージの主流がクラウドへ移るなかで、フォルダに頼る設計は前提から成り立たなくなりました。
この章の冒頭で、「エンジンには、どのファイルの集まりが1つのテーブルなのか分からない」という課題を挙げました。これに対するIcebergの答えが、テーブルに属するファイルを、フォルダという"場所"で暗黙に決めるのではなく、メタデータに"一覧"として明示的に記録する、という考え方です。
こうすると、更新は「新しい一覧を書いて切り替える」ことになり、ファイルのrenameに頼る必要がなくなります。
第3章で見る仕組みは、この延長にあります。
Open Table Formatの主要な3実装 — Iceberg / Hudi / Delta Lake
この課題に最初期に向き合った企業のひとつが、100PB級のデータをS3上で運用していたNetflixでした。
また、2016年から2019年ごろにかけて、そのほか2つの会社がそれぞれの課題に合わせてOpen Table Formatを生み出しました。
| 年 | 誰が | 課題 | アプローチ | オープンソース化 |
|---|---|---|---|---|
| 2016 | Uber → Apache Hudi | 配車データを準リアルタイムでデータレイクに反映したい | 行レベル更新(upsert)と増分処理の先駆 | 2019年にApacheプロジェクト入り |
| 2017 | Netflix → Apache Iceberg | 100PB級のS3データで、Hiveの正確性・スケール限界を解決したい | ファイル単位の追跡+変更をまとめて1回でcommit | 2018年にASFへ寄贈 → 2020年にトップレベルプロジェクト |
| 2019 | Databricks → Delta Lake | バッチとストリーミングを1つの信頼できるテーブルで扱いたい | トランザクションログ(_delta_log)方式 |
2019年にOSS化 → のちLinux Foundation傘下 |
優先課題が、そのままフォーマットの個性になっています。ただし、解こうとした本質は3つとも同じ——ファイル群にテーブルの信頼性を与えることです。いずれも開発元はありますが、現在はすべてオープンソースとして公開されています。
3つとも現役で、それぞれの出自に強みがあります。そのうえで、この記事では主に次の2つの点から、Icebergを取り上げています。
1つ目は、ベンダー中立性です。Icebergの開発はNetflixで始まりましたが、表のとおり3つの中では最も早い2018年にApache Software Foundationへ寄贈され、以来、特定の1社ではなく複数の企業が参加するコミュニティ主導で開発が続いています。仕様も実装もオープンであり、特定の製品戦略に依存していません。
2つ目は、エンジン対応の広さです。Spark、Trino、Snowflake、BigQueryといった主要なエンジンやクラウドが横断的にIcebergの読み書きに対応しており、Oracle Autonomous AI Databaseでも対応が進んできています。第1章で見たとおり、この記事の前提は「同じデータを、多くのエンジンで使いたい」でした。その観点から見ると、どのエンジンからも扱える中立なフォーマットであることは、この記事のテーマと相性の良い性質です。
では早速、次の章からIcebergを例にOpen Table Formatの仕組みを深掘りしていきます。
第3章:Apache Icebergの仕組み — 4つの問いで理解する
いよいよ、Icebergの仕組みに入ります。
この章では、まず構成要素の"名前"と"実体"を確認し、そのうえで次の4つの問いに1つずつ答えていきます。全体の構造を先に押さえておくと、個々の機能はその構造の説明として読めるようになります。
- Q1. 複数エンジンは、どうやって同じテーブルを見つける?
- Q2. どのファイルが"今のテーブル"?
- Q3. 更新中の未完成データを、なぜ読み手は読まない?
- Q4. 過去のデータをどう再現する?
その前に — OTFとIcebergの関係
基本的な確認ですが、ここで改めて2つのキーワード——Open Table FormatとApache Iceberg——の関係を整理しておきます。
Open Table Format(OTF)は"仕様" です。
Object Storage上に置いた大量のファイルを"1つのテーブル"として扱うための、オープンな決めごと。どれが今の正しいデータか、更新しても壊れない一貫性、スキーマ変更への対応、カタログ経由の共有——ファイルの群れにこうした"テーブルの意味"を足します。
Apache Icebergは、そのOTFを実装した"実物"です。代表的なオープンソース実装のひとつで、前章で触れたとおり、幅広いエンジンやサービスが対応を進めています。
仕様だから特定の製品に縛られず、いろんなエンジンが同じデータを共有できる。この記事の"深掘り"は、この仕様の中身をIcebergという実物で具体的に見ていくことです。
全体アーキテクチャ — 構成要素は5種類
Icebergテーブルの実体は、3つのレイヤーに分かれています。
- カタログ — 「今の最新版はどれか」を指すポインタを1本持つ
- メタデータレイヤー — データを束ねて「いつ・何が・どういう状態か」を記録するファイル群
- データレイヤー — 実データ。Parquetファイルが置かれる
この3つのレイヤーの中に、基本の構成要素が5つあります。上位の要素が下位の要素を参照する、次のような構成です。
- Catalog
- metadata file
- manifest list(=snapshot)
- manifest file
- data file
構成要素の名前は、この後の説明で出てくるので、ここで覚えてしまう必要はありません。特徴的なのは、メタデータレイヤーとデータレイヤーの実体が、Object Storage上のただのファイルであることです。一度書かれたファイルが書き換えられることはなく、更新のたびに新しいファイルが増えていく構造になっています。
実体 — CREATE TABLE → INSERT → INSERT で何が増えるか
概念だけだと掴みにくいので、実際にファイルが作られる様子を見ていきます。ここが第3章の中心です。
① CREATE TABLE
CREATE TABLE dev.db.sales (…) USING iceberg;
まだデータは1行も入れていませんが、metadata/ にはもう v1.metadata.json が生まれています。この中にはカラムやデータ型といったスキーマ情報が入っていて、カタログが「現在の版はv1」を指します。
CREATE TABLEで作られるのは metadata(テーブルの設計図)だけ。dataはまだありません。
② 1回目のINSERT
INSERT INTO dev.db.sales VALUES (1, 'apple'), …;
INSERTが完了すると、snapshotが1つ生まれます。作られる順番が大事です。
- 実データを Parquetファイル(既定)で書く
- そのParquetを列挙する manifest file を作る(実データのパスに加えて、列の最大/最小値などの統計も入る)
- manifest fileを列挙する manifest list を作る
- 新しいsnapshotを含む metadata.json を作る(v2へ世代交代)
- カタログのポインタを更新する(=commit)
③ 2回目のINSERT
INSERT INTO dev.db.sales VALUES (4, 'grape'), …;
2回目も同じ流れで、data・manifest・manifest list・v3.metadata.json が増えます。
既存のファイルは一切書き換えられず、新しいファイルが増えるだけ。 過去の版(v1・v2)のファイルはすべて残っています。これが後で出てくるtime travelの土台です。
✓ ここまでの整理
新しい名前が続いたので、一度整理します。
この後の4つの問いは、この構造を前提に進みます。
- 構成要素は5つ:Catalog → metadata file → manifest list(=snapshot)→ manifest file → data file
- エンジンがデータを読むときは、この一本道を上から辿る:①Catalogに聞く → ②metadata.json → ③manifest list → ④manifest → ⑤data file
- 書いても既存ファイルは書き換わらない:新しいファイルが増え、metadataが世代交代(v1→v2→v3)し、最後にカタログのポインタが切り替わる
- その"ポインタ切り替え"がcommit
Q1. 複数エンジンは、どうやって同じテーブルを見つける?
エンジンは、まずカタログに「sales.orders はどこ?」と問い合わせます。
カタログが返すのは、現在のmetadataファイルの場所だけです。名前から場所を引く、"電話帳"のような役割です。データ本体は中継しません。返ってきた場所へ、各エンジンがObject Storageを直接読み書きします。そのため、カタログが扱うのはポインタのやり取りだけで、大量のデータ転送を担うことはありません。
"同じテーブルを見ている"とは、同じカタログから、同じポインタを取得していることを指します。
あわせて、第2章の④「発見と権限」も、このカタログ層の担当です。
カタログの要件は2つ
では、カタログはどのような実装であるべきか。要件はシンプルで、次の2つです。
- 現行のmetadataポインタを保持できること
- それをアトミックに(中途半端な状態なく)更新できること——compare-and-swap(CAS)、check-and-putとも呼ばれます
本質はatomic pointer swapで、これがcommitを成立させる要件です。要件が2つだけなので、実装は多様になれます。
- Hive Metastore
- AWS Glue、
- RDBの1行を使うJDBC方式、
- そして現在主流のRESTカタログ(Apache Polaris、Unity Catalog、S3 Tables、Oracle AI Data Catalogなど)
があります。
注意点:
ファイル名のrenameでcommitする旧方式(Hadoop Catalog系)は、S3系ストレージでは競合検出が弱く、コミュニティが非推奨としています。理由はHiveのところと同じで、S3にアトミックなrenameが無いためです。同時書き込みで片方の更新が消える——lost updateのリスクがあります。
Q2. どのファイルが"今のテーブル"?
"今のテーブル"を決めているのは、metadata.json の中の current-snapshot-id です。
metadata.json にはテーブルの状態が入っています。
スキーマ、パーティション定義、これまでのsnapshotの一覧、そして「今はこのsnapshot」を1つ指す current-snapshot-id。
"今のテーブル"は、次の流れで一意に決まります。
Catalog → v3.metadata.json(現在の版)
→ current-snapshot-id = S2
→ snapshot S2(=manifest list)
→ manifest → data files
エンジンは current-snapshot-id 以外を参照しないため、読み取り時点の状態が一意に定まります。
ここで大事なのは、commitが metadata.json の上書きではなく、新しいバージョンの生成(v1 → v2 → v3)であることです。"今"が常に1つに定まり、さらに過去の版も残ります。
manifestは"索引"でもある
manifestは一貫性のためのメタデータですが、もう1つ役割があります。索引です。
例えるなら、書類キャビネットです。
manifest listは引き出しの一覧で、「この引き出しは4月〜9月の契約書」と範囲が書いてあります。manifestは引き出しの中の目録で、1件ごとの日付が載っています。どちらも中身が外から読めるので、開ける引き出しを先に決められます。
上記下段の図の例では WHERE day = '2026-07-10' というクエリに対して、manifest AとCは範囲外なので、開くまでもなく対象外になります。manifest Bだけを開き、その目録からf2とf3だけが該当すると分かる。結果として、4ファイル中2ファイルだけを読みます。
Hiveでは、まずストレージのディレクトリを端からLISTして存在するファイルの一覧を取得し、そのうえで読む対象を絞り込む必要がありました。Icebergでは、その一覧と統計をメタデータ側が持っているため、ストレージにファイル一覧を問い合わせずにスキャン計画が立ちます。第2章でParquetの統計は「ファイル1個の内側まで」と書きましたが、それをテーブル全体に広げて集約しているのがmanifestです。
一貫性のために持っているメタデータが、そのまま性能にも効いている。ここがIcebergのうまくできているところだと思います。
Q3. 更新中の未完成データを、なぜ読み手は読まない?
Icebergでは、ファイルを書き込むことと、それを読み手から見えるようにすること(=commit)が、別のステップに分かれています。
図の4コマを順に追います。
① 現在はS0。カタログはv2を指していて、読み手はS0を読んでいる。
② 書き手が新しいdata fileを書く。ただし"横に"置くだけで、S0の世界は何も変わらない。
③ 新しいsnapshot S1を含むv3.metadata.jsonを書く。これもまだ誰にも見えない。
④ カタログのポインタをv2からv3へ差し替える。これがcommitで、この瞬間だけが"公開"です。
ファイルの書き込みに10分かかっても、読み手には影響しません。公開は、ポインタ1個の差し替えで一瞬で終わります。
逆に、同時に"書いたら"どうなる?
2つのエンジンが同時に書いた場合です。両者とも新しいメタデータを横で作り、最後にカタログのポインタを差し替えようとします。
この差し替えに使われるのが CAS(compare-and-swap) です。「現在の値が、自分の想定している値と一致しているかを**比較(compare)し、一致したときだけ新しい値に交換(swap)**する」という操作で、比較と交換は分割されず、1つの操作として実行されます。途中に他の書き手が割り込む余地がありません。カタログのポインタに当てはめると、「今のポインタが、自分が作業の前提にした版のままなら差し替える。すでに誰かが変えていたら失敗させる」という動きになります。
図では、先に成功した書き手Aのcommitが反映されます。もう一方の書き手Bは、前提にした版がすでに古いため弾かれ、最新のsnapshotを土台に変更を作り直してリトライします。互いの変更が重ならなければ自動で取り込まれ、論理的に衝突するときだけ検証エラーになります。
ポイントは、これが「早い者勝ちで、負けた更新は消える」という仕組みではないことです。同時書き込みでも"更新の取りこぼし(lost update)"は起きず、負けた側の変更もリトライを経て反映されます。
この分離によって、書き込み中の中間状態が読み手から見えることはなくなります。第4章では、この「書く」と「公開する」の間の"すき間"を実機で確認します。
読み手がS0しか見ない理由 — snapshot isolation
読み手は、読み始めた時点のsnapshotを、クエリが終わるまで使い続けます。途中で書き手が何をしようが、commitが起きようが、影響を受けません。commit後に新しく読み始めた読み手は、S1を見ます。
読み取りは常にcommit済みsnapshotを使い、書き込みが部分的に見えることはない。
metadataファイルのアトミック交換が "the basis for serializable isolation"(Apache Iceberg Table Spec)。
第2章で見た"半分だけ新しい世界"は、この仕組みで起こらなくなります。しかも読み手はロックを取らないため、書き手と衝突しません。複数のエンジンが同居できるのは、この性質によるものです。
Q4. 過去のデータをどう再現する?
ここまで見たとおり、commitのたびにsnapshotが履歴として積み上がり、古いsnapshotとそれが指すdata fileは削除されずに残ります。
保持期限内はどれも消えません。これによって、2つのことができます。
| Time travel | ロールバック | |
|---|---|---|
| 何をする | 過去を "読む" | "今"を過去に戻す |
| テーブルの今 | 変わらない | 戻した状態が新しい"今"としてcommitされる |
| SQL | SELECT … FOR VERSION AS OF <S0のID> |
CALL … rollback_to_snapshot('orders', <S1のID>) |
| 用途 | 障害調査/監査/MLの学習時点を固定 | 誤った更新をまるごと巻き戻す |
Time travel は、過去のsnapshotを"読む"機能です。通常のSELECTに、FOR VERSION AS OF を1句加えます。
SELECT order_state, COUNT(*)
FROM orders
FOR VERSION AS OF 1250675833300994520 -- snapshot ID(時刻での指定も可)
GROUP BY order_state;
指定したsnapshot時点のデータが返ります。「先週金曜の状態でもう一度集計する」が、この1句で済みます。テーブルの今は変わりません。
ロールバック は、"今"を過去に戻す操作です。
CALL catalog.system.rollback_to_snapshot('db.orders', 1250675833300994520);
戻した状態が、新しい"今"としてcommitされます。履歴は消えずに積み上がるので、戻したあとで「やはり元に戻したい」も可能です。
どちらも「履歴を消さずに積み上げる」という同じ仕組みの上に成り立っています。
注意:ただし、無限に遡れるわけではありません。保持期間を過ぎたsnapshotはexpireで消えます。「どこまで戻れるか」は運用ポリシーで決めます(この後のメンテナンスで触れます)。
4つの問いに出てこなかった、もう2つの柱
最後に、4つの問いに出てこなかった機能を2つ紹介します。この2つは性質が異なります。1つ目はIcebergの仕様に組み込まれている機能で、どのエンジンから使っても同じように働きます。2つ目は利用者側で行う運用の管理操作で、実行しない限り動きません。
変化への対応(仕様として決まっている機能)
運用していれば、テーブルは必ず変わります。列を追加したくなりますし、パーティションの粒度も変えたくなります。そのときに「作り直し」にならないための機能が、仕様そのものに入っています。
- Schema Evolution — 列を名前ではなく"列ID"で追跡する。列の追加・改名・削除・型の拡張を、既存のdata fileを書き換えずに行える
- Hidden Partitioning — 「order_tsを日で切る」といった変換ルールをmetadata側が持つ。利用者は普通のWHEREを書くだけで絞り込みが効き、書き忘れによるフルスキャンが起きない
- Partition Evolution — パーティションの粒度(月→日など)を後から変えられる。過去のファイルは月単位のまま、新しいファイルだけ日単位で書ける
3つに共通するのは、変更がmetadataの更新だけで終わることです。data fileには触らないので短時間で終わり、過去のデータも壊れません。これらはIcebergの仕様そのものなので、どのエンジンから使っても同じように機能します。
テーブルを健全に保つ(利用者が運用として行う操作)
前提として、Icebergは書き込みのたびに新しいsnapshotを作ります。Time travelを支えている一方で、放置するとsnapshotもmetadataもdata fileも増え続けます。そのため、"片付ける"操作が必要になります。代表的なのは次の4つです。
- Snapshot expiration — 保持期間を過ぎたsnapshotを失効させ、不要になったdata fileを削除する
-
古いmetadataの整理 — 世代交代で増えた古い
metadata.jsonを整理する - Orphan fileの削除 — 書き込み失敗などで、どのsnapshotからも参照されなくなったファイルを掃除する
- Compaction — 追記で増えた小さなファイルをまとめ直す
これらは"読み書きの仕組み"ではなく、テーブルを運用するための管理操作です。エンジン(Sparkの手続きなど)やマネージド基盤が定期的に実行し、一部はテーブル設定で自動化できます。仕様として決まっている読み書きとは、層が違います。
第4章:デモ — 3つのエンジンで同じテーブルを触ってみた
ここまでは、仕様の話でした。仕組みの上では"こうなるはず"です。
では、本当にそのとおり動くのか——実際に組んで、目で見て確かめます。
確かめたかったこと
このデモの目的は、第3章で見た仕組みを実機で観測することです。確かめることは4つあります。
- マルチエンジン共有 — 1つのIcebergテーブルを、3つのエンジンがコピーなしで共有できること
- 読み取り一貫性 — 更新中も、読み手には確定済みのデータしか見えないこと
- commit境界 — "書く"と"公開する"が分かれていて、切り替わるのはcommitの一瞬だけであること
- Time travel — 過去の時点を再現できること
このうち2つ目と3つ目は表裏の関係で、Step 4でまとめて確かめます。1つの問いにすると、こうなります。
Sparkが更新している最中に、別のエンジンから読んだら、本当に中途半端な状態は見えないのか?
これを確かめるために、読み手のクエリを1秒間隔で実行し続けた状態で書き手のMERGEを走らせ、その間の観測をすべてログに記録しました。
想定した場面
1つの注文テーブルを、社内の各部署が別々のエンジンで使う——という想定です。
受注管理は毎朝ADBで定型集計、経営企画はBIダッシュボード、データサイエンスは学習データの抽出、データ基盤チームは後から届く変更をSparkで一括反映、データ分析チームはTrinoで横断分析。(あくまで一例です。)
今回のデモでは、その中でも読み手をADBとTrinoが、書き手をSparkが担当します。Sparkの更新中も、読み手側の業務が止まらないことを、このデモで目で見て確認してみます。
構成とデータ
| 層 | 使ったもの |
|---|---|
| カタログ | Oracle AI Data Catalog(AICAT)— ADB内蔵のIceberg REST Catalog |
| ストレージ | OCI Object Storage — S3互換エンドポイント。データはParquet |
| エンジン① | ADB 26ai(CREATE / INSERT / 集計) |
| エンジン② | Spark 3.5(OCI Data Flow・MERGE) |
| エンジン③ | Trino 482(読み手ループ / time travel) |
中央のカタログ(Oracle AI Data Catalog: AICAT)で全エンジンがmetadataを共有します。1つのIceberg tableを3エンジンが共有し、コピーは作りません。
(カタログはREST Catalogであれば何でも構いません。Q1で書いたとおり要件は「ポインタを持つ」「アトミックに更新できる」の2つなので、Polaris等に読み替えても話は変わりません。)
データは、ブラジルECサイト「Olist」の公開データ(Kaggle)をベースにデモ用に加工した上で利用しました。
- 対象 — 2018-08-20〜08-26の1週間の注文 1,071件
- テーブル構成 — order_id・注文日・注文ステータスの3列のみ
-
初期状態 — 全件が
ACCEPTED(受付済み)。これが S0 - その後の更新 — キャンセル12件+配送遅延71件の計83件が、後から届く
注文の受付後にキャンセルや配送遅延の連絡が届いて更新が走る、という現実的な流れを再現しています。
デモは、次の5つのStepで進めます。
- Step 1 — ADBからIcebergテーブルを作成し、1,071件を投入する(S0)
- Step 2 — ADBでS0を集計する
- Step 3 — Trinoから同じテーブルを読む
- Step 4 — SparkでMERGEを実行し、83件を反映する。その間も読み手は読み続ける
- Step 5 — 履歴を確認し、Time travelで過去に戻る
Step 1〜3で「コピーなしの共有」を、Step 4で「読み取り一貫性とcommit境界」を、Step 5で「Time travel」を確かめます。
なお、具体的な手順やコードをこちらの記事にまとめています。詳細は次の記事を参照ください。
Step 1:ADBからIcebergテーブルを作成する
まず、ADBのSQLだけで、Object Storage上にオープンなIceberg tableを作ります。
CREATE ICEBERG TABLE でテーブルを作成します。カタログ(AICAT)を指定している点がポイントです。
続いて、INSERT INTO … SELECT で1,071件を投入します。INSERTの完了と同時に、snapshot(S0)としてcommitされます。
作成されたテーブルは、カタログとObject Storageの両方から確認できます。AICATのカタログをリフレッシュすると、先ほどまで何もなかったところにテーブルが登録されていることが確認できます。
データも挿入されています。
Object Storageのバケットには、metadata(metadata.json等)とdata(Parquet)の実体ができています。第3章で見たファイル構成が、実際に並んでいるところです。
SQLを実行しただけですが、この時点でObject Storage上にオープンな形式のテーブルが存在し、他のエンジンと共有できる状態になっています。
Step 2:ADBでS0を集計する
そのまま、いつものSQLで集計してみます。
SELECT order_state, COUNT(*)
FROM "demo"."orders_…"@AICAT
GROUP BY order_state;
結果は accepted 1,071 / canceled 0 / delayed 0。
全件が受付済みの初期状態、つまり S0 です。
ADBの利用者には、Iceberg tableが"普通の表"に見えます。snapshotやmetadataといった内部の仕組みを知らなくても、通常のSQLで現在のデータを読めます。裏側ではRESTでmetadataの位置を取得し、Object Storageのdata fileを直接読んでいます。
Step 3:Trinoから同じテーブルを読む
次に、まったく同じテーブルを、別のエンジン(Trino)から読みます。ADB側の操作は何もしていません。
snapshot=S0:1250675833300994520
accepted=1071 canceled=0 delayed=0 total=1071
ADBとまったく同じ結果です。再コピーは一切していません。 TrinoのIcebergコネクタが同じカタログを参照し、Object Storage上の同じdata fileを直接読んでいるからです。
あわせて、snapshot ID——テーブルの"版番号"——も取得しておきます。これが次のStepで効きます。
ここまでで、確かめたいことの1つ目「コピーなしの共有」が確認できました。次のStep 4では、更新中に読み手から何が見えるかを確かめます。
Step 4:SparkでMERGE — そして「93秒の窓」
3つ目のエンジン、Sparkが書き手として参加します。OCI Data FlowでSparkアプリを実行し、後から届いた83件(キャンセル12・配送遅延71)をMERGEで反映します。方式はcopy-on-write——更新行を含むdata fileを新しいファイル群として書き直し、最後に1回だけcommitします。書き込みは約2分。
さて、書いている最中に読んだら、どうなるか。
やったことはシンプルです。
-
Trinoからループを実行し、1秒間隔でクエリを流し続ける(毎秒、snapshotと集計と
data-files数を表示) - その状態で SparkのMERGEを開始(約2分)
- 書き込み中に、ADBでも同じ集計を1回実行
実測ログ
これが実際のログです。
12:46:51 snapshot=S0:3442487993400379318 accepted=1071 canceled=0 delayed=0 total=1071 | data-files=3
12:47:18 snapshot=S0:3442487993400379318 accepted=1071 canceled=0 delayed=0 total=1071 | data-files=4
12:47:31 snapshot=S0:3442487993400379318 accepted=1071 canceled=0 delayed=0 total=1071 | data-files=4
12:47:34 ◀◀◀ COMMIT — snapshotが切り替わりました(S0 → S1)
12:47:34 snapshot=S1:6480389501209241833 accepted=988 canceled=12 delayed=71 total=1071 | data-files=5
注目する点は3つあります。
-
data-filesが 3→4 と増えている = 新しいファイルはストレージに実在している - それなのに、読み手の結果は
1071/0/0のまま微動だにしない -
◀◀◀ COMMITの一瞬で、次の行から988/12/71(S1)へ一斉切替
以後 data-files=5(新4+旧1)。旧ファイル1つは、Step 5で過去を読むために残っています。
もしIcebergが無かったら — エラーにならずに間違う
逆を考えると、価値がはっきりします。
commitという概念がない「フォルダ内のファイルの集まり」で、深夜バッチが4ファイルを順に置き換えている最中に集計してしまうと——1地域だけ置き換わった瞬間には 1,050 / 3 / 18(合計1,071)が返ります。
更新前(1,071/0/0)でも更新後(988/12/71)でもない、業務上存在しない中途半端な状態です。
怖いのは、SQLがエラーなく成功し、合計も1,071で合っていることです。処理が途中で止まれば、中途半端な状態が残り続ける。誰も気づけません。
「エラーになる」なら気づけます。"エラーにならずに間違う" のが、この構成の怖いところです。第1章の「数字が合わない問題」集計結果が食い違っても誰も気づかないというのも、この"気づけない不整合"の一例でした。
Step 5:履歴を確認し、Time travelで過去に戻る
最後のStepでは、Time travelを試します。MERGEで更新した後のテーブルから、更新前(S0)の状態をそのまま読み出せるのか。履歴の確認 → 現在の集計 → Time travel の順で、Trinoから3つのクエリを実行します。
まず、このテーブル自身が持っている履歴を確認します。$snapshots メタデータ表を読むと、commitの一覧が返ります。
2行が返りました。1行目が最初のcommit(S0。1,071件の投入で operation=append)、2行目がStep 4のMERGEによるcommit(S1。operation=overwrite)です。S1の parent_id がS0のIDを指しており、「S1はS0から生まれた」という系譜が、テーブル自身のメタデータとして残っています。
次に、時点を何も指定せずに集計します。
結果は 988 / 12 / 71。Step 4のMERGE後の状態です。ここまでは、普通のSQLです。
最後に、同じ集計SQLへ FOR VERSION AS OF とS0のsnapshot IDを1行加えます。
SELECT … FROM aicat.demo.orders_…
FOR VERSION AS OF <S0のID>;
結果は 1071 / 0 / 0——MERGE前の状態が、そのまま返ってきました。
バックアップからのリストアはしていません。Step 4のあともdata fileが5つ(新4+旧1)残っていたとおり、旧data fileと旧metadataが保持されているので、過去のsnapshotを指定するだけで当時の状態が読めます。
デモまとめ
違いは、テーブル単位のcommit境界(snapshot)があるか無いか、の一点です。
| commit境界あり(Iceberg) | commit境界なし(従来型) | |
|---|---|---|
| 更新中に見えるもの | S0のみ | 中間状態が漏れる |
| 更新後の切替 | commitの一瞬 | 徐々に |
| 途中で処理が死んだら | 無傷 | 汚れたまま残る |
| 過去に戻れるか | 戻れる | 戻れない |
SparkがMERGEしている間、読み手が見たのはS0かS1だけ。中途半端な状態は一度も読まれませんでした。
第5章:オープンな基盤から、AIへ
最後に、AI活用の例です。
第1章の出発点は「AIによって、同じデータを読む主体が増える」ことでした。この章では、ここまでのデモで使ってきたIcebergテーブルを、そのままAIの分析に使う例を紹介します。
仕組みの上では、ベクトル検索・RAG・自然言語問い合わせも「同じデータを読む新しい経路」であり、ここまで見てきたマルチエンジン共有の延長で扱えます。AI用に別のデータ置き場を用意する必要はありません。
加えて、AIには他の読み手と違う事情があります。AIは、渡されたデータが古くても、気づかないまま回答します。答えの質が土台データの鮮度と一貫性に依存する——この点で、commitで最新が一意に決まり、時点を機械的に特定できるIcebergの性質は、AIの用途で特に意味を持ちます。
デモで具体的に見ていきます。
作ってみた:配送分析アシスタント
先ほどの受発注データを使った、"その後"の分析をやってみます。
ECサイトの物流チーム。配送拠点のトラブルでいくつかの注文が遅延し、うち30件を急遽別の配送会社に切り替えた。
いまは全件届け終わって、レビューも集まっている。
さて、あの対応は本当に良かったのか?
やることは2段階です。まずsnapshotを辿って、どの注文が遅延し、どれが配送会社を切り替えたものかを特定します(=Time travel)。
次に、その顧客のレビューをAIに読ませて、顧客体験を分析します(=ベクトル検索+生成AI)。
Icebergなら過去の状態に戻れるので前後比較ができ、オープンな置き場なのでAIも同じデータに届きます。
アーキテクチャ
| 部品 | 役割 |
|---|---|
| Object Storage(Iceberg) | 受発注/配送ログ、顧客レビュー本文 — すべてIcebergのまま |
| Trino |
FOR VERSION AS OF で3時点の配送イベントを読取り |
| ADB | ベクトル表(VECTOR(1024, FLOAT32))+ IVFベクトル索引 |
| OCI Generative AI | 埋め込み(多言語)/回答生成 |
| Webアプリ(FastAPI) | ①質問文を埋め込み → ②ベクトル検索 → ③回答を生成 |
ポイントは、レビュー本文をIcebergから動かしていないことです。ADBが持つのはベクトルと索引だけ。日本語で質問すると、多言語埋め込みを介してポルトガル語のレビューに"意味で"ヒットします。
結果:配送は3倍速くなった。しかし評価は改善しなかった
まず、数字の集計結果です。
| 切り替えなかった注文 | 切り替えた注文 | |
|---|---|---|
| 遅れ平均 | 12.0日 | 4.0日 |
| 評価 | ★1.54 | ★2.0 |
配送は3倍速くなりました。ところが評価はほとんど改善していない。
AIにレビューを分析させると、理由が並びました。切り替えた注文の不満は「遅い」ではなく——配送会社が変わったと知らされていない、追跡情報が止まった、メールと追跡画面の日付が違う、届いていないのに配達済み表示。
結論:到着は早くなったが、追跡情報と配送会社間の情報連携の問題が、顧客の不安を増やしていた。
分析した結果、今後につながる改善策も提示してもらいました。
"数字の上では成功した対応"に、数字に出ない副作用が隠れていた。これを勘ではなく、過去データとレビューから特定できたのがこのデモの価値です。
このデモを支えたIcebergの4つの性質
| 性質 | デモで効いたこと | |
|---|---|---|
| ① | 最新はcommitで一意に決まる | ADB・Spark・Trino・AIが常にcommit済みの最新だけを読む → コピーや同期を配って回る必要がない |
| ② | "いつのデータか"を機械が読める | 回答に「どの時点のデータか」を自動で付けられる。生のフォルダにはその情報がない |
| ③ | 過去時点をそのまま再現 | 「あの時点ではどうだったか」に、当時のデータで答えられる |
| ④ | **データを動かさない ** | 本文もベクトルもIcebergのまま。鮮度ズレの発生源を作らない |
デモで使ったAI側の機能(埋め込み・ベクトル検索・回答生成)は、いずれも一般的なものです。このデモに固有なのは、参照するデータがcommit済みの一貫した状態に保たれ、過去の時点も再現できるという、テーブル側の性質です。
見方を変えると、この土台が整っていれば、AI活用のために基盤を作り直したり、データを移し替えたりする必要がない、ということでもあります。ベクトル検索も、今回のデモのように対象データをIcebergに置いたまま行えます。分析のために整えた共有テーブルに、AIという新しい読み手が加わるだけで、今回のデモのような分析が成り立ちます。
まとめ
■ 持ち帰ってほしい5つ
- AI時代の前提は「1つのデータ、複数のエンジン」— コピー運用は鮮度・コスト・ガバナンス・再現性で破綻しやすい
- 「保存できる」と「エンジンが読める」は別 — Object StorageとParquetで"保存"は十分に担われるが、ファイル群をテーブルとして解釈するための情報と仕組みが欠けている。そこを補うのがOpen Table Format
- Icebergの本質はシンプル — 不変のデータファイル + 階層メタデータ + snapshot + カタログのアトミックなポインタ更新(commit)
- 「書く」と「公開する」の分離 — 実測では、書き込み完了からcommitまでに93秒の"すき間"がありました。その間、中間状態が読まれることは一度もありませんでした
- 小さく始められる — 既存データを1テーブルIceberg化して、REST Catalog経由で2つのエンジンから読む。そこからで十分です
■ 注意点
ここまでIcebergの仕組みと利点を中心に見てきましたが、注意しておきたい点もまとめておきます。
- 読み手が1つのエンジンに限られる場合、Icebergの利点は活きにくい。「複数のエンジンで同じデータを読む」場面で価値が出る仕組みになっている
- カタログは選択肢が多く、製品ごとの差もある。REST Catalogという共通APIはあるものの、一度選ぶと、後から移りにくい面がある(実質的なロックインポイントになるのか)
- エンジンごとに対応機能の差がある。本記事のデモでも、ADBのIceberg読み取りはtime travel未対応のため、該当部分はTrinoで実行した
- snapshotは書き込みのたびに増え続けるため、expire・compaction・orphan fileの削除といった運用が必要になる。自動では行われない
おわりに
最後まで読んでいただき、ありがとうございました。
Open Table FormatやIcebergは、構成要素の名前だけ見ると身構えてしまいます。でも一度通してみると、やっていること自体はそれほど複雑ではありませんでした。「ファイルは増えるだけ、公開はポインタ1個の差し替え」——ここが分かると、time travelもschema evolutionも、全部その説明として読めるようになります。そして、仕様にそう書いてあることが、実機でもそのとおりに起きていました。
この記事が、その最初の一歩になっていたら嬉しいです。
まずは既存のデータを1テーブルだけIceberg化して、2つのエンジンから読んでみてください。コピーを配らずに済む、という感覚が掴めると思います。
参考
■ 一次情報
- Iceberg Table Spec https://iceberg.apache.org/spec/
- Reliability(並行制御の解説)https://iceberg.apache.org/docs/latest/reliability/
- Schema/Partition Evolution https://iceberg.apache.org/docs/latest/evolution/
- REST Catalog OpenAPI https://github.com/apache/iceberg/blob/main/open-api/rest-catalog-open-api.yaml
- Trino Icebergコネクタ https://trino.io/docs/current/connector/iceberg.html
- Oracle AI Data Catalog https://docs.oracle.com/en/cloud/paas/autonomous-database/serverless/adbsb/oracle-ai-data-catalog.html
- Lakehouse論文(Armbrustら, CIDR 2021)https://www.cidrdb.org/cidr2021/papers/cidr2021_paper17.pdf
■ 歴史・動向
- Ryan Blueインタビュー(Hive問題の述懐)https://thenewstack.io/apache-iceberg-a-different-table-design-for-big-data/
- Databricks×Tabular https://www.databricks.com/company/newsroom/press-releases/databricks-agrees-acquire-tabular-company-founded-original-creators
■ データ出典
- Brazilian E-Commerce Public Dataset by Olist(Kaggle)https://www.kaggle.com/datasets/olistbr/brazilian-ecommerce
■ 関連記事(Iceberg誕生の歴史・機能の網羅解説はこちら)
■ 登壇資料
- OCHaCafe「レイクハウスの要!Open Table Format深掘り」(2026年7月15日)— 公開予定













































