ARCHININGEN TECH BLOG
← 記事一覧へ戻る

Databricks の Delta テーブルを Snowflake でゼロコピー参照する

CONTENTS

Delta Direct によるゼロコピー連携の構成図。上部の ADLS Gen2 (Blob) に多数の Parquet ファイル群、Databricks が管理する _delta_log フォルダ、Snowflake が生成する metadata フォルダが同居する。下部左の Databricks とは書き込みと読み込みの 2 本の矢印で結ばれ、下部右の Snowflake へはストレージからの読み取りの一方向矢印のみ。実データは 1 バイトも動かず、両エンジンが同じ Parquet を参照する

Databricks でデータ基盤を運用していると、ある日「Snowflake も使いたい」という話がやって来ます。BI チームの標準が Snowflake だったり、全社のデータウェアハウス方針が決まったり、経緯は様々ですが、担当者に降ってくる要件は同じです — Databricks で作っている Delta テーブルを、Snowflake からも読めるようにせよ

素直に作ると「Databricks から書き出して Snowflake へロードし直す」コピー型のパイプラインになります。ストレージは二重になり、鮮度はバッチ間隔に縛られ、パイプラインの保守も増える。できれば避けたい構成です。

本記事では、Parquet 実体には一切手を触れず、Snowflake 側でメタデータだけを再作成して Delta テーブルを Iceberg テーブルとしてクエリ可能にする Delta Direct の構成を紹介します。筆者が Azure Databricks + Snowflake の実環境で採用している構成で、Databricks 側の変更はゼロ、やめたくなったら Snowflake 側の設定を消すだけという出入りの軽さが持ち味です。仕組みとセットアップ手順 (Azure / ADLS Gen2)、最大の関門である権限設定の躓きポイント、メタデータ更新戦略の使い分けまでを扱います。

ゼロコピー連携とは何か

Delta Lake と Apache Iceberg は、どちらも「オブジェクトストレージ上の Parquet ファイル + トランザクションログ (メタデータ)」という同じ構造を持つテーブルフォーマットです。実データの持ち方は共通で、違うのはメタデータの書式だけ、という点がこの連携の出発点になります。

つまり Databricks が書いた Parquet ファイル群はそのままに、Snowflake が読める書式のメタデータを後付けで生成してやれば、両エンジンが同一のファイルを参照できます。動くのはメタデータだけで、Parquet は 1 バイトも動きません。これがゼロコピー連携の意味であり、コピー型パイプラインの「二重ストレージ・鮮度遅延・保守負担」の 3 点がまとめて消えます。

仕組み — Delta Direct

Snowflake 公式ドキュメントでこの構成を担うのが Delta Direct です。実体は、オブジェクトストレージを直接読むカタログ統合 (CATALOG_SOURCE = OBJECT_STORE) に TABLE_FORMAT = DELTA を指定したもので、Snowflake が Delta テーブルのトランザクションログ _delta_log/ を直接パースし、Iceberg メタデータを Snowflake 側で生成します。

生成された Iceberg メタデータは、external volume に書き込みが許可されていればテーブルの BASE_LOCATION 直下の metadata/ フォルダへ書き出されます (中身はスナップショット snap-*.avro やマニフェスト *-m0.avro といった Iceberg 標準のメタデータファイル群です)。冒頭の図のとおり、ストレージ上では Databricks が管理する _delta_log/ と Snowflake が生成する metadata/ が同じテーブルディレクトリに同居する形になります。

登場人物は 3 つだけです。

  • external volume: ADLS Gen2 のコンテナへの接続情報と認証を束ねる Snowflake オブジェクト。ここが権限設定の本丸です
  • catalog integration: 「ストレージ上の _delta_log/ をカタログとして読む」ことを宣言するオブジェクト
  • Iceberg テーブル: 上記 2 つを参照して CREATE ICEBERG TABLE で作る本体。実データを持たず、メタデータだけを保持します

重要な性質として、この方式で作った Iceberg テーブルは Snowflake からは読み取り専用です。書き込みの主体はあくまで Databricks 側にあり、Snowflake は追いかけて読む立場になります。「オーナーは 1 エンジンに固定し、他エンジンは読むだけ」という役割分担が、ゼロコピー構成を破綻させないための設計判断です。

なぜ UniForm ではなく Delta Direct か

Databricks の Delta テーブルを Snowflake から読む方法は Delta Direct だけではありません。主な選択肢を並べます。

方式 メタデータ生成側 経路 向いている状況
Delta Direct Snowflake ストレージ直読み 同一ストレージに両者がアクセスできる社内構成
UniForm + Iceberg REST Databricks Unity Catalog の REST エンドポイント UC のガバナンス下で連携を管理したい場合
Delta Sharing Databricks Sharing プロトコル 組織をまたぐデータ共有
コピー型 ETL 実データを複製 フォーマット変換や加工を伴う場合

先に結論を書くと、すでに大きく育ったテーブルが動いている既存環境なら、筆者は Delta Direct を推します。その理由は、対抗馬である UniForm を見送った経緯にそのまま表れています。

UniForm を見送った 3 つの理由

UniForm は Databricks 側で Delta テーブルに Iceberg メタデータを併産させる機能で、Snowflake からは Unity Catalog の Iceberg REST エンドポイント経由のカタログ統合で読めます。テーブル発見やガバナンスを UC に集約できる正攻法ですが、筆者は先に UniForm を試した上で不採用にしました。理由は 3 つあります。

  • 既存テーブルの書き換えが要る: 既存の Delta テーブルへの有効化は REORG TABLE … APPLY (UPGRADE UNIFORM (ICEBERG_COMPAT_VERSION = 2)) で行いますが、公式ドキュメントに全ファイルを書き換える可能性があると明記されています。導入時点で数 TB に育っていた本番テーブルには、書き換えのコンピュートコストと所要時間が無視できませんでした
  • 書き手側のテーブルの形式が変わる: 有効化にはカラムマッピングの有効化と writer プロトコルの昇格が伴い、以後の Parquet は圧縮コーデックが Snappy ではなく Zstandard で書かれます。Zstandard 自体は Snowflake で問題なく読めるためコーデックが障壁なのではなく、「読む側の都合で、稼働中の書き手側テーブルの物理形式とプロトコルを作り替える」こと自体を避けたい、という判断です
  • 同期の制御が Databricks 任せになる: UniForm の Iceberg メタデータ生成は、Delta への書き込みに使ったコンピュート上で非同期に実行されます。メタデータがいつ追いつくかを、読む側から制御する手段がありません

3 つに共通するのは、影響がすべて「すでに動いている書き手側」に及ぶことです。これから新規に作るテーブルなら最初から UniForm を有効化する選択も十分あり得ますが、数 TB に育った本番テーブルを、読む側の都合で書き換えてプロトコルまで変えるのは、既存環境では通しにくい判断でした。同じ状況の環境は多いはずです。

Delta Direct は Snowflake 側だけで完結する

一方 Delta Direct は Databricks 側の変更が一切不要です。Databricks はこれまでどおり Delta テーブルを読み書きし続け、そのテーブルが Snowflake からも使えるようになる。この状態を、ストレージへのアクセス権さえあれば Snowflake 側の設定だけで完結できます。メタデータをいつ更新するかも、後述のとおり読む側が握れます。「書き手側の環境を汚さない」「同期は読む側で制御する」という要件に合ったのが Delta Direct だった、というのが本記事の採用経緯です。

なお Snowflake は、Unity Catalog や AWS Glue といった他カタログ上のテーブル定義から直接 Iceberg テーブルを作ることはできず、あくまでストレージ上のファイルの直読みです。カタログの外側で動くことの含意は、後述の躓きポイントで触れます。

導入も撤退も軽い

Snowflake 側で作るオブジェクトは前述の 3 つだけなので、まず 1 テーブルだけ繋いで試すスモールスタートができます。全社的な連携基盤の話に膨らませる前に、手元のテーブルひとつで価値を確かめられます。

撤退も同じくらい簡単です。Snowflake から読む必要がなくなったら、Snowflake 側の設定 (Iceberg テーブル・catalog integration・external volume) を削除し、Blob 上に生成された metadata/ フォルダを消すだけで、元の Delta テーブルだけの状態に戻ります。Databricks 側には最初から何も手を入れていないので、導入前と完全に同じ状態へ可逆的に戻せます。

もうひとつ、技術の外側ですが「小さく始める」観点で効いた判断材料が Snowflake の契約形態です。Databricks がメインで Snowflake は読み出し口というサブの位置づけなら、契約も小さく始めたいところです。筆者の検討時点では、Azure Marketplace 経由の契約はまとまった前払い (キャパシティ購入) が前提でサブ用途には過剰だったため、オンデマンドで始められる Snowflake との直接契約を選んでいます。契約条件は変わり得るので、検討時は最新の条件を確認してください。

セットアップ手順 (Azure)

手順は 5 ステップです。先に言っておくと、最大の関門はステップ 2 の権限設定で、初見ではどこかで一度は躓きます。ハマったら次章の躓きポイントへ飛んでください。

前提は次のとおりです。

  • Delta テーブルは ADLS Gen2 のコンテナ上にあり、Unity Catalog には external テーブルとして登録されている (external location が明示されている)
  • Snowflake アカウントは Azure の同一リージョンでなくても動きますが、エグレス費用と読み取り遅延の観点から同一リージョンが望ましいです

1. external volume を作る

ACCOUNTADMIN ロールで、対象コンテナを指す external volume を作成します。

SQL
-- Delta テーブルが置かれた ADLS Gen2 コンテナへの external volume
-- (ALLOW_WRITES はデフォルトの TRUE。Reader で組む読み取り専用構成との使い分けは躓きポイント参照)
CREATE EXTERNAL VOLUME delta_exvol
  STORAGE_LOCATIONS = (
    (
      NAME = 'adls-japaneast'
      STORAGE_PROVIDER = 'AZURE'
      STORAGE_BASE_URL = 'azure://mystorageacct.dfs.core.windows.net/lakehouse/'
      AZURE_TENANT_ID = 'a123b4c5-1234-123a-a12b-1a23b45678c9'
    )
  );

URL の書式に 2 つ注意があります。スキームは abfss:// でも https:// でもなく azure://、エンドポイントは ADLS Gen2 なら blob.core.windows.net ではなく dfs.core.windows.net です。Databricks 側の設定で見慣れた abfss:// を反射で書くと通らないので、ここは書式ごと覚え直してください。

2. Azure 側で同意とロール付与を行う

作成した external volume の情報を確認します。

SQL
-- 同意用 URL とサービスプリンシパル名を確認する
DESC EXTERNAL VOLUME delta_exvol;

出力の AZURE_CONSENT_URL をブラウザで開いて Accept すると、Snowflake 用のサービスプリンシパルがテナントに作成されます。続いて Azure ポータルで対象のストレージアカウントの Access Control (IAM) を開き、AZURE_MULTI_TENANT_APP_NAME (アンダースコアより前の部分で検索) に Storage Blob Data Contributor ロールを割り当てます。読むだけの連携なのに Contributor を使う理由と、Reader で組む読み取り専用構成との使い分けは、次章の躓きポイントで説明します。

3. 接続を検証する

ロール反映を待ってから、Snowflake 側で疎通を確認します。

SQL
-- external volume がストレージへ認証・アクセスできるか検証する
SELECT SYSTEM$VERIFY_EXTERNAL_VOLUME('delta_exvol');

"success": true を含む JSON が返れば接続は完了です。失敗する場合の切り分けは次章にまとめました。

4. catalog integration を作る

_delta_log/ をカタログとして読む宣言をします。

SQL
-- ストレージ上の Delta トランザクションログを直接読むカタログ統合
CREATE CATALOG INTEGRATION delta_catalog_int
  CATALOG_SOURCE = OBJECT_STORE
  TABLE_FORMAT = DELTA
  ENABLED = TRUE;

5. Iceberg テーブルを作る

最後に、テーブルディレクトリを指して Iceberg テーブルを作成します。

SQL
-- BASE_LOCATION は STORAGE_BASE_URL からの相対パスで、_delta_log/ を含むディレクトリを指す
CREATE ICEBERG TABLE events
  EXTERNAL_VOLUME = 'delta_exvol'
  CATALOG = 'delta_catalog_int'
  BASE_LOCATION = 'events/';

BASE_LOCATION は単一ファイルではなくディレクトリ、それも直下に _delta_log/ があるテーブルルートを指す必要があります。作成に成功したら、あとは普通のテーブルと同じようにクエリできます。

SQL
-- Databricks が書いた Parquet をそのまま読んでいる
SELECT count(*) FROM events;

Databricks 側の件数と一致すれば、ゼロコピー連携の疎通は完了です。

権限まわりの躓きポイント

ここが本記事でいちばん伝えたい部分です。手順の見た目はシンプルでも、認証・権限には直感に反する仕様がいくつもあります。ハマりどころを先回りでまとめます。

ロール付与はストレージアカウントレベルに行う

最頻出のハマりどころです。Azure の IAM はコンテナ単位でもロールを割り当てられますが、Snowflake の external volume はストレージアカウントレベルのロール割り当てを要求します。コンテナだけにロールを割り当てても SYSTEM$VERIFY_EXTERNAL_VOLUME は失敗します。

「最小権限にしたいから」とコンテナ単位で付けたくなる場面ですが、ここはアカウントレベルが仕様です。範囲を絞りたい場合は連携専用のストレージアカウントを分ける設計で対応します。

反映待ちの時間を見込む

同意後にサービスプリンシパルが IAM の検索に出てくるまで最長 1〜2 時間、ロール割り当ての反映にも最大 10 分程度かかることがあります。検証が失敗し続けると設定ミスを疑って作り直したくなりますが、設定が正しくても待ち時間中は失敗します。「作り直す前にまず待つ」を挟むだけで、無駄な再設定のループを避けられます。

必要な Snowflake 側の権限を整理しておく

external volume と catalog integration の作成にはそれぞれアカウントレベルの CREATE EXTERNAL VOLUME / CREATE INTEGRATION 権限 (実務上は ACCOUNTADMIN) が必要です。一方、日常的にテーブルを作る・更新するロールに ACCOUNTADMIN を配るべきではないので、作成後に USAGE を委譲します。

SQL
-- テーブルを作るロールへは USAGE だけを委譲する
GRANT USAGE ON EXTERNAL VOLUME delta_exvol TO ROLE data_engineer;
GRANT USAGE ON INTEGRATION delta_catalog_int TO ROLE data_engineer;

「external volume の作成は管理者、テーブル作成と REFRESH は利用側ロール」という分業にしておくと、後述のメタデータ更新を Task 化するときも権限で詰まりません。

「読むだけ」なのに書き込み権限が要る

直感に反する最大のポイントです。Snowflake は実データを読むだけですが、テーブル作成や REFRESH で生成した Iceberg メタデータを BASE_LOCATION 直下の metadata/ フォルダへ書き出します (2025 年の動作変更以降のデフォルト挙動)。公式にサポートされる構成は次の 2 つで、Azure ロールと external volume の ALLOW_WRITES をセットで揃えるのが要点です。

構成 Azure ロール ALLOW_WRITES 挙動
書き込みあり Storage Blob Data Contributor TRUE (デフォルト) Iceberg メタデータを metadata/ へ書き出す
読み取り専用 Storage Blob Data Reader FALSE メタデータ生成が実行されない。クエリは可能

Reader + ALLOW_WRITES = FALSE は誤設定ではなく、ストレージのオーナーが別チームで書き込み権限をもらえない場合などに公式が案内している読み取り専用構成です。ただしメタデータが書き出されない分、後述の「他エンジンからも読める」利点は得られません。避けるべきは片方だけ変える不一致の組み合わせで、ロールと ALLOW_WRITES は必ずセットで選んでください。

なお「書き込みをメタデータフォルダだけに限定する」中間の権限設定は公式には存在せず、書き込みを許可するならストレージアカウントレベルの Contributor が前提です。といっても Snowflake が実際に書き込むのは metadata/ フォルダだけで、Parquet 実体や _delta_log/ に手を加えることはありません。書き出された metadata/ は標準の Iceberg メタデータで、将来ほかの Iceberg 対応エンジンから同じテーブルを読む足がかりになるため、筆者は Contributor + ALLOW_WRITES = TRUE を採用しています。

Unity Catalog のマネージドテーブルを指さない

Delta Direct はストレージのパスを直読みするため、Databricks 側は実体パスが明示されている external テーブルが前提です。UC のマネージドテーブルは実体パスを UC が内部管理しており、パスを調べて直接読む構成は Databricks が非推奨としています (パスの安定性が保証されず、UC の権限モデルも完全にバイパスするため)。マネージドテーブルを連携したい場合は、前述の UniForm 経由か、external テーブルへの移行を検討してください。

また前述のとおり、この構成の読み取りは Unity Catalog のガバナンスの外側で行われます。UC 側でどれだけ細かくアクセス制御していても、Snowflake からの読み取りを制御するのは Azure IAM と Snowflake の権限だけです。監査やアクセス制御の一元化を重視する組織では、この点を踏まえて Delta Direct と UniForm を選び分けてください。

メタデータ更新の 3 つの戦略

Databricks 側でテーブルが更新されても、Snowflake 側の Iceberg メタデータは自動では追従しません (デフォルトの場合)。どう追従させるかには 3 つの戦略があり、鮮度要件とコストで使い分けます。

戦略 1: クエリ直前に手動 REFRESH する

最小構成です。読む前にメタデータを更新します。

SQL
-- Delta ログを読み直してメタデータを最新化する (Delta の場合パス指定は不要)
ALTER ICEBERG TABLE events REFRESH;
SELECT count(*) FROM events;

日次バッチの先頭で 1 回叩く、アドホック分析の前に叩く、といった「読むタイミングが決まっている」用途ではこれで十分です。余計な常駐コストが一切かからないのが利点です。

戦略 2: Task でスケジュール更新する

読む側が不特定多数で「いつ読んでも数時間以内の鮮度でいてほしい」場合は、Snowflake の Task に REFRESH を載せます。

SQL
-- 毎時 0 分にメタデータを更新するサーバレスタスク
CREATE TASK refresh_events
  SCHEDULE = 'USING CRON 0 * * * * Asia/Tokyo'
AS
  ALTER ICEBERG TABLE events REFRESH;

ALTER TASK refresh_events RESUME;

作成直後の Task は停止状態なので RESUME を忘れずに。WAREHOUSE を指定しなければサーバレス実行になり、REFRESH のような短時間処理ならウェアハウスを常時確保するより安く済みます。実行するロールには対象テーブルへの権限に加えて EXECUTE TASK 権限が必要です。前章の「作成は管理者・運用は利用側ロール」の分業をしておくと、ここで権限エラーに悩まずに済みます。

戦略 3: 自動リフレッシュに任せる

分単位の鮮度が必要なら、catalog integration にポーリング間隔を持たせてテーブル側で AUTO_REFRESH を有効にします。

SQL
-- 5 分間隔で Delta ログをポーリングするカタログ統合と自動更新テーブル
CREATE CATALOG INTEGRATION delta_catalog_int_auto
  CATALOG_SOURCE = OBJECT_STORE
  TABLE_FORMAT = DELTA
  REFRESH_INTERVAL_SECONDS = 300
  ENABLED = TRUE;

CREATE ICEBERG TABLE events_live
  EXTERNAL_VOLUME = 'delta_exvol'
  CATALOG = 'delta_catalog_int_auto'
  BASE_LOCATION = 'events/'
  AUTO_REFRESH = TRUE;

REFRESH_INTERVAL_SECONDS のデフォルトは 30 秒です。自動リフレッシュは内部的に Snowpipe の仕組みで動くため、請求書には Snowpipe の項目として課金が載ります (ファイル数課金はなし)。間隔を短くするほど課金とポーリング負荷が増えるので、必要な鮮度から逆算して間隔を決めてください。オブジェクトストレージのカタログ統合で自動リフレッシュが使えるのは TABLE_FORMAT = DELTA のときだけで、これは Delta Direct を選ぶ実利のひとつです。

使い分けの整理と共通の注意

  • 読むタイミングが決まっている (バッチ・定例レポート): 戦略 1 の手動 REFRESH
  • 不特定多数が読む・時間単位の鮮度でよい: 戦略 2 の Task
  • 分単位の鮮度が要る: 戦略 3 の自動リフレッシュ (Snowpipe 課金を許容)

どの戦略でも共通の注意が 2 つあります。1 回の REFRESH で処理される Delta コミットは最大 1,000 件で、それ以上溜まっている場合は続きから再開する追加 REFRESH が必要です。更新間隔を空けすぎると 1 回で追いつかなくなるので、Databricks 側のコミット頻度 (特に小さなコミットを大量に作るストリーミング書き込み) と更新間隔のバランスを見てください。もうひとつ、DML を伴わない DDL だけのスキーマ変更は自動リフレッシュに拾われないため、カラム追加などをした際は手動 REFRESH を 1 回挟みます。

更新コストの目安 — 実測ベース

REFRESH は実データの Parquet を読み直す処理ではありませんが、無料でもありません。コミットの増分取り込みが基本である一方、条件によっては Delta のチェックポイントを起点とするフルリフレッシュ (テーブル全体のメタデータ再構築) が実行されるため、コストは実質的にテーブルの規模 (ファイル数・メタデータ量) に応じて増えます。

目安として筆者の環境では、数 TB のテーブルを含むテーブル群を毎日 REFRESH する運用で、Snowflake のコンピュート料金は月額数十ドルでした。「ゼロコピーだから連携は無料」ではなくメタデータ更新のコストは載る、という前提で更新頻度を設計してください。逆に言えば、数 TB 規模でも毎日更新が月額数十ドルに収まるので、コピー型パイプラインを構築・維持するのに比べれば大幅に安い、というのが実感です。

制約事項 (2026 年 8 月時点)

導入判断に効く制約をまとめます。

  • deletion vectors と liquid clustering は 2026 年 3 月から対応済みです (minReaderVersion 3 / Delta Lake 4.0.0 相当まで読取可)。Databricks は近年のランタイムで deletion vectors がデフォルト有効のため、以前はテーブルプロパティで無効化してから連携する回避策が必要でしたが、現在は不要になりました。古い記事にはこの回避策が残っているので注意してください
  • Row Tracking・Change Data Feed・プロトコル進化 (protocol evolution) は未対応です
  • データ型は INTERVAL 型、精度 38 超の DECIMAL などが未対応です
  • Snowflake からは読み取り専用で、書き込み・スキーマ変更はできません
  • Unity Catalog や AWS Glue のカタログ上の定義からは作成できず、ストレージ直読みのみです

制約は今後も緩和されていく可能性が高いので、導入時には公式ドキュメントの最新の記載を確認することをおすすめします。

まとめ

Delta Direct の要点は「動くのはメタデータだけ、Parquet は 1 バイトも動かない」の一点に尽きます。Databricks 側には一切手を入れず、Snowflake 側の external volume・catalog integration・Iceberg テーブルの 3 オブジェクトだけでゼロコピー連携が成立します。撤退するときもこの 3 オブジェクトと Blob 上の metadata/ フォルダを消すだけで導入前の状態に戻る、出入りの軽い構成です。

導入の順番は、まず external volume の権限設定を突破して (azure:// + dfs エンドポイント、ストレージアカウントレベルのロール付与、反映待ち)、手動 REFRESH で疎通を確認し、運用要件が固まってから Task なり自動リフレッシュなりへ更新戦略を昇格させる、という流れをおすすめします。権限設定は初回だけの峠で、越えてしまえば運用は REFRESH の戦略選びだけのシンプルな構成です。

冒頭の状況 — Databricks を使っていて Snowflake の導入を求められている — にいるなら、大掛かりな移行計画を立てる前に、まず 1 テーブルを Delta Direct で繋いでみてください。既存環境には何の影響もなく、合わなければ消すだけ。この試すコストの低さが、既存環境における Delta Direct のいちばんの推しどころです。

参考リンクをまとめておきます。

更新履歴

    • Databricks Delta を Snowflake でゼロコピー参照する記事を追加