2026年のオープンテーブルフォーマット: クラウドオブジェクトストレージにおけるApache Iceberg v2 vs Delta Lake 3.0

目次(11 項目)
データレイクハウス・アーキテクチャは、クラウドオブジェクトストレージ(S3、GCS、Azure Blob Storage)を基盤として活用し、分析ワークロードのデファクトスタンダードとなっています。この進化の中心にあるのが、オープンなテーブルフォーマットであるApache IcebergとDelta Lakeです。これらのフォーマットは、リレーショナルデータベースに伝統的に関連付けられてきたACIDトランザクション、スナップショット分離、タイムトラベル機能をオブジェクトストレージにもたらします。このガイドでは、2026年時点のApache Iceberg(v2仕様)とDelta Lake(UniForm対応の3.0)を詳細に分析し、技術的な比較とアーキテクチャに関する洞察を提供します。
基本原則:オブジェクトストレージ上のACID
IcebergとDelta Lakeはどちらも、データファイルの変更を追跡するトランザクションログまたはメタデータツリーを維持することでACID特性を実現しています。データファイル自体は不変です。更新、削除、挿入は、新しいデータファイルを書き込み、新しい状態を反映するようにメタデータを更新し、古いファイルを論理的に削除済みとしてマークすることで処理されます。このコピーオンライト(CoW)アプローチは、追記専用のオブジェクトストレージ上での動作の基本です。
Apache Iceberg v2:階層型メタデータと進化したパーティショニング
Icebergのアーキテクチャは、階層型メタデータ構造を中心に構築されています。
- カタログ: テーブルの現在のメタデータポインタを保存します。これはHive Metastore、Nessie、AWS Glue、またはカスタムカタログにすることができます。
- メタデータファイル: マニフェストリストのリストを指します。コミットごとに新しいメタデータファイルが生成されます。
- マニフェストリスト: マニフェストファイルのリストを指します。各マニフェストリストはテーブルのスナップショットを表します。
- マニフェストファイル: データファイルのエントリ(パス、パーティション値、統計情報(最小/最大値、NULLカウントなど))を含みます。
- データファイル: 実際のデータを含むParquet、ORC、Avroファイル。
この構造により、クエリ計画中の効率的なプルーニングが可能になります。Iceberg v2は、特にスキーマとパーティションの進化、および行レベルの操作に関して、機能を大幅に強化しています。
パーティションの進化
Icebergのパーティション進化機能により、既存のデータを書き換えることなく、時間の経過とともにパーティショニングスキームを変更できます。これは、データアクセスパターンが進化する長期間使用されるテーブルにとって重要です。
-- Example: Evolving a table's partitioning in Iceberg
-- Initial table creation
CREATE TABLE my_iceberg_table (
id BIGINT,
event_time TIMESTAMP,
value STRING
)
USING iceberg
PARTITIONED BY (days(event_time));
-- Later, evolve partitioning to hourly
ALTER TABLE my_iceberg_table
ADD PARTITION FIELD hours(event_time);
-- Existing data remains partitioned by days, new data by hours.
-- Queries automatically handle both partition schemes.
行レベルの削除と更新(コピーオンライト vs. マージオンリード)
Iceberg v2では、行レベルの操作に2つの主要なアプローチが導入されています。
- コピーオンライト(CoW): 従来のメソッドです。削除または更新の場合、影響を受けるデータファイルが書き換えられ、ターゲット行が除外または変更されます。これは、変更が少ない大きなファイルの場合、I/O負荷が高くなる可能性があります。
- 削除ファイルによるマージオンリード(MoR): Iceberg v2は「削除ファイル」(位置指定削除または等価性削除)をサポートしています。
- 位置指定削除ファイル: データファイル内で削除する行のファイルパスと位置(行番号)を保存します。
- 等価性削除ファイル: 削除する行を一意に識別する列の値を保存します。 クエリ時に、エンジンはデータファイルを読み取り、削除ファイルを適用して行をフィルタリングします。これにより、書き換えコストが読み取り時に繰り延べられます。
// Example: Writing an Iceberg table with Spark and performing a delete
import org.apache.spark.sql.SparkSession
import org.apache.spark.sql.functions._
val spark = SparkSession.builder()
.appName("IcebergDeleteExample")
.config("spark.sql.catalog.local", "org.apache.iceberg.spark.SparkSessionCatalog")
.config("spark.sql.catalog.local.type", "hadoop")
.config("spark.sql.catalog.local.warehouse", "/tmp/iceberg_warehouse")
.getOrCreate()
spark.sql("CREATE NAMESPACE IF NOT EXISTS local.db")
spark.sql("""
CREATE TABLE local.db.my_iceberg_table (
id BIGINT,
data STRING,
ts TIMESTAMP
) USING iceberg
PARTITIONED BY (days(ts))
""")
// Insert initial data
spark.sql("""
INSERT INTO local.db.my_iceberg_table VALUES
(1, 'value_a', TIMESTAMP '2026-01-01 10:00:00'),
(2, 'value_b', TIMESTAMP '2026-01-01 11:00:00'),
(3, 'value_c', TIMESTAMP '2026-01-02 12:00:00')
""")
// Perform a delete operation (CoW by default, but can be configured for MoR with delete files)
// For MoR, ensure the table property 'format-version' is 2 and 'write.delete.mode' is 'merge-on-read'
spark.sql("ALTER TABLE local.db.my_iceberg_table SET TBLPROPERTIES ('format-version'='2', 'write.delete.mode'='merge-on-read')")
spark.sql("DELETE FROM local.db.my_iceberg_table WHERE id = 2")
// Verify the delete
spark.sql("SELECT * FROM local.db.my_iceberg_table").show()
// Expected output: rows with id 1 and 3
Delta Lake 3.0:トランザクションログとUniForm
Delta Lakeの核となるのは、データファイルとともに_delta_logディレクトリに保存される一連のJSONファイル(および効率化のためのParquetチェックポイント)であるトランザクションログです。ログの各エントリはアトミックなコミットを表し、データファイルの追加と削除の詳細を記述します。
UniForm(ユニバーサルフォーマット)
Delta Lake 3.0では、相互運用性のための重要な機能であるUniFormが導入されました。UniFormは、Delta Lakeのネイティブトランザクションログに加えて、IcebergおよびHudiのメタデータを生成します。これにより、IcebergまたはHudiをネイティブに理解するエンジンは、Delta Lakeコネクタを必要とせずにDeltaテーブルを読み取ることができます。これは、真のオープンテーブルフォーマットの相互運用性に向けての重要な一歩です。
// Example: Creating a Delta table with UniForm enabled in Spark
import org.apache.spark.sql.SparkSession
val spark = SparkSession.builder()
.appName("DeltaUniFormExample")
.config("spark.sql.extensions", "io.delta.sql.DeltaSparkSessionExtension")
.config("spark.sql.catalog.spark_catalog", "org.apache.spark.sql.delta.catalog.DeltaCatalog")
.getOrCreate()
// Create a Delta table with UniForm enabled
spark.sql("""
CREATE TABLE my_delta_table (
id BIGINT,
data STRING,
ts TIMESTAMP
) USING DELTA
TBLPROPERTIES (
'delta.enableIcebergCompatV2' = 'true',
'delta.universalFormat.enabledFeatures' = 'iceberg'
)
PARTITIONED BY (CAST(ts AS DATE))
""")
// Insert data
spark.sql("""
INSERT INTO my_delta_table VALUES
(1, 'delta_val_a', TIMESTAMP '2026-01-01 10:00:00'),
(2, 'delta_val_b', TIMESTAMP '2026-01-01 11:00:00')
""")
// Now, this Delta table can be read by Iceberg-compatible engines
// (e.g., Trino, DuckDB) directly, without Delta Lake specific connectors.
// The Iceberg metadata is automatically generated and kept in sync.
パーティショニングとスキーマの進化
Delta Lakeは、スキーマの進化(列の追加/削除、型の変更)とパーティションの進化もサポートしています。パーティショニングは通常、テーブル作成時に定義され、テーブルを書き換えることで変更できます。
-- Example: Delta Lake schema evolution
ALTER TABLE my_delta_table ADD COLUMNS (new_col INT);
行レベルの削除と更新
Delta Lakeは、DELETE、UPDATE、およびMERGE操作に主にCoWアプローチを使用します。影響を受けるファイルを特定し、変更を加えて書き換え、新しいファイルをトランザクションログに記録します。頻繁に小さな更新が行われる大規模なテーブルの場合、これによりファイル増幅とパフォーマンスオーバーヘッドが発生する可能性があります。Databricksは、これらの問題の一部を軽減するために「リキッドクラスタリング」と「Photonアクセラレーテッドライト」を導入しましたが、コアメカニズムはCoWのままです。
エンジン間の相互運用性
さまざまなエンジンからテーブルをクエリできることは、レイクハウスの基礎です。
Trino
Trino(旧PrestoSQL)は、IcebergとDelta Lakeの両方に対して堅牢なコネクタを備えています。
-- Trino: Querying an Iceberg table
-- Catalog configuration (e.g., in etc/catalog/iceberg.properties)
-- connector.name=iceberg
-- iceberg.catalog.type=rest # or hive, glue, hadoop
SELECT * FROM iceberg.db.my_iceberg_table WHERE id = 1;
-- Trino: Querying a Delta Lake table (with UniForm, via Iceberg connector)
-- Assuming UniForm is enabled on the Delta table, Trino's Iceberg connector
-- can read it directly.
SELECT * FROM iceberg.db.my_delta_table WHERE id = 1;
-- Trino: Querying a Delta Lake table (via Delta Lake connector)
-- Catalog configuration (e.g., in etc/catalog/delta.properties)
-- connector.name=delta_lake
-- delta.catalog.type=glue # or hive, hadoop
SELECT * FROM delta.db.my_delta_table WHERE id = 1;
Apache Spark
Sparkは、IcebergとDelta Lakeの両方にとってネイティブな環境です。
// Spark: Reading Iceberg
spark.read.format("iceberg").load("local.db.my_iceberg_table").show()
// Spark: Reading Delta Lake
spark.read.format("delta").load("/path/to/my_delta_table").show()
DuckDB
DuckDBは、インプロセスOLAPデータベースであり、優れたローカル分析機能を提供します。
# Python with DuckDB: Reading Iceberg
import duckdb
con = duckdb.connect(database=':memory:', read_only=False)
con.execute("INSTALL iceberg;")
con.execute("LOAD iceberg;")
# Point to the Iceberg table's metadata file or catalog
# For local Iceberg table created by Spark in /tmp/iceberg_warehouse/db/my_iceberg_table
con.execute("CREATE OR REPLACE TABLE my_iceberg_table AS SELECT * FROM iceberg_scan('/tmp/iceberg_warehouse/db/my_iceberg_table/metadata/v*.metadata.json');")
con.execute("SELECT * FROM my_iceberg_table;").fetchdf()
# Python with DuckDB: Reading Delta Lake (with UniForm, via Iceberg extension)
# Assuming UniForm is enabled on the Delta table
con.execute("INSTALL delta;") # Delta extension is also available
con.execute("LOAD delta;")
con.execute("CREATE OR REPLACE TABLE my_delta_table AS SELECT * FROM delta_scan('/path/to/my_delta_table');")
con.execute("SELECT * FROM my_delta_table;").fetchdf()
# If UniForm is enabled, you might be able to use iceberg_scan directly on the Delta path
# (This depends on the exact implementation of DuckDB's Iceberg connector and UniForm's metadata sync)
# con.execute("CREATE OR REPLACE TABLE my_delta_table_as_iceberg AS SELECT * FROM iceberg_scan('/path/to/my_delta_table');")
アーキテクチャの比較とトレードオフ
| 機能 | Apache Iceberg v2 | Delta Lake 3.0 (UniForm) |
|---|---|---|
| メタデータ構造 | 階層型(カタログ -> メタデータファイル -> マニフェストリスト -> マニフェストファイル -> データファイル) | トランザクションログ(JSONファイル、Parquetチェックポイント) |
| 行レベル操作 | CoW、MoR(位置指定/等価性削除ファイル) | 主にCoW(ファイルを書き換え) |
| パーティションの進化 | ネイティブ、シームレス(フィールドの追加/削除、変換の変更) | 大幅な変更にはテーブルの書き換えが必要 |
| スキーマの進化 | 列の追加/削除、型の変更 | 列の追加/削除、型の変更 |
| 相互運用性 | オープン仕様、複数の言語実装(Java、Python、Rust、C++) | オープン仕様だが、ネイティブエンジンサポートにはDelta Lakeコネクタが必要な場合が多い。UniFormはIceberg/Hudiに橋渡しする。 |
| カタログオプション | Hive Metastore、Nessie、AWS Glue、カスタムREST | Hive Metastore、AWS Glue、Databricks Unity Catalog |
| オープンソースガバナンス | Apache Foundation | Linux Foundation、Databricksの影響を強く受ける |
| ファイル形式 | Parquet、ORC、Avro | Parquet |
| ユースケースの強み | 複雑なデータパイプライン、進化するスキーマ/パーティション、多様なエンジンエコシステム、頻繁な小規模更新に対するMoR | Databricksエコシステム、強力なCoWパフォーマンスチューニング、より広範なリーチのためのUniForm |
トレードオフ
- メタデータオーバーヘッド: Icebergの階層型メタデータは、非常に大きなテーブルで多くのパーティションと頻繁なコミットがある場合、より多くの小さなファイル(マニフェスト)につながる可能性があります。ただし、そのプルーニング機能は非常に効率的です。Delta Lakeのトランザクションログはよりシンプルですが、非常に大きくなる可能性があり、チェックポイント処理が必要になります。
- 行レベル操作: Icebergの削除ファイルによるMoRは、CoWに代わる魅力的な選択肢であり、特に少数の行に対して更新/削除が頻繁に行われるテーブルの場合、書き込み増幅を減らします。Delta LakeのCoWはバッチ操作では非常に高性能ですが、きめ細かい変更の場合、書き換えコストが高くなる可能性があります。
- 相互運用性: Icebergの設計は、当初から本質的にオープンでエンジンに依存しません。Delta LakeのUniFormは、Iceberg互換のメタデータを生成することで、同様の広範な相互運用性を実現するための戦略的な動きであり、DeltaテーブルをIcebergエンジンで読み取れるようにします。これはエコシステムにとって大きな勝利です。
- コミュニティとエコシステム: どちらも強力で活発なコミュニティを持っています。IcebergはApache Foundationのガバナンスの恩恵を受け、多様なエコシステムを育成しています。Delta Lakeはオープンソースですが、Databricksとの強い結びつきがあり、その開発と機能セットの多くを推進しています。
本番環境での注意点とトラブルシューティング
-
スモールファイル問題(IcebergとDelta Lakeの両方): 頻繁な小さな追記や更新は、小さなデータファイルの爆発的な増加につながる可能性があります。これにより、メタデータ処理とI/Oオーバーヘッドが増加するため、クエリパフォーマンスが低下します。
- 解決策: コンパクションジョブ(例:Deltaの場合はSpark
OPTIMIZE、Icebergの場合はrewrite_data_filesアクション)を実装して、小さなファイルを大きなファイルにマージします。これらを定期的にスケジュールします。
scala// Delta Lake compaction spark.sql("OPTIMIZE my_delta_table ZORDER BY (event_time)") // Iceberg compaction (Spark example) import org.apache.iceberg.spark.actions.SparkActions SparkActions.get(spark).rewriteDataFiles(spark.table("local.db.my_iceberg_table")).execute() - 解決策: コンパクションジョブ(例:Deltaの場合はSpark
-
メタデータ肥大化(IcebergとDelta Lakeの両方): 時間の経過とともに、トランザクションログ(
_delta_log)またはIcebergのメタデータファイルが非常に大きくなり、コミット時間とクエリ計画に影響を与える可能性があります。- 解決策: 古いスナップショットの保持ポリシーを設定します。Delta Lakeの場合は、保持期間を指定して
VACUUMを使用します。Icebergの場合は、expire_snapshotsアクションを使用します。
scala// Delta Lake vacuum (retains 7 days by default, can be configured) spark.sql("SET spark.databricks.delta.retentionDurationCheck.enabled = false") // Disable safety check for testing spark.sql("VACUUM my_delta_table RETAIN 0 HOURS") // DANGER: Deletes files not referenced by current snapshot spark.sql("SET spark.databricks.delta.retentionDurationCheck.enabled = true") // Iceberg expire snapshots (retains last N snapshots or snapshots newer than a timestamp) SparkActions.get(spark).expireSnapshots(spark.table("local.db.my_iceberg_table")) .retainLast(10) // Retain last 10 snapshots .execute() - 解決策: 古いスナップショットの保持ポリシーを設定します。Delta Lakeの場合は、保持期間を指定して
-
スキーマ進化の競合: どちらもスキーマ進化をサポートしていますが、予期しないデータ型や列名の変更が問題を引き起こす可能性があります。
- 解決策: 書き込み前に、受信データをテーブルスキーマに対して検証します。スキーマ進化でUDFの問題が発生した場合は、Sparkで
spark.sql.legacy.allowUntypedScalaUDFを使用します。Icebergの場合、schema.auto.mergeを本番環境で慎重な検討なしに安易に有効にしないようにしてください。
- 解決策: 書き込み前に、受信データをテーブルスキーマに対して検証します。スキーマ進化でUDFの問題が発生した場合は、Sparkで
-
オブジェクトストレージのレート制限/スロットリング: 高い同時実行性や頻繁な小さな操作は、オブジェクトストレージAPIの制限(例:S3のプレフィックスあたり毎秒5000 PUT/GETリクエスト)に達する可能性があります。
- 解決策: より大きなファイルサイズで書き込みを最適化します。データ取り込みパイプラインで指数バックオフと再試行ロジックを実装します。可能であれば、より多くのS3プレフィックスにデータを分散させます。クラウドプロバイダーのメトリクスでスロットリングを監視します。
-
一貫性のない読み取り(タイムトラベル): タイムトラベルを明示的に使用しない場合、クエリが計画を開始した直後、かつデータファイルを読み取る前に同時書き込みがコミットされると、古いデータが読み取られる可能性があります。
- 解決策: 厳密な一貫性が必要な重要な分析クエリの場合、タイムトラベルを使用して特定のスナップショットIDまたはタイムスタンプをクエリします。
sql-- Delta Lake time travel SELECT * FROM my_delta_table VERSION AS OF 5; SELECT * FROM my_delta_table TIMESTAMP AS OF '2026-01-01 12:00:00'; -- Iceberg time travel SELECT * FROM local.db.my_iceberg_table FOR SYSTEM_VERSION AS OF 1234567890123456L; -- Snapshot ID SELECT * FROM local.db.my_iceberg_table FOR SYSTEM_TIME AS OF '2026-01-01 12:00:00';
よくある質問
-
IcebergとDelta Lakeのどちらを選ぶべきですか? Icebergを選択すべきなのは、真にオープンでベンダーに依存しないエコシステムを優先する場合、高度なパーティション進化が必要な場合、または頻繁な行レベルの更新/削除に対して効率的なMoRが必要な場合です。 Delta Lakeを選択すべきなのは、Databricksエコシステムに深く投資している場合、よりシンプルなトランザクションログモデルを好む場合、またはDatabricks固有のパフォーマンス最適化(例:Photon、Liquid Clustering)の恩恵を受ける場合です。UniFormにより、Delta LakeとIcebergの相互運用性のギャップは大幅に縮小されました。
-
UniFormはIcebergとDelta Lakeの選択にどのように影響しますか? UniFormにより、Delta LakeテーブルはIceberg互換エンジンで読み取れるようになります。これにより、Delta Lakeに対する「ベンダーロックイン」の懸念が大幅に軽減されます。Icebergのより広範なエンジンサポートが主な懸念事項であった場合、UniFormはそれを大きく緩和します。ただし、UniFormは現在、読み取り専用のIceberg互換性しか提供しておらず、書き込みには依然としてDelta Lakeが必要です。
-
行レベル操作におけるIcebergのMoRとDelta LakeのCoWのパフォーマンスへの影響は何ですか? IcebergのMoRは、書き換えコストを読み取り時に繰り延べることで、頻繁な小規模更新/削除に対してより良い書き込みパフォーマンスを提供できます。これは、書き込み増幅が懸念される場合に有利です。ただし、MoRはエンジンが削除ファイルを適用する必要があるため、読み取りオーバーヘッドを発生させる可能性があります。Delta LakeのCoWは常にクリーンな読み取りを保証しますが、きめ細かい変更の場合、より高い書き込み増幅とI/Oにつながる可能性があります。最適な選択は、読み取り/書き込みパターンとレイテンシ要件によって異なります。
-
既存のHiveテーブルをIcebergまたはDelta Lakeに移行できますか? はい、どちらのフォーマットも移行ツールを提供しています。Icebergの場合、アンマネージドテーブルにはSparkの
ALTER TABLE ... SET TBLPROPERTIES ('format'='iceberg')を使用するか、migrateアクションを使用できます。Delta Lakeの場合、CONVERT TO DELTAコマンドはParquetまたはHiveテーブルをインプレースで変換できます。常に、まずデータの一部で移行を徹底的にテストしてください。 -
これらのテーブルフォーマットにおけるカタログの役割は何ですか? カタログは非常に重要です。テーブルの中央レジストリとして機能し、現在のメタデータポインタ(Icebergの場合)またはベースパス(Delta Lakeの場合)を保存します。カタログがなければ、エンジンはテーブルの状態を特定して解釈できません。一般的なカタログには、Hive Metastore、AWS Glue Data Catalog、Project Nessie(Iceberg用)、Databricks Unity Catalog(Delta Lake用)などがあります。
Free In-Browser Developer Tools
Clean AI CLI logs, build cron expressions, decode JWTs, and calculate chmod permissions offline.
Related Articles

2026年のカラム型データ: Apache Arrowメモリレイアウト、Parquetストレージ、DuckDBベクタライゼーション
2026年のカラム型データについて、Apache Arrowメモリレイアウト、Parquetストレージ、DuckDBベクタライゼーションを、本番環境レベルのアーキテクチャとコード例で網羅的に解説する総合ガイドです。
Read more
Serverlessアーキテクチャの隠れた落とし穴
2026年のServerlessアーキテクチャにおけるコールドスタートレイテンシー、データベース接続枯渇、予期せぬクラウド費用といった隠れた落とし穴と、その対策について解説します。
Read more
2026年のEdgeComputing: 実世界におけるアーキテクチャパターンとユースケース
CDNの枠を超えて成熟したEdgeComputingが、リアルタイムAI推論から分散型マルチプレイヤーゲームまで、現代のアプリケーションをどのように支えているかを探ります。
Read more