Spanner にデータをエクスポートする(リバース ETL)

このドキュメントでは、BigQuery から Spanner への逆方向の抽出、変換、読み込み(リバース ETL)ワークフローを設定する方法について説明します。これを行うには、EXPORT DATA ステートメントを使用して、Iceberg テーブルなどの BigQuery データソースから Spanner テーブルにデータをエクスポートします。

このリバース ETL ワークフローは、BigQuery の分析機能と Spanner の低レイテンシ、高スループットを組み合わせたものです。このワークフローでは、BigQuery の割り当てと上限を使い切ることなく、アプリケーション ユーザーにデータを配信できます。

始める前に

必要��ロール

BigQuery データを Spanner にエクスポートするために必要な権限を取得するには、プロジェクトに対して次の IAM ロールを付与するよう管理者に依頼してください。

ロールの付与については、プロジェクト、フォルダ、組織へのアクセス権の管理をご覧ください。

必要な権限は、カスタムロールや他の事前定義ロールから取得することもできます。

制限事項

  • この機能は Assured Workloads ではサポートされていません。

  • 次の BigQuery データ型は Spanner に同等のものがなく、サポートされていません。

Spanner データベース言語 サポートされていない BigQuery の型
すべての言語
  • STRUCT
  • GEOGRAPHY
  • DATETIME
  • RANGE
  • TIME
GoogleSQL
  • BIGNUMERIC: サポートされている NUMERIC 型が十分に広くありません。クエリの NUMERIC 型に明示的なキャストを追加することを検討してください。
  • エクスポートされる行の最大サイズは 1 MiB です。

  • Spanner は、エクスポート中に参照完全性を適用します。ターゲット テーブルが別のテーブルの子テーブルである場合(INTERLEAVE IN PARENT)や、ターゲット テーブルに外部キー制約がある場合、外部キーと親キーはエクスポート中に検証されます。エクスポートされ��行が INTERLEAVE IN PARENT を使用してテーブルに書き込まれ、親行が存在しない場合、エクスポートは失敗し、「親行がありません。行を書き込めません」というエラーが表示されます。エクスポートされた行が外部キー制約のあるテーブルに書き込まれ、存在しないキーを参照している場合、エクスポートは失敗し、「外部キー制約に違反しています」というエラーが表示されます。複数のテーブルにエクスポートする場合は、エクスポート中に参照完全性が維持されるようエクスポートの順序付けを行うことをおすすめします。通常、これは、親テーブルと外部キーで参照されるテーブルを、それらを参照するテーブルの前にエクスポートすることを意味します。

    エクスポートの対象となるテーブルに外部キー制約がある場合、または別のテーブルの子テーブルである場合(INTERLEAVE IN PARENT)、子テーブルのエクスポート前に親テーブルにデータを入力し、対応するすべてのキーを含める必要があります。親テーブルに関連するキーがすべて含まれていない場合、子テーブルをエクスポートしようとすると失敗します。

  • Spanner への抽出ジョブなどの BigQuery ジョブの最大期間は 6 時間です。大規模な抽出ジョブの最適化については、エクスポートの最適化をご覧ください。または、入力を個々のデータブロックに分割し、個々の抽出ジョブとしてエクスポートできるようにすることを検討してください。

  • Spanner へのエクスポートは、BigQuery Enterprise エディションまたは Enterprise Plus エディションでのみサポートされています。BigQuery Standard エディションとオンデマンド コンピューティングはサポートされていません。

  • 継続的クエリを使用して、自動生成された主キーを持つ Spanner テーブルにエクスポートすることはできません。

  • 継続的クエリを使用して、PostgreSQL 言語データベースの Spanner テーブルにエクスポートすることはできません。

  • 継続的クエリを使用して Spanner テーブルにエクスポートする場合は、BigQuery テーブルで単調に増加する整数に対応しない主キーを選択してください。これを行うと、エクスポートでパフォーマンスの問題が発生する可能性があります。Spanner の主キーと、これらのパフォーマンスの問題を軽減する方法については、主キーを選択するをご覧ください。

spanner_options オプションを使用してエクスポートを構成する

spanner_options オプションを使用して、宛先の Spanner デ��タベースとテーブルを指定できます。構成は、次の例に示すように、JSON 文字列の形式で表現されます。

EXPORT DATA OPTIONS(
   uri="https://spanner.googleapis.com/projects/PROJECT_ID/instances/INSTANCE_ID/databases/DATABASE_ID",
  format='CLOUD_SPANNER',
   spanner_options = """{
      "table": "TABLE_NAME",
      "change_timestamp_column": "CHANGE_TIMESTAMP",
      "priority": "PRIORITY",
      "tag": "TAG",
   }"""
)

次のように置き換えます。

  • PROJECT_ID: Google Cloud プロジェクトの名前。
  • INSTANCE_ID: データベース インスタンスの名前。
  • DATABASE_ID: データベースの名前。
  • TABLE_NAME: 既存の宛先テーブルの名前。
  • CHANGE_TIMESTAMP: 宛先 Spanner テーブル内の TIMESTAMP 型の列の名前。このオプションは、エクスポート時に最新の行の更新のタイムスタンプを追跡するために使用されます。このオプションを指定すると、エクスポートは最初に Spanner テーブルの行の読み取りを実行し、最新の行の更新のみが書き込まれるようにします。継続的エクスポートの実行時には、同じ主キーを持つ行の変更順序が重要なため、TIMESTAMP 型の列を指定することをおすすめします。
  • PRIORITY(省略可): 書き込みリクエストの優先度。使用できる値: LOWMEDIUMHIGH。デフォルト値: MEDIUM
  • TAG(省略可): Spanner モニタリングでエクスポータ トラフィックを識別するために使用されるリクエストタグ。デフォルト値: bq_export

エクスポート クエリの要件

クエリ結果を Spanner にエクスポートするには、結果が次の要件を満たしている必要があります。

  • 結果セット内のすべての列が宛先テーブルに存在し、型が一致しているか、変換可能である必要があります。
  • 結果セットには、宛先テーブルのすべての NOT NULL 列が含まれている必要があります。
  • 列の値は、Spanner のテーブル内のデータサイズの上限を超えないようにする必要があります。
  • サポートされていない列の型は、Spanner にエクスポートする前に、サポートされている型のいずれかに変換する必要があります。

型変換

便宜上、Spanner エクスポータは次の型変換を自動的に適用します。

BigQuery の型 Spanner の型
BIGNUMERIC NUMERIC(PostgreSQL 言語のみ)
FLOAT64 FLOAT32
BYTES PROTO
INT64 ENUM

データのエクスポート

EXPORT DATA ステートメントを使用して、BigQuery テーブルから Spanner テーブルにデータをエクスポートできます。

次の例では、mydataset.table1 という名前のテーブルから選択したフィールドをエクスポートします。

EXPORT DATA OPTIONS (
  uri="https://spanner.googleapis.com/projects/PROJECT_ID/instances/INSTANCE_ID/databases/DATABASE_ID",
  format='CLOUD_SPANNER',
  spanner_options="""{ "table": "TABLE_NAME" }"""
)
AS SELECT * FROM mydataset.table1;

次のように置き換えます。

  • PROJECT_ID: Google Cloud プロジェクトの名前
  • INSTANCE_ID: データベース インスタンスの名前
  • DATABASE_ID: データベースの名前
  • TABLE_NAME: 既存の宛先テーブルの名前

同じ rowkey 値を含む複数の結果をエクスポートする

同じ rowkey 値を持つ複数の行を含む結果をエクスポートすると、Spanner に書き込まれた値は同じ Spanner 行に格納されます。エクスポートによって生成された Spanner 行セットには、一致する BigQuery 行が 1 つだけ存在します(どの行が一致するかは保証されません)。

継続的にエクスポートする

エクスポート クエリを継続的に処理するには、継続的クエリを作成するの手順とサンプルコードをご覧ください。

エクスポートの最適化

BigQuery から Spanner へのレコードのエクスポートを最適化するには、次の方法をお試しください。

  • Spanner 宛先インスタンスのノード数を増やします。エクスポートの初期段階では、インスタンスのノード数を増やしても、エクスポート スループットがすぐに向上しない場合があります。Spanner が負荷に基づいた分割を実行する間、若干の遅延が生じることがあります。負荷に基づいた分割により、エクスポート スループットが向上し、安定します。EXPORT DATA ステートメントを使用すると、データがバッチ処理され、Spanner への書き込みが最適化されます。詳細については、パフォーマンスの概要をご覧ください。

  • spanner_options 内で優先度を HIGH に指定��ます。Spanner インスタンスで自動スケーリングが有効になっている場合、優先度を HIGH に設定すると、CPU 使用率がスケーリングをトリガーするために必要なしきい値に確実に達します。これにより、オートスケーラーはエクスポートの負荷に応じてコンピューティング リソースを追加できるため、エクスポートのスループット全体を向上させることができます。

    次の例は、優先度が HIGH に設定された Spanner エクスポート コマンドを示しています。

    EXPORT DATA OPTIONS (
      uri="https://spanner.googleapis.com/projects/PROJECT_ID/instances/INSTANCE_ID/databases/DATABASE_ID",
      format='CLOUD_SPANNER',
      spanner_options="""{ "table": "TABLE_NAME", "priority": "HIGH" }"""
    )
  • クエリ結果を並べ替えないようにします。結果セットにすべての主キー列が含まれている場合は、書き込みを合理化して競合を最小限に抑えるために、エクスポータが宛先テーブルの主キーを自動的に並べ替えます。

    宛先テーブルの主キーに生成列が含まれている場合は、生成列の式をクエリに追加することで、エクスポートされたデータが適切に並べ替えられ、バッチ処理されるようにします。

    たとえば、次の Spanner スキーマでは、SaleYearSaleMonth は Spanner 主キーの先頭を構成する生成列です。

    CREATE TABLE Sales (
      SaleId STRING(36) NOT NULL,
      ProductId INT64 NOT NULL,
      SaleTimestamp TIMESTAMP NOT NULL,
      Amount FLOAT64,
      -- Generated columns
      SaleYear INT64 AS (EXTRACT(YEAR FROM SaleTimestamp)) STORED,
      SaleMonth INT64 AS (EXTRACT(MONTH FROM SaleTimestamp)) STORED,
    ) PRIMARY KEY (SaleYear, SaleMonth, SaleId);

    主キーで生成列が使用されている BigQuery から Spanner テーブルにデータをエクスポートする場合は、これらの生成列の式を EXPORT DATA クエリに含めることが推奨されます(必須ではありません)。これにより、BigQuery はデータを事前に正しく並べ替えることができます。これは、効率的なバッチ処理と Spanner への書き込みに不可欠です。EXPORT DATA ステートメントの生成列の値は、Spanner によって自動生成されるため、Spanner ではコミットされませんが、エクスポートの最適化に使用されます。

    次の例では、主キーで生成列を使用している Spanner Sales テーブルにデータをエクスポートします。書き込みパフォーマンスを最適化するため、クエリには生成された SaleYear 列と SaleMonth 列に一致する EXTRACT 式が含まれています。これにより、BigQuery はエクスポート前にデータを並べ替えることができます。

    EXPORT DATA OPTIONS (
      uri="https://spanner.googleapis.com/projects/PROJECT_ID/instances/INSTANCE_ID/databases/DATABASE_ID",
      format='CLOUD_SPANNER',
      spanner_options="""{ "table": "Sales" }"""
    )
    AS SELECT
      s.SaleId,
      s.ProductId,
      s.SaleTimestamp,
      s.Amount,
      -- Add expressions that match the generated columns in the Spanner PK
      EXTRACT(YEAR FROM s.SaleTimestamp) AS SaleYear,
      EXTRACT(MONTH FROM s.SaleTimestamp) AS SaleMonth
    FROM my_dataset.sales_export AS s;
  • ジョブが長時間実行されないように、パーティションごとにデータをエクスポートします。クエリのタイムスタンプなどのパーティション キーを使用して、BigQuery データをシャーディングします。

    EXPORT DATA OPTIONS (
      uri="https://spanner.googleapis.com/projects/PROJECT_ID/instances/INSTANCE_ID/databases/DATABASE_ID",
      format='CLOUD_SPANNER',
      spanner_options="""{ "table": "TABLE_NAME", "priority": "MEDIUM" }"""
    )
    AS SELECT *
    FROM 'mydataset.table1' d
    WHERE
    d.timestamp >= TIMESTAMP '2025-08-28T00:00:00Z' AND
    d.timestamp < TIMESTAMP '2025-08-29T00:00:00Z';

    これにより、クエリは 6 時間のジョブ実行時間内に完了します。これらの上限の詳細については、クエリジョブの上限をご覧ください。

  • データの読み込みパフォーマンスを向上させるために、データがインポートされる Spanner テーブルのインデックスを削除します。インポートが完了したら、インデックスを再作成します。

  • 1 つの Spanner ノード(1,000 PU)と最小限の BigQuery スロット予約から始めることをおすすめします。たとえば、100 スロット、または自動スケーリングを使用する 0 ベースライン スロットなどです。100 GB 未満のエクスポートの場合、この構成は通常 6 時間のジョブ上限内に完了します。100 GB を超えるエクスポートの場合は、必要に応じて Spanner ノードと BigQuery スロット予約をスケールアップして、スループットを向上させます。スループットは、ノードあたり約 5 MiB/秒でスケーリングされます。

料金

EXPORT DATA ステートメントを使用して Spanner にデータをエクスポートする場合は、BigQuery 容量コンピューティングの料金が適用されます。

継続的クエリを使用して Spanner に継続的にエクスポートするには、BigQuery Enterprise エディションまたは Enterprise Plus エディションのスロット予約と、CONTINUOUS ジョブタイプを使用する予約の割り当��������要��す。

リージョン境界を越える BigQuery から Spanner へのエクスポートの料金には、データ抽出レートが適用されます。詳細については、BigQuery の料金をご覧ください。データ転送料金が発生しないようにするには、BigQuery のエクスポートが Spanner のデフォルトのリーダーと同じリージョンで行われるようにします。継続的クエリのエクスポートでは、リージョン境界を越えるエクスポートはサポートされていません。

データのエクスポート後、Spanner にデータを保存すると料金が発生します。詳細については、Spanner の料金をご覧ください。