Snowflake Iceberg テーブルデータを BigQuery Iceberg テーブルから参照してみた。

Snowflake Iceberg テーブルデータを BigQuery Iceberg テーブルから参照してみた。

2026.05.18

こんにちは、みかみです。

よく行く釣り場が夕日スポットとして認知されはじめているようで、夕暮れ時になると人々が集まってくるようになりました。釣りしているのが恥ずかしいやら申し訳ないやら。
ちなみにこないだの釣果はチョウチョウウオ1匹でした。別の釣り場を探そうと思います。

やりたいこと

  • Snowflake 管理の Iceberg テーブルを、BigQuery Iceberg テーブルから参照したい。
  • Snowflake 側で追加・更新・削除されたデータを、BigQuery Iceberg テーブルで参照するために必要な手順を確認したい。

前提

Google Cloud SDK(gcloud コマンド)の実行環境は準備済みであるものとします。 本エントリでは、Cloud Shell を使用しました。

また、BigQuery や GCS など各サービス操作に必要な API の有効化と権限は付与済みです。
Snowflake 側でも、必要なユーザーの作成や権限付与は実施済みです。

なお、文中、Google Cloud プロジェクト ID など一部の文字は伏字に変更しています。

本ブログの検証に使用した Snowflake の AWS と BigQuery のデータセットは、ともに東京リージョンです。

Snowflake の Iceberg テーブルを作成

GCS バケットをベース URL とした、Snowflake 管理の Iceberg テーブルを作成します。
GCS バケットはすでに作成済みです。

Snowflake で以下の SQL を実行して、EXTERNAL VOLUME(外部ボリューム)を作成します。

CREATE OR REPLACE EXTERNAL VOLUME MIKAMI_GCS_VOL
  STORAGE_LOCATIONS = (
    (
      NAME = 'gcs-loc',
      STORAGE_PROVIDER = 'GCS',
      STORAGE_BASE_URL = 'gcs://test-mikami/iceberg/'
    )
  );

以下の SQL 出力結果のproperty_valueSTORAGE_GCP_SERVICE_ACCOUNT のサービスアカウント名を取得します。

DESC EXTERNAL VOLUME MIKAMI_GCS_VOL;

以下の gcloud コマンドで、取得した外部ボリュームのサービスアカウントに GCS バケットへのアクセス権を付与します。

gcloud storage buckets add-iam-policy-binding gs://test-mikami \
  --member="serviceAccount:mqtuzmgwut@awsapnortheast1-0987.iam.gserviceaccount.com" \
  --role="roles/storage.objectAdmin"

続いて以下の SQL で、Snowflake の Iceberg テーブルを作成し、データを投入します。

-- テーブル作成
CREATE OR REPLACE ICEBERG TABLE MIKAMI_DB.PUBLIC.ICEBERG_TO_BQ (
  id          INT,
  name        STRING,
  created_at  TIMESTAMP_NTZ,
  updated_at  TIMESTAMP_NTZ
)
CATALOG = 'SNOWFLAKE'
EXTERNAL_VOLUME = 'MIKAMI_GCS_VOL'
BASE_LOCATION = 'ICEBERG_TO_BQ/';

-- 初期データ3件投入
INSERT INTO MIKAMI_DB.PUBLIC.ICEBERG_TO_BQ
SELECT 1, 'Alice',   CONVERT_TIMEZONE('Asia/Tokyo', CURRENT_TIMESTAMP())::TIMESTAMP_NTZ,
                     CONVERT_TIMEZONE('Asia/Tokyo', CURRENT_TIMESTAMP())::TIMESTAMP_NTZ
UNION ALL
SELECT 2, 'Bob',     CONVERT_TIMEZONE('Asia/Tokyo', CURRENT_TIMESTAMP())::TIMESTAMP_NTZ,
                     CONVERT_TIMEZONE('Asia/Tokyo', CURRENT_TIMESTAMP())::TIMESTAMP_NTZ
UNION ALL
SELECT 3, 'Charlie', CONVERT_TIMEZONE('Asia/Tokyo', CURRENT_TIMESTAMP())::TIMESTAMP_NTZ,
                     CONVERT_TIMEZONE('Asia/Tokyo', CURRENT_TIMESTAMP())::TIMESTAMP_NTZ;

データが正常に投入されたことを確認しておきます。

SELECT * FROM MIKAMI_DB.PUBLIC.ICEBERG_TO_BQ ORDER BY id;

sf_table_data

GCS 上には以下のファイルが出力されている状態です。

$ gcloud storage ls --recursive --long gs://test-mikami/iceberg/
         0  2026-05-16T18:35:19Z  gs://test-mikami/iceberg/

gs://test-mikami/iceberg/:
         0  2026-05-16T18:35:19Z  gs://test-mikami/iceberg/

gs://test-mikami/iceberg/ICEBERG_TO_BQ.cTrlUAdR/:

gs://test-mikami/iceberg/ICEBERG_TO_BQ.cTrlUAdR/data/:
      1536  2026-05-16T19:34:50Z  gs://test-mikami/iceberg/ICEBERG_TO_BQ.cTrlUAdR/data/snow_K9FFHEshBV0_AO6g2LsisBg_0_1_002.parquet

gs://test-mikami/iceberg/ICEBERG_TO_BQ.cTrlUAdR/metadata/:
       924  2026-05-16T19:34:40Z  gs://test-mikami/iceberg/ICEBERG_TO_BQ.cTrlUAdR/metadata/00000-59617882-7f0a-4fc3-9306-bed1acf8a636.metadata.json
      2006  2026-05-16T19:34:52Z  gs://test-mikami/iceberg/ICEBERG_TO_BQ.cTrlUAdR/metadata/00001-b24f3456-9989-4db4-9b68-daaa16dbda5d.metadata.json
      7177  2026-05-16T19:34:51Z  gs://test-mikami/iceberg/ICEBERG_TO_BQ.cTrlUAdR/metadata/4ab5f637-ef27-415c-9590-1945ea3fc588-m0.avro
      4461  2026-05-16T19:34:51Z  gs://test-mikami/iceberg/ICEBERG_TO_BQ.cTrlUAdR/metadata/snap-2619304162310719079-1-4ab5f637-ef27-415c-9590-1945ea3fc588.avro
TOTAL: 7 objects, 16104 bytes (15.73kiB)

BigQuery の Iceberg テーブルを作成

以下のコマンドで、BigQuery Connection を作成します。

bq mk --connection \
  --location=asia-northeast1 \
  --project_id=[PROJECT_ID] \
  --connection_type=CLOUD_RESOURCE sf_iceberg_conn

作成した Connection のサービスアカウントを確認し、GCS バケットへのアクセス権を付与します。

# サービスアカウント確認
bq show --connection [PROJECT_ID].asia-northeast1.sf_iceberg_conn

# 権限付与
gcloud storage buckets add-iam-policy-binding gs://test-mikami \
  --member="serviceAccount:bqcx-[PROJECT_NUMBER]-i94x@gcp-sa-bigquery-condel.iam.gserviceaccount.com" \
  --role="roles/storage.objectViewer"

Snowflake で以下の SQL を実行し、metadata ファイルパスを確認しておきます。

SELECT SYSTEM$GET_ICEBERG_TABLE_INFORMATION('MIKAMI_DB.PUBLIC.ICEBERG_TO_BQ');

BigQuery で以下の SQL を実行し、Iceberg テーブルを作成します。
uris に、Snowflake 側で確認したメタデータファイルパスを指定しています。
なお GCS パスのプレフィクスは、Snowflake では gcs:// ですが、Google Cloud では gs:// となるのでご注意ください。

CREATE OR REPLACE EXTERNAL TABLE
 dataset_1.ieberg_from_SF
WITH CONNECTION `[PROJECT_ID].asia-northeast1.sf_iceberg_conn`
OPTIONS (
  format = 'ICEBERG',
  uris = ['gs://test-mikami/iceberg/ICEBERG_TO_BQ.cTrlUAdR/metadata/00001-b24f3456-9989-4db4-9b68-daaa16dbda5d.metadata.json']
);

データを確認してみます。

SELECT * FROM dataset_1.ieberg_from_SF ORDER BY id;

Snowflake の Iceberg テーブルと同じデータが、BigQuery の Iceberg テーブルから参照できることが確認できました。

bq_table_data

Snowflake Iceberg テーブルデータを追加・更新・削除

Snowflake で以下の SQL を実行して、Iceberg テーブルにデータを追加・更新・削除します。

-- INSERT: id=4 (Dave) を追加
INSERT INTO MIKAMI_DB.PUBLIC.ICEBERG_TO_BQ
SELECT 4, 'Dave',
       CONVERT_TIMEZONE('Asia/Tokyo', CURRENT_TIMESTAMP())::TIMESTAMP_NTZ,
       CONVERT_TIMEZONE('Asia/Tokyo', CURRENT_TIMESTAMP())::TIMESTAMP_NTZ;

-- UPDATE: id=1 のnameを変更し、updated_atを更新
UPDATE MIKAMI_DB.PUBLIC.ICEBERG_TO_BQ
SET name       = 'Alice Updated',
    updated_at = CONVERT_TIMEZONE('Asia/Tokyo', CURRENT_TIMESTAMP())::TIMESTAMP_NTZ
WHERE id = 1;

-- DELETE: id=2 (Bob) を削除
DELETE FROM MIKAMI_DB.PUBLIC.ICEBERG_TO_BQ WHERE id = 2;

テーブルデータを確認します。

SELECT * FROM MIKAMI_DB.PUBLIC.ICEBERG_TO_BQ ORDER BY id;

sf_table_data_updated

id = 4 が追加され、id = 1 が更新、id = 2 が削除されていることが確認できました。

GCS 上のファイルは以下の状態です。

$ gcloud storage ls --recursive --long gs://test-mikami/iceberg/
         0  2026-05-16T18:35:19Z  gs://test-mikami/iceberg/

gs://test-mikami/iceberg/:
         0  2026-05-16T18:35:19Z  gs://test-mikami/iceberg/

gs://test-mikami/iceberg/ICEBERG_TO_BQ.cTrlUAdR/:

gs://test-mikami/iceberg/ICEBERG_TO_BQ.cTrlUAdR/data/:
      1536  2026-05-16T19:39:33Z  gs://test-mikami/iceberg/ICEBERG_TO_BQ.cTrlUAdR/data/snow_K9FFHEshBV0_AFFy_wAjsBg_0_1_002.parquet
      1536  2026-05-16T19:34:50Z  gs://test-mikami/iceberg/ICEBERG_TO_BQ.cTrlUAdR/data/snow_K9FFHEshBV0_AO6g2LsisBg_0_1_002.parquet
      1024  2026-05-16T19:39:17Z  gs://test-mikami/iceberg/ICEBERG_TO_BQ.cTrlUAdR/data/snow_K9FFHEshBV0_xLYbB_MisBg_0_1_002.parquet
      3072  2026-05-16T19:39:23Z  gs://test-mikami/iceberg/ICEBERG_TO_BQ.cTrlUAdR/data/snow_K9FFHEshBV0_xrYbB_MisBg_0_1_002.parquet

gs://test-mikami/iceberg/ICEBERG_TO_BQ.cTrlUAdR/metadata/:
       924  2026-05-16T19:34:40Z  gs://test-mikami/iceberg/ICEBERG_TO_BQ.cTrlUAdR/metadata/00000-59617882-7f0a-4fc3-9306-bed1acf8a636.metadata.json
      2006  2026-05-16T19:34:52Z  gs://test-mikami/iceberg/ICEBERG_TO_BQ.cTrlUAdR/metadata/00001-b24f3456-9989-4db4-9b68-daaa16dbda5d.metadata.json
      3034  2026-05-16T19:39:19Z  gs://test-mikami/iceberg/ICEBERG_TO_BQ.cTrlUAdR/metadata/00002-d3394ad2-3b97-471b-88ae-0b5bdea6bff0.metadata.json
      4146  2026-05-16T19:39:25Z  gs://test-mikami/iceberg/ICEBERG_TO_BQ.cTrlUAdR/metadata/00003-2a69fc31-572e-451e-a595-f566f0ed89af.metadata.json
      5257  2026-05-16T19:39:35Z  gs://test-mikami/iceberg/ICEBERG_TO_BQ.cTrlUAdR/metadata/00004-424edbf5-f044-48df-9bed-4e78490ee305.metadata.json
      7163  2026-05-16T19:39:19Z  gs://test-mikami/iceberg/ICEBERG_TO_BQ.cTrlUAdR/metadata/01678a50-1b75-4cda-9037-61a5c08eda70-m0.avro
      7178  2026-05-16T19:39:25Z  gs://test-mikami/iceberg/ICEBERG_TO_BQ.cTrlUAdR/metadata/0c95b293-0b89-4229-b7a6-fcf8ebf351f4-m0.avro
      7193  2026-05-16T19:39:25Z  gs://test-mikami/iceberg/ICEBERG_TO_BQ.cTrlUAdR/metadata/0c95b293-0b89-4229-b7a6-fcf8ebf351f4-m1.avro
      7195  2026-05-16T19:39:34Z  gs://test-mikami/iceberg/ICEBERG_TO_BQ.cTrlUAdR/metadata/1bdb0645-5c96-4719-be57-afcde9addef8-m0.avro
      7193  2026-05-16T19:39:35Z  gs://test-mikami/iceberg/ICEBERG_TO_BQ.cTrlUAdR/metadata/1bdb0645-5c96-4719-be57-afcde9addef8-m1.avro
      7177  2026-05-16T19:34:51Z  gs://test-mikami/iceberg/ICEBERG_TO_BQ.cTrlUAdR/metadata/4ab5f637-ef27-415c-9590-1945ea3fc588-m0.avro
      4461  2026-05-16T19:34:51Z  gs://test-mikami/iceberg/ICEBERG_TO_BQ.cTrlUAdR/metadata/snap-2619304162310719079-1-4ab5f637-ef27-415c-9590-1945ea3fc588.avro
      4553  2026-05-16T19:39:25Z  gs://test-mikami/iceberg/ICEBERG_TO_BQ.cTrlUAdR/metadata/snap-5295394461694027022-1-0c95b293-0b89-4229-b7a6-fcf8ebf351f4.avro
      4532  2026-05-16T19:39:19Z  gs://test-mikami/iceberg/ICEBERG_TO_BQ.cTrlUAdR/metadata/snap-830033498443085298-1-01678a50-1b75-4cda-9037-61a5c08eda70.avro
      4550  2026-05-16T19:39:35Z  gs://test-mikami/iceberg/ICEBERG_TO_BQ.cTrlUAdR/metadata/snap-8843058784580407502-1-1bdb0645-5c96-4719-be57-afcde9addef8.avro
TOTAL: 21 objects, 83730 bytes (81.77kiB)

BQ側で確認

そのまま BigQuery Iceberg テーブルデータを確認しても、先程の追加・更新・削除データは確認できませんでした。

Snowflake で現在のメタデータファイルを確認し、BigQuery Iceberg テーブルの metadata ファイルパスを更新します。

SELECT SYSTEM$GET_ICEBERG_TABLE_INFORMATION('MIKAMI_DB.PUBLIC.ICEBERG_TO_BQ');
# テーブル定義ファイルを作成
cat > iceberg_def.json <<'EOF'
{
  "sourceFormat": "ICEBERG",
  "sourceUris": [
    "gs://test-mikami/iceberg/ICEBERG_TO_BQ.cTrlUAdR/metadata/00004-424edbf5-f044-48df-9bed-4e78490ee305.metadata.json"
  ],
  "connectionId": "[PROJECT_ID].asia-northeast1.sf_iceberg_conn"
}
EOF

# 既存テーブルの定義を更新
bq update \
  --external_table_definition=iceberg_def.json \
  [PROJECT_ID]:dataset_1.ieberg_from_SF

BigQuery Iceberg テーブルデータを確認してみます。

bq_table_data_updated

最新のデータが反映されるようになりました。

また、タイムトラベルで追加・更新・削除前のテーブルデータを参照できることも確認できました。

SELECT *
FROM dataset_1.ieberg_from_SF
  FOR SYSTEM_TIME AS OF TIMESTAMP '2026-05-17 04:35:00+09:00'
ORDER BY id;

bq_table_data_timetravel

なお、現在のところ、SQL でメタデータファイルパスの更新はできないようです。

以下の ALTER TABLE 文で更新しようとしたところ、エラーが発生してしまいました。

ALTER TABLE dataset_1.ieberg_from_SF
SET OPTIONS (
  uris = ['gs://test-mikami/iceberg/ICEBERG_TO_BQ.cTrlUAdR/metadata/00004-424edbf5-f044-48df-9bed-4e78490ee305.metadata.json']
);

bq_alter_table_error

SQL レイヤで処理する必要がある場合はテーブルを作り直す必要がありますが、その場合タイムトラベルは使えなくなってしまうので、ご留意ください。

BigQuery 外部テーブル経由で Snowflake テーブルデータを参照

Iceberg テーブル経由ではなく、BigQuery で外部テーブルを作成して、GCS 上のファイルを参照することも可能です。

Snowflake で以下の SQL を実行して、テーブルデータを CSV ファイルで GCS にエクスポートします。
なお、ファイル出力先 GCS のストレージ統合と外部ステージはすでに作成済みです。新規作成が必要な場合は以下をご参照ください。

https://dev.classmethod.jp/articles/snowflake-gcs-as-external-stage/

COPY INTO @MIKAMI_DB.PUBLIC.MIKAMI_GCS_TEST_STAGE/iceberg_to_bq/
FROM MIKAMI_DB.PUBLIC.ICEBERG_TO_BQ
FILE_FORMAT = (
  TYPE = CSV
  FIELD_DELIMITER = ','
  FIELD_OPTIONALLY_ENCLOSED_BY = '"'
  NULL_IF = ('NULL', 'null', '')
  EMPTY_FIELD_AS_NULL = TRUE
  COMPRESSION = NONE
)
HEADER = TRUE
OVERWRITE = TRUE;

GCS に CSV ファイルがエクスポートできました。

file_export_to_gcs

$ gcloud storage ls --recursive --long gs://test-mikami/ext/
gs://test-mikami/ext/:

gs://test-mikami/ext/iceberg_to_bq/:
       233  2026-05-17T17:11:44Z  gs://test-mikami/ext/iceberg_to_bq/data_0_0_0.csv
TOTAL: 1 objects, 233 bytes (233.00B)

BigQuery で以下の SQL を実行して、外部テーブルを作成してデータを確認してみます。

CREATE OR REPLACE EXTERNAL TABLE
  dataset_1.ext_from_SF_csv
OPTIONS (
  format            = 'CSV',
  uris              = ['gs://test-mikami/ext/iceberg_to_bq/*'],
  skip_leading_rows = 1,
  field_delimiter   = ',',
  quote             = '"'
);

SELECT * FROM dataset_1.ext_from_SF_csv ORDER BY id;

bq_table_data_ext

GCS 上の CSV ファイルのデータが参照できました。

続いて Snowflake で以下の SQL を実行して、データファイルを追加します。

COPY INTO @MIKAMI_DB.PUBLIC.MIKAMI_GCS_TEST_STAGE/iceberg_to_bq/
FROM (
  SELECT
    5                                                                AS id,
    'Eve'                                                            AS name,
    CONVERT_TIMEZONE('Asia/Tokyo', CURRENT_TIMESTAMP())::TIMESTAMP_NTZ AS created_at,
    CONVERT_TIMEZONE('Asia/Tokyo', CURRENT_TIMESTAMP())::TIMESTAMP_NTZ AS updated_at
)
FILE_FORMAT = (
  TYPE = CSV
  FIELD_DELIMITER = ','
  FIELD_OPTIONALLY_ENCLOSED_BY = '"'
  NULL_IF = ('NULL', 'null', '')
  EMPTY_FIELD_AS_NULL = TRUE
  COMPRESSION = NONE
)
HEADER = TRUE
INCLUDE_QUERY_ID = TRUE;
$ gcloud storage ls --recursive --long gs://test-mikami/ext/
gs://test-mikami/ext/:

gs://test-mikami/ext/iceberg_to_bq/:
        98  2026-05-17T17:23:51Z  gs://test-mikami/ext/iceberg_to_bq/data_01c46ed3-0003-d34c-0000-00b40a5efcf6_0_0_0.csv
       233  2026-05-17T17:11:44Z  gs://test-mikami/ext/iceberg_to_bq/data_0_0_0.csv
TOTAL: 2 objects, 331 bytes (331.00B)

再度 BigQuery 外部テーブルデータを確認すると

bq_table_data_ext_add

追加データも正常に参照できることが確認できました。

外部テーブル経由であれば、Iceberg テーブル経由で参照する場合と比べて比較的容易に実装することが可能です。
Iceberg テーブルの場合はデータ追加時に metadata ファイルパスを更新する必要がありますが、外部テーブルであればそのまま参照可能です。
データファイルをダウンロードして開けばすぐ内容を確認できる CSV や JSON 形式もサポートされているので、運用面でのメリットもあるのではないかと思います。

しかし、GCS 上の指定パス配下の全ファイルが参照されるため、データの更新・削除時には、ファイル出力(Snowflake)側または参照(BigQuery)側で制御する必要があり、タイムトラベルも利用できません。
また、クエリのパフォーマンスを考慮すると、高頻度や大量データ連携時の利用は慎重に検討する必要があるかと思います。

まとめ(所感)

Snowflake の Iceberg テーブルデータを、BigQuery の Iceberg テーブルで参照できました。

なお、BigQuery の Iceberg テーブルを Snowflake から参照することも可能です。

https://dev.classmethod.jp/articles/bigquery-tables-for-apache-iceberg-to-snowflake-iceberg-table/

オープンテーブルフォーマットとして互換性が高い印象のある Iceberg テーブルですが、現在のところ BigQuery・Snowflake それぞれが独自のメタデータ管理方式を採用しており、完全な互換性があるわけではありません。
metadata ファイルパスの更新が必要だったり、データの書き込みができなかったりと、利用時にはいくつか制約があります。

とはいえ、データの整合性を考慮した複雑なバッチ処理を実装することなく、更新・削除含むデータの連携が可能な Iceberg テーブルは、異なるプラットフォーム間のデータ連携の構成をシンプルに構築できる嬉しい選択肢だと思いました。

参考


Snowflakeの導入支援はクラスメソッドに!

クラスメソッドでは Snowflake の導入を支援しております。
製品の詳細や支援の内容についてお気軽にお問い合わせください。

Snowflakeの詳細を見る

この記事をシェアする

関連記事