はじめに
Databricksでは、Unity Catalogのマネージドテーブルとして Apache Iceberg テーブルを作れるようになっています。ドキュメントは2026年7月6日に更新されています。
CREATE TABLE に USING ICEBERG を付けるだけでマネージド Iceberg テーブルになります。手軽に作れる一方で、Deltaテーブルと同じ感覚で触ると引っかかる箇所がいくつかありました。
同じデータからIcebergテーブルとDeltaテーブルを並べて作り、上から順に確かめたノートブックを作りました。この記事はその結果です。ドキュメントに書かれていない挙動もいくつか出てきたので、そのあたりも含めて書いていきます。
検証環境
サーバレスノートブック(環境バージョン5、Spark 4.2.0)を使いました。
ドキュメントには「サーバレスコンピュートが有効なワークスペース」という要件がありますが、これは実行するコンピュートの話ではありません。Databricksがマネージド Iceberg テーブルのメタデータ最適化を裏で実行するための要件で、ノートブックをどのコンピュートで動かすかとは別です。
Iceberg v3を扱うセクションだけは Databricks Runtime 18 LTS 以降が必要です。サーバレスの環境バージョン5がDBR 18系統相当なので、これを選べば全部の要件を満たします。
なお、サーバレスではランタイムのバージョンが spark.conf から取れません。spark.databricks.clusterUsageTags.sparkVersion も spark.databricks.environmentVersion も存在しない扱いになります。記事でバージョンを明記したい場合は、クラシックコンピュートでDBRを固定したほうが書きやすいです。
ソースデータは samples.nyctaxi.trips を使いました。
CREATE TABLE trips_iceberg
USING ICEBERG
AS SELECT * FROM samples.nyctaxi.trips;
CREATE TABLE trips_delta
AS SELECT * FROM samples.nyctaxi.trips;
USING ICEBERG を付けなければ従来どおりDeltaテーブルです。この2本を並べて、同じ操作を両方に投げていきます。
素性の見え方
カタログエクスプローラーで見ると、タイプが「マネージド」、データソースが「Iceberg」と表示されます。Unity Catalogのマネージドテーブルとして扱われていて、その中でフォーマットがIcebergになっている、という関係がここで分かります。
SQLからは DESCRIBE EXTENDED のProvider行で区別できます。
for t in ["trips_iceberg", "trips_delta"]:
rows = spark.sql(f"DESCRIBE EXTENDED {t}").collect()
provider = [r["data_type"] for r in rows if r["col_name"] == "Provider"]
print(f"{t:<16}: Provider = {provider[0]}")
trips_iceberg : Provider = iceberg
trips_delta : Provider = delta
DESCRIBE DETAIL はどちらでも通ります。
一方で SHOW CREATE TABLE はIcebergテーブルで通りませんでした。
[MANAGED_ICEBERG_OPERATION_NOT_SUPPORTED] Managed Iceberg tables do not support SHOW CREATE TABLE.
ドキュメントの制限事項セクションには載っていない項目です。Deltaテーブルでは通常どおりDDLが返るので、再作成用のDDLを控えておく運用をしている場合は、Iceberg側で代替手段を考える必要があります。
テーブル機能が入っていない
両者の差が一番はっきり出たのは、テーブルプロパティでした。Deltaの「テーブル機能」は delta.feature. で始まるプロパティとして見えます。
for t in ["trips_iceberg", "trips_delta"]:
rows = spark.sql(f"SHOW TBLPROPERTIES {t}").collect()
# delta.feature. で始まるプロパティがテーブル機能
features = sorted(
r["key"].replace("delta.feature.", "")
for r in rows if r["key"].startswith("delta.feature.")
)
print(f"{t:<16}: {features or '(なし)'}")
結果はこうなりました。
trips_iceberg : (なし)
trips_delta : ['appendOnly', 'deletionVectors', 'invariants']
Iceberg側にはテーブル機能が1つも入っていません。この違いが、このあと出てくる制約のほとんどを説明します。
普通に使える操作
まず、問題なく使えたものから挙げます。
UPDATE、DELETE、MERGE はそのまま動きます。MERGE については、結合キーに対してソース側の行が一意でないとエラーになる点はDeltaと同じです。samples.nyctaxi.trips は乗車時刻と降車時刻の組が重複しうるので、ソース側を先に重複排除しておきます。
MERGE INTO trips_iceberg AS t
USING (
SELECT * EXCEPT (rn) FROM (
SELECT *,
row_number() OVER (
PARTITION BY tpep_pickup_datetime, tpep_dropoff_datetime
ORDER BY fare_amount DESC
) AS rn
FROM samples.nyctaxi.trips
) WHERE rn = 1
LIMIT 100
) AS s
ON t.tpep_pickup_datetime = s.tpep_pickup_datetime
AND t.tpep_dropoff_datetime = s.tpep_dropoff_datetime
WHEN MATCHED THEN UPDATE SET t.fare_amount = s.fare_amount
WHEN NOT MATCHED THEN INSERT *;
DESCRIBE HISTORY もそのまま使えます。Icebergはスナップショットで履歴を管理する仕組みですが、Databricks上ではDeltaと同じく0から始まる連番のバージョンが並びます。
タイムトラベルも VERSION AS OF と TIMESTAMP AS OF の両方が使えます。
スキーマ進化は、列の追加、名前変更、コメントの付与、削除がすべて通りました。Icebergが売りにしている機能なので、ここは期待どおりです。
OPTIMIZE と ANALYZE も動きます。
タイムトラベルで時刻を指定するとき
TIMESTAMP AS OF に実行時刻を基準にした相対時刻を渡すと、扱いにくいことになります。
current_timestamp() をそのまま渡すと、最新コミットより後の時刻を指してしまいます。
[DELTA_TIMESTAMP_GREATER_THAN_COMMIT] Timestamp 2026-09-18 05:40:23.833369 is
after the table's latest version (2026-09-18 05:40:17.082).
かといって current_timestamp() - INTERVAL 1 MINUTE にすると、テーブルを作った直後なら今度は最初のコミットより前を指します。検証用のノートブックでは、作ってすぐ読むので両方向に外れます。
履歴から実在するコミット時刻を取り出して指定するのが確実でした。
rows = spark.sql("DESCRIBE HISTORY trips_iceberg").orderBy("version").collect()
commit_times = [(r["version"], r["timestamp"]) for r in rows]
oldest_version, oldest_ts = commit_times[0]
display(spark.sql(
f"SELECT count(*) AS row_count FROM trips_iceberg TIMESTAMP AS OF '{oldest_ts}'"
))
Deltaでも同じ話なのですが、検証コードを書くときに引っかかりやすいところです。
使えなかった操作
ここからが本題です。同じDDLをIcebergとDeltaの両方に投げて比べました。
生成列
Icebergで失敗、Deltaで成功でした。
[DELTA_ICEBERG_WRITER_COMPAT_VIOLATION.INCOMPATIBLE_TABLE_FEATURE]
The validation of IcebergWriterCompatV1 has failed.
IcebergWriterCompatV1 is incompatible with feature generatedColumns.
CHECK制約
Databricksでは CHECK 制約を CREATE TABLE の中に書けません。インラインで書くとIcebergでもDeltaでも同じエラーになり、両者の差が見えなくなります。ALTER TABLE ADD CONSTRAINT で後から付ける形にすると差が出ました。
CREATE TABLE cmp_iceberg (id INT, amount DOUBLE) USING ICEBERG;
ALTER TABLE cmp_iceberg ADD CONSTRAINT amount_positive CHECK (amount >= 0);
テーブルの作成は通りますが、制約の追加で失敗します。
[DELTA_ICEBERG_WRITER_COMPAT_VIOLATION.INCOMPATIBLE_TABLE_FEATURE]
IcebergWriterCompatV1 is incompatible with feature checkConstraints.
一方で NOT NULL はIcebergでも通りました。ドキュメントの制限事項には「制約」とまとめて書かれていますが、NOT NULL と CHECK 制約では扱いが違います。
照合順序
[DELTA_ICEBERG_WRITER_COMPAT_VIOLATION.UNSUPPORTED_DATA_TYPE]
IcebergWriterCompatV1 does not support the data type string collate UTF8_LCASE
in your schema.
Delta側では通ります。
シャロークローン
ディープクローンは使えますが、シャロークローンは使えません。ドキュメントの記述どおりでした。
[MANAGED_ICEBERG_OPERATION_NOT_SUPPORTED] Managed Iceberg tables do not support SHALLOW CLONE.
なお、クローン時にフォーマットを指定することはできません。USING ICEBERG と DEEP CLONE を同じ文に書くと構文エラーになります。指定する余地がないので、クローン先のフォーマットはソースに従います。実際にProviderを確認すると、Deltaをクローンすれば delta、Icebergをクローンすれば iceberg になりました。
パーティション進化
Icebergの売りの1つであるパーティション進化は、Databricks SQLからは操作できません。ドキュメントにも「外部のIcebergエンジンからのみサポート」と書かれています。実際に投げると、構文の時点で弾かれます。
[PARSE_SYNTAX_ERROR] Syntax error at or near 'FIELD': missing '('.
ALTER TABLE ... ADD PARTITION FIELD という構文自体をパーサーが受け付けていない、ということです。
式ベースのパーティション分割も使えませんでした。ただしこれはIceberg固有ではなく、IcebergでもDeltaでもまったく同じエラーになります。
[DELTA_OPERATION_NOT_ALLOWED] Operation not allowed:
`Partitioning by expressions` is not supported for Delta tables.
Iceberg側にも「Delta tables」と出ている点が、後述する実装の話につながります。
リキッドクラスタリングは一手間かかる
ドキュメントには、マネージド Iceberg テーブルでもリキッドクラスタリングと予測的最適化が使えると書かれています。ただし、そのまま CLUSTER BY を実行すると失敗しました。ここが一番時間を使ったところです。
ALTER TABLE trips_iceberg CLUSTER BY (pickup_zip);
[MANAGED_ICEBERG_ATTEMPTED_TO_ENABLE_CLUSTERING_WITHOUT_DISABLING_DVS_OR_ROW_TRACKING]
Attempted to enable Liquid clustering on a Managed Iceberg table without disabling
both deletion vectors and row tracking.
リキッドクラスタリングは削除ベクトルと行トラッキングを前提にしていますが、マネージド Iceberg テーブルはその両方に対応していません。エラーメッセージの指示どおり、先に両方を無効化します。
ALTER TABLE trips_iceberg SET TBLPROPERTIES (
'delta.enableDeletionVectors' = 'false',
'delta.enableRowTracking' = 'false'
);
ALTER TABLE trips_iceberg CLUSTER BY (pickup_zip);
これで通ります。作成時に指定する場合はこうなります。
CREATE TABLE trips_iceberg_clustered
USING ICEBERG
CLUSTER BY (pickup_zip)
TBLPROPERTIES (
'delta.enableDeletionVectors' = 'false',
'delta.enableRowTracking' = 'false'
)
AS SELECT * FROM samples.nyctaxi.trips;
設定できたかどうかは DESCRIBE DETAIL の clusteringColumns で確認します。DESCRIBE EXTENDED には出てきません。
for t in ["trips_iceberg", "trips_iceberg_clustered", "trips_delta"]:
row = spark.sql(f"DESCRIBE DETAIL {t}").collect()[0].asDict()
print(f"{t:<24}: clusteringColumns = {row.get('clusteringColumns')}")
trips_iceberg : clusteringColumns = ['pickup_zip']
trips_iceberg_clustered : clusteringColumns = ['pickup_zip']
trips_delta : clusteringColumns = []
この2つのプロパティを無効化する手順は、Icebergのドキュメントページには書かれていませんでした。
リキッドクラスタリングとIceberg v3は両立しない
無効化した状態で、既存テーブルをIceberg v3に上げようとすると失敗します。
ALTER TABLE trips_iceberg SET TBLPROPERTIES ('format-version' = 3);
[DELTA_ICEBERG_COMPAT_VIOLATION.WRONG_REQUIRED_TABLE_PROPERTY]
The validation of IcebergCompatV3 has failed.
IcebergCompatV3 requires table property 'delta.enableRowTracking' to be set to 'true'.
Current value: 'false'.
リキッドクラスタリングは delta.enableRowTracking が false であることを求め、Iceberg v3 は同じプロパティが true であることを求めます。Iceberg v3の行リネージが行トラッキングの上に成り立っているためだと思われます。
同じテーブルで両方を有効にすることはできません。どちらを取るかを選ぶことになります。この制約もドキュメントには書かれていませんでした。
Iceberg v3
新しいテーブルであれば、v3は問題なく作れました。
CREATE TABLE v3_demo (
id INT,
payload VARIANT,
row_status STRING DEFAULT 'active'
)
USING ICEBERG
TBLPROPERTIES ('format-version' = 3);
VARIANT型への書き込みと取り出し、書き込み時の既定値がそのまま使えます。
INSERT INTO v3_demo (id, payload)
VALUES (1, parse_json('{"device":"ios","version":"18.2"}')),
(2, parse_json('{"device":"android","version":"15"}'));
SELECT id,
payload:device::string AS device,
payload:version::string AS version,
row_status
FROM v3_demo;
既存のv2テーブルを後からv3に上げることもできます。クラスタリングを設定していないテーブルであれば format-version を変えるだけです。
ALTER TABLE v2_to_v3_demo SET TBLPROPERTIES ('format-version' = 3);
マテリアライズドビューは別物だった
USING ICEBERG を付けたマテリアライズドビューも作れます。ただし、こちらはサーバレスの汎用コンピュート、つまり通常のノートブックからは作成できませんでした。
[MATERIALIZED_VIEW_OPERATION_NOT_ALLOWED.MV_NOT_ENABLED_ON_SERVERLESS_GENERIC_COMPUTE]
The materialized view operation CREATE is not allowed
マテリアライズドビューの作成は裏でLakeflowのパイプラインが動くためです。SQLエディタからSQLウェアハウスに接続して実行すると通りました。
比較のため、USING ICEBERG あり・なしの2本を作りました。
CREATE MATERIALIZED VIEW mv_trips_by_zip
USING ICEBERG
AS
SELECT pickup_zip,
count(*) AS trip_count,
avg(fare_amount) AS avg_fare
FROM trips_iceberg
GROUP BY pickup_zip;
ここで想定と違ったのは、できあがったものがマネージド Iceberg テーブルではなかったことです。DESCRIBE EXTENDED のProviderは delta で、そのかわりに # Delta Uniform Iceberg というセクションが出てきます。
# Delta Uniform Iceberg
Metadata location s3://.../_external_metadata/metadata/00000-....metadata.json
Converted delta version 2
Converted delta timestamp 2026-09-18T06:02:40.486730774Z
SHOW TBLPROPERTIES を見ると、delta.universalFormat.enabledFormats = iceberg と delta.enableIcebergCompatV3 = true が付いています。UniFormです。
さらに、テーブル本体とは正反対のプロパティが並びます。
delta.columnMapping.mode name
delta.enableChangeDataFeed true
delta.enableDeletionVectors true
delta.enableIcebergCompatV3 true
delta.enableRowTracking true
delta.universalFormat.enabledFormats iceberg
マネージド Iceberg テーブルではこれらの機能が使えず、リキッドクラスタリングのために無効化まで必要でした。マテリアライズドビューでは、同じ機能が前提として有効になっています。
USING ICEBERG あり・なしの差は、icebergCompatV3 と columnMapping の2つだけでした。それ以外のプロパティは同一です。
同じ USING ICEBERG という書き方でも、テーブルとマテリアライズドビューでは実体が違う。ここは読み替えが必要なところです。
エラーメッセージから見える実装
今回の検証で繰り返し出てきたエラーコードを並べると、こうなります。
| エラーコード | 出た場面 |
|---|---|
MANAGED_ICEBERG_OPERATION_NOT_SUPPORTED |
SHOW CREATE TABLE、SHALLOW CLONE |
DELTA_ICEBERG_WRITER_COMPAT_VIOLATION |
生成列、CHECK制約、照合順序 |
DELTA_ICEBERG_COMPAT_VIOLATION |
クラスタリング済みテーブルのv3化 |
DELTA_OPERATION_NOT_ALLOWED |
式ベースのパーティション分割 |
IcebergWriterCompatV1 という検証器が、Icebergテーブルに書き込める範囲を決めています。式ベースのパーティション分割にいたっては、Icebergテーブルに対しても「not supported for Delta tables」と返ります。
マネージド Iceberg テーブルは、Deltaのエンジンの上でIceberg互換の書き込みを行う仕組みとして実装されている。エラーメッセージからはそう読めます。Icebergの仕様書を読んで「できるはず」と考えたことが通らない場合、理由はIcebergの仕様ではなくDelta側の互換レイヤーにある、という見当を付けられます。
まとめ
マネージド Iceberg テーブルを一通り触って分かったことをまとめます。
-
CREATE TABLE ... USING ICEBERGでマネージド Iceberg テーブルになる。付けなければ従来どおりDelta -
UPDATE/DELETE/MERGE、DESCRIBE HISTORY、タイムトラベル、スキーマ進化、DEEP CLONE、OPTIMIZE/ANALYZE、NOT NULL、列によるパーティション分割はそのまま使える - 生成列、CHECK制約、照合順序、
SHALLOW CLONEは使えない。いずれもIcebergWriterCompatV1の検証で弾かれる SHOW CREATE TABLEが使えない。ドキュメントの制限事項には記載がない- リキッドクラスタリングを有効にするには、先に削除ベクトルと行トラッキングを無効化する必要がある
- その無効化がIceberg v3の要求と衝突する。同じテーブルでリキッドクラスタリングとIceberg v3は両立しない
- Icebergマテリアライズドビューはマネージド Iceberg テーブルではなく、UniFormを有効にしたDeltaテーブルとして作られる
- マテリアライズドビューはサーバレスの汎用コンピュートからは作成できない。SQLウェアハウスが必要
- パーティション進化はDatabricks SQLからは構文の時点で受け付けられない
- テーブル機能を見ると、Iceberg側は
delta.feature.*が空。Delta側には削除ベクトルなどが入っている
一番の収穫は、エラーメッセージを読む習慣が効くと分かったことでした。DELTA_ICEBERG_WRITER_COMPAT_VIOLATION のようなコードは、Iceberg側の制約ではなくDelta側の互換レイヤーが何を許していないかを教えてくれます。Icebergの仕様書だけを見て設計すると、この層の存在を見落とします。
リキッドクラスタリングとIceberg v3の二択は、実務では効いてくるところだと思います。外部エンジンから読ませたいのか、Databricks側の性能機能を取りたいのか。テーブルを作る前に決めておいたほうがよさそうです。
参考リンク
- Databricks の Apache Iceberg とは?
- Iceberg を使用したマネージドテーブルのクローン作成
- Apache Iceberg v3
- マネージドテーブル
- 外部システムを使用して Iceberg テーブルにアクセスする
- サーバレスコンピュートのリリースノート

