16 人が閲覧(直近 30 日) データ分析・BI

IcebergとParquetの違い|テーブル形式とファイル形式の関係を実機で確認

IcebergとParquetの違い|テーブル形式とファイル形式の関係を実機で確認

Apache ParquetとApache Icebergは、どちらもデータレイクで使われる「形式」ですが、比べる層が違います。Parquetは1つのファイルの中身を列ごとに並べるファイルフォーマット、Icebergは多数のParquetファイルを1つのテーブルとして追跡するテーブルフォーマットです。Icebergテーブルの中を開くと、データ本体は普通のParquetファイルとして置かれています。この記事では、PyIceberg 0.12.0で実際にテーブルを作り、削除や列名の変更をしたときに両者の差がどこに出るかを確かめた結果をもとに、違いと使い分けを整理します。

まとめ:IcebergとParquetの違いの要点

  • 層が違うので二者択一ではない:Parquetはファイルの書き方、Icebergはそのファイル群をテーブルとして扱う決まりごと。Icebergの既定のデータファイル形式はParquetです。
  • Icebergが足すのはメタデータ:どのParquetがテーブルに属するかをmetadata.json・マニフェストリスト・マニフェストで記録し、更新はスナップショットの切り替えで反映します。
  • 列はfield_idで解決される:Icebergが書いたParquetには列ごとにfield_idが入り、列名を変えてもファイルは書き直されません。Parquet単体では旧ファイルと新ファイルで列が割れます。
  • IcebergのParquetを直接読むと結果が狂う:1行削除した後にdata/配下をread_parquetで読むと、正しくは2行のところ5行返りました。Icebergテーブルは必ずIceberg対応の読み方で読みます。
  • 判断の目安:書いたら変えない単発のファイルならParquet単体。更新・削除、複数エンジンからの読み書き、過去時点の参照のどれかが要るならIceberg。
  • 最新状況(2026年10月):Iceberg 1.12.0が2026年9月29日リリース。仕様はv1〜v3が確定、v4は策定中です。

ParquetとIcebergの役割と管理単位の違い

両者は「データをどう保存するか」の別々の段を受け持っています。Parquetが決めるのは1ファイルの内部構造で、Icebergが決めるのは「どのファイルの集まりを、どのスキーマのテーブルとして見せるか」です。

観点 Parquet Iceberg
種類 ファイルフォーマット テーブルフォーマット
管理する単位 1ファイル 多数のファイル=1テーブル
実体 .parquetファイル メタデータ+データファイル
スキーマの持ち方 ファイルごと テーブル全体で1つ
最新版(2026年10月) parquet-format 2.14.0 Iceberg 1.12.0

Parquet:1ファイルの中を列ごとに並べるファイル形式

Parquetは列指向のファイルフォーマットで、同じ列の値をまとめて圧縮して格納します。集計で使う列だけを読み出せ、ファイルのフッターに行グループごとの統計(最小値・最大値など)を持つため、条件に合わない範囲を読み飛ばせます。仕様はapache/parquet-formatのリリースで管理され、2026年9月11日に2.14.0が出ています。ただしParquetが定めるのは1ファイルの中身までで、「複数ファイルで1つのテーブル」という概念はありません。読み方・圧縮方式の実測はParquetとは?読み方・仕組み・CSVとの違いとSnappy・Zstd圧縮の実測にまとめています。

Iceberg:多数のファイルを1つのテーブルとして追跡するテーブル形式

Icebergは、オブジェクトストレージやHDFS上の大量のファイルを1つのテーブルとして扱うための仕様です。Iceberg Table Specは冒頭で「This table format tracks individual data files in a table instead of directories.」と述べており、ディレクトリではなく個々のファイルを記録する点が本質です。テーブルの状態はメタデータファイルに保持され、変更のたびに新しいメタデータファイルを作って原子的に差し替えます。データファイルの形式はParquet・Avro・ORCから選べ、テーブル設定のwrite.format.defaultの既定値はparquetです。

Icebergテーブルの中身を開くと、データはParquetのまま

Python 3.13にpyiceberg 0.12.0・pyarrow 26.0.0を入れ、SQLiteをカタログにした手元のテーブルに3行を書き込みました。

import pyarrow as pa
from pyiceberg.catalog.sql import SqlCatalog
from pathlib import Path
from pyiceberg.expressions import EqualTo

Path("/tmp/wh").mkdir(parents=True, exist_ok=True)

cat = SqlCatalog("local", uri="sqlite:////tmp/wh/catalog.db",
                 warehouse="file:///tmp/wh")
cat.create_namespace("demo")
df = pa.table({
    "order_id": pa.array([1, 2, 3], pa.int64()),
    "region": ["tokyo", "osaka", "tokyo"],
    "amount": pa.array([1200, 800, 500], pa.int64()),
})
t = cat.create_table("demo.orders", schema=df.schema)
t.append(df)

書き込み後のテーブルディレクトリには、次の5ファイルができました(サイズはバイト)。

demo/orders/
  data/00000-0-b0992446-....parquet                    1365  データ本体
  metadata/00000-bf03e6d6-....metadata.json           747  作成直後のテーブル定義
  metadata/00001-07a63619-....metadata.json          1539  追記後のテーブル定義
  metadata/snap-7676437028008430470-0-b0992446-....avro  1775  マニフェストリスト
  metadata/b0992446-...-m0.avro                       4360  マニフェスト

メタデータの4階層:カタログからデータファイルまで

読み手は上から順にたどります。カタログ(ここではSQLiteのcatalog.db)が「今のmetadata.jsonはどれか」を指し、metadata.jsonがスキーマ・パーティション定義・スナップショット一覧を持ち、スナップショットごとのマニフェストリストがマニフェストを列挙し、マニフェストが各データファイルのパスと列の統計を持ちます。この検証では各操作を個別にコミットしたため、操作ごとにmetadata.jsonが1つ増えました。この検証ではテーブル作成・追記・削除・列名変更の4操作で、metadata.jsonが00000から00003まで4つになりました。コミットの多いテーブルでは古いメタデータの掃除が必要になる理由がここにあります。カタログの選び方はApache Polarisとは?IcebergカタログのRBACとDocker導入手順【1.8.0対応】で扱っています。

データファイルは列にfield_idが付いた普通のParquet

data配下のファイルをpyarrowでそのまま開けます。スキーマを表示すると、各列にPARQUET:field_idが付いていました。

order_id: int64
  -- field metadata --
  PARQUET:field_id: '1'
region: string
  -- field metadata --
  PARQUET:field_id: '2'
amount: int64
  -- field metadata --
  PARQUET:field_id: '3'

この番号がIcebergのスキーマの列IDと対応します。また、PyIcebergが書いたファイルの圧縮はZSTD、同じpyarrow 26.0.0でpq.write_tableを直接呼んだファイルはSNAPPYでした。Iceberg Java実装の設定でもwrite.parquet.compression-codecの既定はzstdです。Icebergを載せると、同じParquetでも書かれ方が変わる例です。

機能で比べるParquet単体とIcebergの違い

Icebergを載せて増えるのは「テーブルとしての振る舞い」です。下の表のうち、削除・列名変更・過去時点の参照は手元で動かして確かめました。

観点 Parquet単体 Iceberg
行の更新・削除 ファイルを自分で書き直す DELETE・UPDATE・MERGE
書き込みの一貫性 複数ファイルの原子的更新は規定なし コミット単位で原子的
列名の変更 旧ファイルと列が割れる field_idで追従
過去時点の参照 なし スナップショット指定
パーティション 仕様外・配置は利用側で管理 隠蔽・後から変更可

行削除におけるファイル書き直しとスナップショット切り替え

Parquetファイルは書いたら変更できないため、1行消すにはその行を除いたファイルを作り直すしかありません。Icebergでも物理的には同じで、t.delete(EqualTo("order_id", 2))を実行すると、data配下には元の3行のファイルが残ったまま、2行だけの新しいファイルが追加されました。違いは、Icebergが新しいスナップショットで「今のテーブルは新しいファイルだけ」と記録し、読み手がそのとおりに読む点です。削除の方式はwrite.delete.modeで、既定のcopy-on-write(ファイルを書き直す)とmerge-on-read(削除情報を別ファイルに書く。v2以降)を選べる実装があります。ただし、この検証で使ったPyIceberg 0.12.0はmerge-on-readによる削除に未対応で、指定しても警告を出してcopy-on-writeで処理します。

列名変更時の名前による照合とfield_idによる照合の違い

Parquet単体で運用中にamountをamount_jpyに変えたとします。旧ファイルと新ファイルをDuckDB 1.5.6のread_parquet('*.parquet')でまとめて読むと「schema mismatch in glob」で失敗し、union_by_name=trueを付けるとamountとamount_jpyが別々の列になり、互いにNULLが入りました。

Icebergでrename_column("amount", "amount_jpy")を実行すると、テーブルの列名はamount_jpyに変わる一方、2つのParquetファイルの列名はamountのままでした。ファイルは書き直されていません。仕様のColumn Projectionに「Columns in Iceberg data files are selected by field id.」とあり、列は名前でなくfield_idで対応づけられるためです。

スナップショットIDによる過去時点の参照

削除前のスナップショットIDをt.scan(snapshot_id=...)に渡すと、削除したorder_id 2を含む3行が返りました。誤った更新の確認や、ある時点の集計の再現に使えます。過去のスナップショットはメタデータとファイルを溜め続けるため、公式のメンテナンス手順は定期的なスナップショットの失効を推奨しています。

IcebergのParquet直接参照による削除済み行と重複行の混入

「中身がParquetなら、Parquetとして読めばよい」と考えるのは失敗のもとです。上の削除と列名変更を終えたテーブルを、2つの方法で読み比べました。コード中の...は省略したパスで、metadata.jsonはt.metadata_locationの値、data配下はテーブルの保存先に置き換えます。

import duckdb
# Iceberg として読む(metadata.json を指定)
duckdb.sql("INSTALL iceberg; LOAD iceberg;")
duckdb.sql("SELECT * FROM iceberg_scan('.../metadata/00003-....metadata.json') ORDER BY 1").fetchall()
# -> (1, tokyo, 1200), (3, tokyo, 500)

# data/ 配下を Parquet として直接読む
duckdb.sql("SELECT * FROM read_parquet('.../data/*.parquet') ORDER BY 1").fetchall()
# -> (1, tokyo, 1200), (1, tokyo, 1200), (2, osaka, 800), (3, tokyo, 500), (3, tokyo, 500)

直接読むと、削除したはずの行が復活し、残った行は二重に数えられました。原因は、data配下にある「今のテーブルに属さない古いファイル」まで拾うことです。PyIceberg 0.12.0で古いスナップショットを失効させた後も、data配下のParquetは2ファイルのまま残りました。Icebergテーブルを、ディレクトリ配下のファイル一覧で読む仕組み(read_parquetのワイルドカード指定や、同じ場所をHive形式の外部テーブルとして登録する方法)で読んではいけません。DuckDBならiceberg_scan、SparkやTrinoならIcebergカタログ経由で読みます。

Icebergと従来のテーブル形式(Hive)の違い

「アイスバーグテーブルは従来のテーブル形式とどう違うのか」で比べられる従来形式は、多くの場合Hiveテーブルです。ここで比較するのは、パーティションに対応するディレクトリ配下のファイル一覧をテーブルの中身とみなす、従来の非ACID Hiveテーブルです。上の失敗例は、まさにこの読み方をIcebergのディレクトリに当てたものです。なお、HiveにもACIDテーブル(トランザクション対応テーブル)があり、こちらはトランザクションの可視性を管理するため下表の比較は当てはまりません。

観点 非ACID Hiveテーブル Icebergテーブル
テーブルの中身の決め方 ディレクトリの一覧 マニフェストに記録したファイル
書き込み途中 途中のファイルが見えうる コミット後にまとめて見える
パーティション 列とディレクトリが一致 変換で隠蔽・後から変更可
スキーマ変更 列名・位置で対応 field_idで対応

Icebergはファイル一覧を取らずにマニフェストの統計でファイルを絞り込むため、ファイル数が多いテーブルでも読み込み計画が一覧取得に引きずられません。パーティションもdays(ts)のような変換で定義でき、利用者はWHERE ts >= ...と書くだけで済みます。既存のHiveテーブルは、Sparkのmigrateプロシージャでデータファイルを書き換えずにIcebergへ移行できます(対応形式はAvro・Parquet・ORC、バケット化されたテーブルは失敗します)。

add_filesによる既存ParquetのIcebergテーブルへの登録

すでにParquetで溜めたデータは、コピーせずにIcebergテーブルへ登録できます。PyIcebergではadd_filesを使います。pyarrowで書いた(field_idを持たない)Parquetを登録した結果が次です。

from pathlib import Path
import pyarrow.parquet as pq

tb = pa.table({
    "order_id": pa.array([10, 11], pa.int64()),
    "region": ["nagoya", "tokyo"],
    "amount": pa.array([300, 900], pa.int64()),
})
Path("/tmp/raw").mkdir(parents=True, exist_ok=True)
pq.write_table(tb, "/tmp/raw/sales_2026_09.parquet")
t = cat.create_table("demo.sales", schema=tb.schema)
t.add_files(["/tmp/raw/sales_2026_09.parquet"])
[task.file.file_path for task in t.scan().plan_files()]
# -> ['/tmp/raw/sales_2026_09.parquet']   テーブル配下の data/ は空のまま

ファイルは移動もコピーもされず、元の場所のままテーブルの一部になりました(更新時刻も変わっていません)。field_idの無いファイルは、PyIcebergのドキュメントのとおりName Mapping(列名からfield_idへの対応表)が自動で作られて読めます。注意点は2つあります。1つは、登録後はIcebergの管理下に入るため、同ドキュメントにあるとおりスナップショットの失効などの保守操作で物理削除されうること。もう1つは、元の場所を別の処理が上書き・削除するとテーブルが壊れることです。Sparkでも同名のadd_filesプロシージャがあり、ファイルを移動せずメタデータだけを作ります。

Parquet単体で足りる場面とIcebergが要る場面

Icebergにはカタログの運用、スナップショットの失効、小さなファイルの統合といった手間が付いてきます。その手間を払う理由があるかで決めます。

  • Parquet単体で足りる:書いたら変えない日次のエクスポート、機械学習の学習データ、1人が1つのツールで読む分析用ファイル。ファイル名に日付を入れて追記するだけなら、テーブルフォーマットは要りません。
  • Icebergが要る:行の更新・削除がある(個人データの削除依頼への対応を含む)、Sparkで書いてTrinoやAthenaで読むなど複数エンジンが同じデータを触る、書き込み中も読み手に一貫した状態を見せたい、過去時点の集計を再現したい、運用中に列を変えたい。

避けたいのは、更新のあるデータをParquet単体で抱えて「上書き用のスクリプト」を自作するパターンです。書き直し中に読んだジョブが半端な結果を返し、列名を変えた日に古いファイルとの結合が崩れます。逆に、数MBの静的なファイルにIcebergを載せてもカタログ運用の手間が増えるだけです。データレイク全体の設計から検討する場合は、一創のデータ分析基盤構築・MLOps構築支援でも相談を受けています。手元で試すならDuckDBとは?特徴・用途とSQLite・PostgreSQLとの違いを実測で解説【1.5.6対応】の環境に、この記事のiceberg拡張を足すのが手軽です。

Iceberg・Delta Lake・Hudiとの関係

Parquetの上に載るテーブルフォーマットはIcebergだけではありません。Delta Lake(2026年10月5日に4.4.1)とApache Hudi(2026年9月24日に1.2.1)も、データファイルとテーブル状態を管理する情報を組み合わせます。Delta LakeはParquetを使用します。HudiはParquet以外のベースファイル形式にも対応し、Merge-on-Readではベースファイルと更新ログを組み合わせて読みます。「Parquetか、Icebergか」で迷っている場合、実際に比べるべき相手はこの2つです。3者の違いと選び方はオープンテーブルフォーマットとは?Iceberg・Delta Lake・Hudiの違いと選び方を実装視点で解説【2026年版】で、個別の仕組みはDelta Lakeとは?データレイクに信頼性を与えるオープンテーブルフォーマットを実装視点で解説とApache Hudiとは?レコード単位の更新と増分取り込みを支える仕組みを実装視点で解説【2026年版】で解説しています。

Icebergの最新版と仕様v3・v4の状況(2026年10月時点)

実装の最新はIceberg 1.12.0で、2026年9月29日にリリースされました。Spark 3.4とFlink 2.0の対応が外れ、配布されるランタイムはSpark 3.5・4.0・4.1、Flink 1.20・2.1・2.2・2.3向けです。Spark 3.4のまま1.12.0へ上げることはできません。

仕様のバージョン(format-version)は実装の版番号とは別物です。Table Specは「Versions 1, 2 and 3 of the Iceberg spec are complete and adopted by the community. Version 4 is under active development and has not been formally adopted.」としています。テーブルの既定はv2のままで、v3は明示して選びます。

仕様 状態 主な追加
v1 確定 Parquet・Avro・ORCの分析テーブル
v2 確定・既定 行単位の削除(削除ファイル)
v3 確定 variant・geometry・ナノ秒時刻・削除ベクトル・行の来歴
v4 策定中 メタデータ構造の再設計・相対パス

v3はエンジンごとに対応が分かれます。2026年10月時点の公式ドキュメントと手元の実測を並べると次のとおりです。

エンジン・サービス v3の扱い
Snowflake 2026年5月7日にGA
Databricks 2026年5月21日にGA(Runtime 18 LTS以上)
AWS Glue 6.0 v2・v3に対応、既定はv2
Amazon Athena v2のみ作成・操作
PyIceberg 0.12.0 v3への書き込みは未対応(実測)

出典はSnowflakeのリリースノート、DatabricksのIceberg v3ガイド、AWS Glueのガイド、Athenaのガイドです。PyIceberg 0.12.0でformat-versionを3にしたテーブルへ書き込むと「NotImplementedError: Writing V3 is not yet supported」で止まりました。Glueのガイドにあるとおりv2からv3への引き上げはデータファイルを書き換えないメタデータ変更ですが、Athenaのガイドは作成・操作の対象をv2のテーブルに限っており、v3へ上げたテーブルを同じように扱える保証はありません。削除ベクトルやvariant型を使う予定があっても、そのテーブルに触る全エンジンがv3に対応するまでv2のまま置くのが安全です。AWSで扱う場合の選択肢はAmazon S3 Tablesとは?テーブルバケットの仕組みと東京リージョンの料金・作成手順【2026年版】、Aurora PostgreSQLから読む方法はaurora_analyticsとは?Aurora PostgreSQLからS3のIceberg・Parquetを直接読む設定手順と採用判断にあります。

よくある質問

IcebergとParquetはどちらを選ぶべきですか?

どちらか一方を選ぶものではありません。Icebergテーブルのデータファイルは既定でParquetです。決めるのは「Parquetファイルの上にIcebergを載せるかどうか」で、更新・削除、複数エンジンからの読み書き、過去時点の参照のどれかが要るなら載せます。

アイスバーグテーブルは従来のテーブル形式とどのように異なりますか?

従来の非ACID Hiveテーブルはディレクトリ配下のファイル一覧を読みますが、Icebergはテーブルに属するファイルをマニフェストに1つずつ記録します。そのため書き込みがコミット単位で反映され、列はfield_idで対応づけられ、パーティションを後から変更できます。

IcebergはParquet以外のファイル形式も使えますか?

使えます。仕様はParquet・Avro・ORCに対応し、write.format.defaultでテーブルごとに選べます。既定値はparquetです。

Parquetの読み方と意味は?

「パーケイ」または「パーケット」と読まれます。英語のparquetは寄せ木張りの床を指す語です。データ形式としては列指向のファイルフォーマットの名前です。

IcebergテーブルはPythonやDuckDBから読めますか?

読めます。PythonならPyIcebergのtable.scan().to_arrow()、DuckDBならiceberg拡張のiceberg_scanにmetadata.jsonなどのパスを指定します。RESTカタログ経由では、カタログをATTACHしてテーブル名でクエリします。data配下のParquetをread_parquetで直接読むと、削除済みの行や重複が混ざります。

関連記事

お気に入りに入れた記事の一覧

この記事は以下の記事からリンクされています

ほか 2 件の記事からもリンクされています。

資料請求

今日のトレンド記事 直近 24 時間で、いつもより多く読まれている記事

  1. 2026.10.27 テックブログ Brotli 脆弱性:CVE-2026-111497の影響確認とNode.js・Python・.NETの更新手順
  2. 2026.10.28 テックブログ リンツ不正アクセス:314万件の対象確認とパスワード強制リセット後の対処
  3. 2026.10.27 テックブログ Joshin(ジョーシン)不正アクセス:最大600万件の対象とサブスク初期パスワードの対処
  4. 2026.10.29 テックブログ So-net情報漏洩:258万件の対象確認とメールのパスワード再設定、窓口の番号
  5. 2026.10.23 テックブログ Renta!はいつ再開?不正アクセスでメールアドレス等が流出したおそれ・再開時のパスワード再設定と運営側の対応

RELATED POSTS 関連記事

目次