「Planningが長い」謎を解く。Icebergで学ぶ、メタデータという"データ"のコスト

ログラスの龍島(@hryushm)です。氷のように寒い日々ですね。今日はIcebergの話です。
はじめに
「SQLのIN句に大量のIDを指定するとクエリが遅くなる」。 これはRDBMSの世界ではよく知られた話です。
一般的にその理由は、オプティマイザが複雑なOR条件の展開に苦戦するためであったり、巨大なSQL文自体のパース負荷の問題として説明されます。
私自身も、BigQueryやSnowflakeといったモダンなクラウドDWHで同様の現象(大量ID指定による遅延)に遭遇した際、「伝統的なRDBMSみたいに、オプティマイザが頑張りすぎているんだろう」「裏側で複雑な処理が行われているのだろう」程度の認識でいました。
しかし最近、Apache Iceberg の内部構造を学ぶ機会があり、その仕様を理解するにつれて、裏側で物理的にどのような処理が行われているのかの解像度が上がりました。
今回はIcebergの内部構造(特にメタデータ管理の仕組み)を紐解きながら、ブラックボックス化されがちなDWHの「Planning」フェーズで、具体的にどのようなコストが発生しているのかについて考えます。もちろん、BigQueryやSnowflakeの内部実装がIcebergそのものであるわけではありません。しかし、 「ストレージとコンピュートの分離」 や 「メタデータによるプルーニング」 といったアーキテクチャの根幹は共通しており、Icebergの仕組みを知ることは、ブラックボックス化された各DWHの挙動を推測する上で有用なメンタルモデルとなります。
事象:実行計画(Planning)で長時間待たされるクエリ
かつて私が遭遇した事象も、まさに「大量ID指定」によるものでした。
数億行規模のテーブルに対して、以下のようなフィルタリングするクエリを実行していました。
SELECT *
FROM my_large_table
WHERE partition_date = '2025-01-01'
AND id IN (
-- ここに数千~数万件のIDリストが指定されている
1001, 1005, 1024, ...
)
※単純化されたクエリであり、実際はフィルタ結果に対し処理が続いていました
対象のテーブルは適切にパーティション分割(Partitioning)されており、id カラムによるクラスタリング(Clustering)も設定済みです。
通常であれば、DWHのプルーニング機能が働き、必要なデータブロックのみを効率的に読み込むため、数秒程度で結果が返ってくることを期待していました。
しかし実際には、クエリの実行自体が開始されるまでに長い待機時間が発生していました。
実行計画を確認すると、データ読み込み(Compute)の前段である 計画(Planning) フェーズでボトルネックが発生していることがわかりました。
なぜ、データを読み込む前の段階でこれほど時間がかかってしまうのか。
Icebergのアーキテクチャを参照することで、その物理的なメカニズムが見えてきます。
Icebergの内部構造からボトルネックを紐解く
Planning遅延の要因を理解するためには、DWHがどのようにデータを管理しているかを知る必要があります。Apache Iceberg の仕様書を参照すると、その構造がよく分かります。

出典: Apache Iceberg Spec - Overview
Icebergは実データ(Parquetなど)とは別に、メタデータ層 でファイルの場所や統計情報を管理しています。
-
Catalog
- Current Metadata Pointer: テーブルの最新状態を指すポインタ
-
メタデータ層
- Metadata File: テーブルのスキーマ、パーティション設定、スナップショット履歴などを管理するファイル
- Manifest List: マニフェストファイルのリスト。各マニフェストが保持するデータのパーティションの範囲 などの統計情報を持つ
- Manifest File: データファイルのリスト。ここに各カラムの 詳細な統計情報(Column Statistics) が含まれる
-
データ層
- Data File: 実データ(Parquet等)
重要なのは、Manifest Fileに含まれる 統計情報(Column Statistics) です。ここには各ファイルに含まれる列データの 「最小値(Min)と最大値(Max)」 が記録されています。
クエリ実行時にこのメタデータを参照し、Min <= 条件値 <= Max の範囲に含まれないファイルを読み飛ばします。これが プルーニング(Pruning) と呼ばれる機能であり、データを高速に処理するための重要な仕組みです。
この「メタデータを活用してスキャン量を減らす」というアプローチは、クラウドDWHにおいて一般的です。
例えば BigQuery では CMETA (Column Metadata Index) と呼ばれるインデックスで各ブロックの統計情報を管理しており(参考)、Snowflake は FoundationDB を用いた分散Key-Valueストアとしてメタデータを管理していると言われています(参考)。
実装技術こそ異なりますが、「クエリ実行前にメタデータ層にアクセスし、不要なデータを読み飛ばす(枝刈りする)」 という戦略は共通していると言えます。
しかし、この「メタデータをチェックして不要なファイルを弾く」という処理自体にも計算コストがかかります。
通常のクエリではこのコストは無視できるほど小さいですが、「数万件のID指定」を行うクエリの場合、話が変わってきます。
大量ID指定時に発生しているメタデータスキャンのコスト
WHERE id IN (1001, 1002, ..., 50000) というクエリが発行された際、オプティマイザはプルーニングのために以下の処理を行う必要があります。
- 対象テーブルのマニフェストファイルを読み込む。
- 各ファイルの
Min/Max範囲と、IN句で指定された 数万件のIDリスト を比較する。 - 「このファイルには指定されたIDが含まれている可能性があるか?」を全てのファイルに対して判定する。
このときの計算コストは、単純に線形探索すると仮定すると 「マニフェストファイルの数」×「指定したIDの数」 に比例して増えていく計算になります。
※実際はIcebergにPuffin fileという追加で統計情報を持たせる機能があり、Bloom Filterなどと組み合わせることで最適化する余地が大いにあります。
BigQueryやSnowflakeにおいて発生していた「Planning の遅延」も、これと類似した現象であると推測されます。
実行計画で「Metadata Read」や「Planning」に時間がかかっている場合、本来スキャン量を減らすためのプルーニング処理自体が、大量のIDリストに対するメタデータの検索・照合コストによってボトルネックになってしまっていた可能性が高いのです。
メタデータもまた、計算リソースを消費する
ここまでから得られる知見は、「メタデータもまた、処理コストのかかるデータである」 ということです。
普段私たちはメタデータを「検索のための軽量なインデックス」として捉えがちです。しかし、データ本体が巨大になれば、それを管理するメタデータ(Manifest Files)自体も相応のサイズになります。
Icebergのようなモダンなアーキテクチャは、メタデータ層を活用して効率的なクエリを実現していますが、そのメタデータ自体も物理的なファイルであり、読み込みや比較演算には CPU / IO リソースを消費します。
「メタデータへのアクセスはコストが低い」という前提は、クエリの複雑度やデータ規模によっては必ずしも成立しないということを認識しておく必要があります。
つまり、メタデータであっても通常のデータと同じように、その規模に応じた適切なデータ構造や探索アルゴリズム、処理の効率化 を考慮して向き合う必要があるのです。
まとめ:抽象化された基盤の裏側を理解する
BigQueryやSnowflakeは内部の複雑さを隠蔽し、SQLを書くだけで大規模データを処理できる優れたサービスです。しかし、その抽象化の裏側では物理的なファイル操作や計算処理が必ず行われています。
今回、Apache Icebergの仕様を通じてDWHの内部構造(メタデータ管理、プルーニング)を学んだことで、ブラックボックスになりがちなクラウドDWHの挙動を、より解像度高く想像できるようになりました。
特に、「メタデータもまた、計算リソースを消費する『データ』である」 という事実は、大規模データ基盤を扱う上で非常に重要な視点です。
次回は、メタデータをデータとして捉えた時に、どのように立ち向かうべきなのか、Icebergはそのメタデータをなぜファイルで管理しようとしているのか考えてみたいと思います。
Discussion