BigQuery ハンズオン教科書|2026-08 版
第 10 章

外部データとレイクハウス — BigLake・Iceberg・フェデレーション

ロードせずに問い合わせる。オープンフォーマットで「囲い込まれない」DWH を組む

この章で学ぶこと

前提: ハンズオン 10-1 はサンドボックスで完走可・無料枠内。10-2 と 10-3 は課金プロジェクト必須(概算 数円)。

「ロードしない」という選択肢

第 3 章では、外部のデータを BigQuery のネイティブストレージへロードして使う方法を学びました。ロードには「クエリが速い」「BigQuery のフル機能が使える」という利点がありますが、裏を返せば「コピーを作る」ということです。元データが更新されるたびに再ロードが必要になり、ストレージも二重に持つことになります。

BigQuery には、データを移動させずにその場でクエリするための経路が複数あります。代表的なものが次の 4 つです。

外部のデータ BigQuery 側 GCS / Iceberg ロード(コピー) ネイティブテーブル GCS / Drive クエリの都度アクセス 外部テーブル S3 / GCS 委任アクセス + キャッシュ BigLake テーブル Cloud SQL 等 EXTERNAL_QUERY 転送 フェデレーテッドクエリ
図 10-1 ロードは「コピーして速さを買う」、残り 3 経路は「その場に置いたまま問い合わせる」。BigLake テーブルだけがアクセス委任とキャッシュを持つ点が外部テーブルとの分岐点。

共通点は「BigQuery のネイティブストレージにデータを複製しない」こと。違いは、誰が元データへの権限を持つかとメタデータをキャッシュできるかです。この 2 点が、この章全体を貫く軸になります。

実務の視点: 「とりあえず外部テーブルでつなぐ」は入口として悪くありませんが、複数チームが同じ Cloud Storage バケットを参照する構成になった瞬間、権限管理が破綻しがちです。全員に storage.objectViewer を配って回るのか、BigLake テーブルでアクセスを委任し BigQuery の IAM だけで完結させるのかは、早い段階で決めておく価値があります。

BigLake テーブル — アクセス委任とメタデータキャッシュ

BigLake テーブルは、Cloud Storage・Amazon S3・Azure Blob Storage 上の構造化データを、通常の外部テーブルと同じ CREATE EXTERNAL TABLE 文の延長で扱えるようにする仕組みです。ただし Amazon S3・Azure Blob Storage 上に作る場合は BigQuery Omni の扱いになり、データセット自体を Omni 対応リージョン(aws-us-east-1 など)に作成する必要があります。本書既定の US マルチリージョンのデータセットに作れるのは Cloud Storage の BigLake テーブルだけです(Omni は後述)。公式ドキュメントは通常の外部テーブルとの違いを次のように説明しています。

Access delegation decouples access to the BigLake table from access to the underlying data store.

普通の外部テーブルでは、クエリを投げるユーザー本人が「外部テーブルへの権限」と「Cloud Storage バケットそのものへの権限」の両方を持っている必要があります。BigLake テーブルでは、BigQuery Connection リソースに紐づくサービスアカウントがバケットへのアクセス権限を持ち、ユーザーは BigLake テーブルへの IAM 権限だけを持てば十分になります。バケットの権限管理をデータエンジニアリング側に集約し、分析者には行レベル・列レベルセキュリティを含む BigQuery 側の権限モデルだけを意識させられるのが利点です。

分析者 BigLake 権限のみ BigLake テーブル 委任 Connection サービスアカウント storage.objectViewer Cloud Storage バケット メタデータキャッシュ 自動更新: 通常 30〜60 分間隔 手動更新: BQ.REFRESH_EXTERNAL_METADATA_CACHE staleness 上限: 30 分〜7 日で設定
図 10-2 分析者は BigLake テーブルへの IAM 権限だけを持てばよく、GCS バケットへの実アクセスは Connection のサービスアカウントが代行する。ファイル一覧などのメタデータはキャッシュされ、staleness の許容範囲を超えると初めてデータストアへ取りに行く。

メタデータキャッシュは、ファイル数が多い外部データセットで特に効果を発揮します。クエリのたびに Cloud Storage のファイル一覧を取得し直すのではなく、キャッシュされたメタデータ(ファイル名・パーティション情報・行数など)を使うことでプランニングが速くなります。対応するのはオブジェクトテーブル、Amazon S3 BigLake テーブル、Cloud Storage BigLake テーブルの 3 種類で、素の Cloud Storage 外部テーブルにはこの機能はありません。

比較軸外部テーブルBigLake テーブル
データストアへの権限クエリするユーザー本人が必要Connection のサービスアカウントに委任
行・列レベルセキュリティ非対応対応
メタデータキャッシュ非対応対応(staleness 30 分〜7 日)
対応データソースCloud Storage・Google Drive・Bigtable 等Cloud Storage・Amazon S3・Azure Blob Storage

Apache Iceberg 対応 — 「外部」と「マネージド」を区別する

Apache Iceberg はオープンなテーブルフォーマットで、複数のクエリエンジン(Spark・Trino・BigQuery など)から同じデータを読み書きできることが最大の価値です。BigQuery の Iceberg 対応は、性質の異なる 2 つの機能に分かれています。名前が紛らわしいので、現行ドキュメントの表記を正として整理します。

Iceberg external tables(読み取り専用) Spark / Trino 書き込み GCS 上の Iceberg メタデータ 読み取り BigQuery Iceberg managed tables(BigQuery が書き込みを管理) INSERT / UPDATE DML BigQuery テーブル Parquet 保存 自分の GCS バケット → どちらも Iceberg フォーマットなので、他エンジンから見れば同じ「オープンなテーブル」に見える
図 10-3 Iceberg external tables は他エンジンが書いたデータを読むだけ、Iceberg managed tables は BigQuery 自身が DML でテーブルのライフサイクルを管理する。書き込み主体がどちらかで選ぶ。

Iceberg マネージドテーブルの作成は、通常の CREATE TABLE に table_format = 'ICEBERG' と保存先を指定するだけです。

CREATE TABLE `PROJECT_ID.handson_ch10.iceberg_events`
(
  event_id INT64,
  event_name STRING,
  event_ts TIMESTAMP
)
WITH CONNECTION `PROJECT_ID.us.biglake_conn`
OPTIONS (
  file_format = 'PARQUET',
  table_format = 'ICEBERG',
  storage_uri = 'gs://PROJECT_ID-bq-handson/iceberg/events'
);

一方、Iceberg 外部テーブルは既存の Iceberg メタデータファイルを指すだけです。

CREATE EXTERNAL TABLE `PROJECT_ID.handson_ch10.iceberg_external`
WITH CONNECTION `PROJECT_ID.us.biglake_conn`
OPTIONS (
  format = 'ICEBERG',
  uris = ['gs://other-team-bucket/lake/events/metadata/00003.metadata.json']
);

Iceberg 外部テーブルには注意点があります。参照する metadata.json は手動で最新のものに更新する必要があり、他エンジンが新しいスナップショットを書いても自動追従しません。頻繁に更新されるテーブルを他エンジンと共有するなら、マネージドテーブルか、カタログ経由(AWS Glue Data Catalog・Lakehouse ランタイムカタログ)での接続を検討してください。

オブジェクトテーブル・フェデレーテッドクエリ・Omni・Drive 連携

非構造化データにも SQL で触れる仕組みがあります。オブジェクトテーブルは Cloud Storage 上の画像・PDF・音声などのファイルを、uri・content_type・size といった固定スキーマの読み取り専用テーブルとして見せます。ファイル本体はテーブルに取り込まれず、参照だけが行と対応します。

CREATE EXTERNAL TABLE `PROJECT_ID.handson_ch10.product_images`
WITH CONNECTION `PROJECT_ID.us.biglake_conn`
OPTIONS (
  object_metadata = 'SIMPLE',
  uris = ['gs://PROJECT_ID-bq-handson/images/*']
);

オブジェクトテーブルの真価は、AI.GENERATE_TEXT・ML.ANNOTATE_IMAGE・ML.PROCESS_DOCUMENT といった AI 関数と組み合わせ、画像や文書に対して SQL 一発で推論をかけられる点にあります。この使い方は第 14 章(オブジェクトテーブルと AI 活用)で本格的に扱います。ここでは「非構造化データを SQL の世界に引き込む入口」として位置づけを押さえておいてください。

フェデレーテッドクエリは、AlloyDB・Spanner・Cloud SQL(および 2026-08 時点でプレビューの SAP Datasphere)に対して、その場でクエリを送り結果を一時テーブルとして受け取る仕組みです。EXTERNAL_QUERY 関数を使います。

SELECT *
FROM EXTERNAL_QUERY(
  'PROJECT_ID.us.cloudsql_conn',
  'SELECT order_id, status FROM orders WHERE status != ''CANCELLED'''
);

実行される SQL は接続先データベースの方言(MySQL / PostgreSQL 等)で書き、読み取り専用に限られます。パフォーマンスはネイティブの BigQuery テーブルより劣り、クロスリージョンのクエリには 1 日あたり 1 TB という上限があるため、大量データの定常的な分析には向きません。「トランザクション DB の最新状態と BigQuery 側の分析データを都度突き合わせたい」といった、小〜中規模のルックアップ用途に向いています。

BigQuery Omni は、Amazon S3・Azure Blob Storage に置いたままのデータに対して BigQuery のクエリエンジンを実行する仕組みです。データを Google Cloud 側に移動させず、コンピュートの方をデータの近く(AWS 6 リージョン・Azure 1 リージョンで提供)に配置します。BigQuery の料金モデルがそのまま適用されますが、DML・BigQuery ML・JavaScript UDF は非対応で、クロスクラウドのデータ転送にはエグレス料金がかかります。「マルチクラウドでデータがサイロ化しているが、分析基盤は BigQuery に統一したい」という組織向けの選択肢です。

Google スプレッドシートや Google ドライブ上のファイルも、Cloud Storage と同様に外部テーブルとして参照できます。スプレッドシートを都度エクスポートする運用から抜け出し、業務部門が更新するシートを BigQuery 側からそのまま SQL で読みに行けるのが利点です。ただし更新頻度の高い共同編集シートを分析の主データソースにするのは避け、あくまで補助的なマスタデータの参照に留めるのが安全です。

元データは今も更新中? いいえ(安定) ロード(第 3 章) はい(都度最新が欲しい) GCS/S3/Iceberg?→他DB(Cloud SQL等)? 権限委任が要る BigLake / Iceberg テーブル フェデレーテッドクエリ
図 10-4 最初の分岐は「元データが動き続けているか」。動いていなければロード一択。動いているなら、権限委任やキャッシュが要るか(BigLake / Iceberg)、単発の突き合わせで済むか(フェデレーテッドクエリ)で分ける。
選択肢鮮度性能ガバナンスコスト傾向
ロード再ロードするまで固定最速BigQuery IAM のみストレージ二重化
外部テーブル常に最新キャッシュ無しでやや遅いデータストア側の権限が必要ストレージは元のまま
BigLake テーブルstaleness 設定に依存キャッシュで改善アクセス委任で BigQuery 側に集約Connection 管理コスト
フェデレーテッドクエリ常に最新低い(都度転送)接続先 DB の権限が必要転送量に依存
ハンズオン 10-1|Cloud Storage 外部テーブルを作りロードしたテーブルと比べる

公開の GCS バケットにある小さな CSV を外部テーブルとして参照し、同じデータをロードしたテーブルと見比べます。サンドボックスで完走可・無料枠内(スキャン量は数十 KB 程度)。自分のバケットは作りません。

  1. 演習用データセットを作成します(ロケーションは US)。
  2. 外部テーブルを作成します。
bq mk --location=US --dataset PROJECT_ID:handson_ch10

CREATE OR REPLACE EXTERNAL TABLE `PROJECT_ID.handson_ch10.states_external`
(
  name STRING,
  post_abbr STRING
)
OPTIONS (
  format = 'CSV',
  uris = ['gs://cloud-samples-data/bigquery/us-states/us-states.csv'],
  skip_leading_rows = 1
);
  1. クエリを実行し、外部テーブルの内容を確認します(dry run で処理バイト量も確認します)。
-- 処理: 数十 KB 程度(実測は dry run / エディタ右上の見積もりで確認)
SELECT name, post_abbr
FROM `PROJECT_ID.handson_ch10.states_external`
WHERE post_abbr IN ('CA', 'TX', 'NY');

期待される出力: California/CA、Texas/TX、New York/NY の 3 行。

  1. 同じデータをロードしたテーブルを作り、クエリを再実行して速度感を比べます。
bq load --location=US --autodetect --source_format=CSV \
  handson_ch10.states_loaded \
  gs://cloud-samples-data/bigquery/us-states/us-states.csv

外部テーブルはクエリのたびに Cloud Storage へアクセスするため、初回実行にわずかな遅延が乗ります。データ量が小さい今回はほぼ体感できませんが、ファイル数・データ量が増えるほど差が開きます。

後片付け:

bq rm -r -f -d PROJECT_ID:handson_ch10
ハンズオン 10-2|BigLake テーブルでアクセス委任とメタデータキャッシュを体験する

Cloud Storage バケットと BigQuery Connection を作り、BigLake テーブル経由でアクセスを委任します。課金プロジェクト必須(バケット作成・少量のクエリで概算数円)。

  1. 作業用バケットと Connection を作成します(Connection のロケーションはデータセットと合わせて US)。
gcloud storage buckets create gs://PROJECT_ID-bq-handson --location=us

bq mk --connection --location=US \
  --connection_type=CLOUD_RESOURCE \
  biglake_conn

bq show --location=US --connection PROJECT_ID.us.biglake_conn
# 出力の serviceAccountId をメモする
  1. Connection のサービスアカウントに、バケットへの読み取り権限を付与します。
gcloud storage buckets add-iam-policy-binding gs://PROJECT_ID-bq-handson \
  --member="serviceAccount:CONNECTION_SERVICE_ACCOUNT" \
  --role="roles/storage.objectViewer"

echo "name,post_abbr" > /tmp/states.csv
echo "California,CA" >> /tmp/states.csv
gcloud storage cp /tmp/states.csv gs://PROJECT_ID-bq-handson/states/states.csv
  1. BigLake テーブルを、メタデータキャッシュを有効にして作成します。
CREATE OR REPLACE EXTERNAL TABLE `PROJECT_ID.handson_ch10.states_biglake`
(
  name STRING,
  post_abbr STRING
)
WITH CONNECTION `PROJECT_ID.us.biglake_conn`
OPTIONS (
  format = 'CSV',
  uris = ['gs://PROJECT_ID-bq-handson/states/*.csv'],
  skip_leading_rows = 1,
  max_staleness = INTERVAL 1 HOUR,
  metadata_cache_mode = 'AUTOMATIC'
);

-- 処理: 1 KB 未満(無料枠内)
SELECT * FROM `PROJECT_ID.handson_ch10.states_biglake`;

期待される出力: California/CA の 1 行。このテーブルへの SELECT 権限だけを持つユーザーは、バケット自体への権限を一切持たなくてもクエリできることを確認してください(Connection のサービスアカウントが代行しています)。

後片付け:

bq rm -r -f -d PROJECT_ID:handson_ch10
bq rm --connection --location=US PROJECT_ID.us.biglake_conn
gcloud storage rm -r gs://PROJECT_ID-bq-handson
ハンズオン 10-3|Apache Iceberg マネージドテーブルに DML で書き込む

BigQuery が管理する Iceberg テーブルを作成し、DML で更新します。DML はサンドボックスでは実行できないため課金プロジェクト必須(概算数円)。ハンズオン 10-2 で作成した Connection とデータセットを使い回します(削除済みの場合は再作成してください)。

  1. Iceberg マネージドテーブルを作成します。
CREATE TABLE `PROJECT_ID.handson_ch10.iceberg_events`
(
  event_id INT64,
  event_name STRING,
  event_ts TIMESTAMP
)
WITH CONNECTION `PROJECT_ID.us.biglake_conn`
OPTIONS (
  file_format = 'PARQUET',
  table_format = 'ICEBERG',
  storage_uri = 'gs://PROJECT_ID-bq-handson/iceberg/events'
);
  1. DML でデータを投入し、更新します。
INSERT INTO `PROJECT_ID.handson_ch10.iceberg_events`
VALUES (1, 'signup', CURRENT_TIMESTAMP()),
       (2, 'purchase', CURRENT_TIMESTAMP());

UPDATE `PROJECT_ID.handson_ch10.iceberg_events`
SET event_name = 'purchase_completed'
WHERE event_id = 2;

SELECT * FROM `PROJECT_ID.handson_ch10.iceberg_events` ORDER BY event_id;

期待される出力: event_id=1 は signup、event_id=2 は purchase_completed の 2 行。通常の外部テーブルでは INSERT / UPDATE は使えませんが、Iceberg マネージドテーブルは BigQuery のネイティブテーブルと同じ DML が使えます。裏側の Cloud Storage バケットを確認すると、Parquet ファイルと Iceberg のメタデータ(スナップショット履歴)が生成されていることが分かります。

後片付け:

bq rm -r -f -d PROJECT_ID:handson_ch10
bq rm --connection --location=US PROJECT_ID.us.biglake_conn
gcloud storage rm -r gs://PROJECT_ID-bq-handson

つまずきポイント

ロケーション不一致で外部テーブルが作れない: データセットと Cloud Storage バケットのロケーションが一致していないと CREATE EXTERNAL TABLE がエラーになります。本書は一貫して US(マルチリージョン)を使っていますが、既存プロジェクトで別ロケーションのバケットを使う場合は、データセット側もそのロケーションに合わせる必要があります。
BigLake テーブルが Permission denied になる: ユーザーに BigLake テーブルへの権限があっても、Connection のサービスアカウントにバケットへの storage.objectViewer 権限が付与されていないとクエリは失敗します。「テーブルへの権限」と「委任先サービスアカウントへの権限」は別物として、両方を確認してください。
サンドボックスで DML がいきなり弾かれる: Iceberg マネージドテーブルは魅力的な機能ですが、DML はサンドボックスの制限一覧に明記された非対応機能です。「Iceberg マネージドテーブルだから使えるはず」ではなく、DML そのものがサンドボックス不可という前提を思い出してください。
Iceberg 外部テーブルが古いデータを返す: Iceberg 外部テーブルは metadata.json のパスを直接指定するため、他エンジンが新しいスナップショットを書き込んでも自動では追従しません。最新の metadata.json パスへ手動で更新するか、カタログ経由の接続方式に切り替えてください。

理解度チェック

Q1. 外部テーブルと BigLake テーブルの本質的な違いは何ですか?

アクセス委任の有無です。外部テーブルはクエリするユーザー本人がデータストアへの権限も持つ必要がありますが、BigLake テーブルは Connection のサービスアカウントが権限を代行するため、ユーザーは BigLake テーブルへの権限だけで済みます。加えて BigLake テーブルはメタデータキャッシュと行・列レベルセキュリティにも対応します。

Q2. BigLake テーブルのメタデータキャッシュにおける max_staleness の設定可能範囲は?

30 分から 7 日の間で指定します。自動更新モードでは通常 30〜60 分間隔でキャッシュが更新され、手動更新モードでは BQ.REFRESH_EXTERNAL_METADATA_CACHE システムプロシージャで明示的に更新します。

Q3. Apache Iceberg external tables と Apache Iceberg managed tables の違いを説明してください。

external tables は他のエンジン(Spark・Trino 等)が書き込んだ Iceberg データを読み取り専用で参照するものです。managed tables は BigQuery 自身がテーブルのライフサイクルを管理し、DML・スキーマ進化・タイムトラベルに対応するフルマネージドテーブルで、旧称は「BigLake tables for Apache Iceberg in BigQuery」でした。

Q4.(シナリオ)Spark と Trino がすでに書き込んでいる本番の Iceberg データレイクを、BigQuery からは分析目的の読み取りだけに使いたい。BigQuery 側からの書き込みは想定していません。最適な選択肢は?
A. Cloud Storage 外部テーブル B. BigLake テーブル(CSV/Parquet) C. Apache Iceberg managed tables D. Apache Iceberg external tables

D。 書き込みが他エンジン側にあるため、BigQuery 側は読み取り専用の Iceberg external tables で十分です。A は Iceberg のスナップショット・スキーマ進化を意識した読み取りができません。B は Iceberg フォーマットを直接扱う機能ではありません。C は BigQuery 側に書き込み管理を持たせる機能で、他エンジンが主体の運用には噛み合いません。

Q5.(シナリオ)新規にデータレイクテーブルを BigQuery 上で立ち上げ、DML による更新・スキーマ進化・タイムトラベルを活用しつつ、将来的に他エンジンからもオープンフォーマットで読めるようにしておきたい。最適な選択肢は?
A. Cloud Storage 外部テーブル B. フェデレーテッドクエリ C. Apache Iceberg managed tables D. Apache Iceberg external tables

C。 BigQuery 側で DML・スキーマ進化・タイムトラベルを使いながらデータを管理し、実体は Iceberg フォーマットで Cloud Storage に保存されるため他エンジンからも読めます。A は DML 非対応、B はそもそもテーブルを作る機能ではなく都度クエリを転送するだけ、D は他エンジンが書いたデータを読むための機能で新規構築には向きません。

まとめと次章へ