1
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

PR: データブリックス・ジャパン株式会社
Omnigentによるメタハーネス入門(1)基礎編

Databricks のマネージド Iceberg テーブルを一通り試す — Delta との差はどこに出るか

1
Posted at

はじめに

Databricksでは、Unity Catalogのマネージドテーブルとして Apache Iceberg テーブルを作れるようになっています。ドキュメントは2026年7月6日に更新されています。

CREATE TABLEUSING 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.sparkVersionspark.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になっている、という関係がここで分かります。

Screenshot 2026-09-18 at 15.17.54.png

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つも入っていません。この違いが、このあと出てくる制約のほとんどを説明します。

普通に使える操作

まず、問題なく使えたものから挙げます。

UPDATEDELETEMERGE はそのまま動きます。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 OFTIMESTAMP AS OF の両方が使えます。

スキーマ進化は、列の追加、名前変更、コメントの付与、削除がすべて通りました。Icebergが売りにしている機能なので、ここは期待どおりです。

OPTIMIZEANALYZE も動きます。

タイムトラベルで時刻を指定するとき

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 ICEBERGDEEP 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 DETAILclusteringColumns で確認します。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.enableRowTrackingfalse であることを求め、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 = icebergdelta.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 あり・なしの差は、icebergCompatV3columnMapping の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 / MERGEDESCRIBE HISTORY、タイムトラベル、スキーマ進化、DEEP CLONEOPTIMIZE / ANALYZENOT 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

はじめてのDatabricks

Databricks無料トライアル

Databricks無料トライアル

1
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?