こんにちは!
KDDIアイレットの取り組みとして、本日6月22日から7月3日にかけて「Google Cloud Next '26 / Google I/O やってみた系ブログリレー」がスタートしました!
記念すべき第1日目の今回は、「Spanner Omni」を対象に、実際に試してみた内容をご紹介します。
Spanner Omni を Mac の Docker 上で動かし、 既存の Spanner ベースのバックエンドの統合テスト 449 件を Cloud Spanner Emulator から移行できるか検証しました。 結論から言うと、 アプリ側のコード差分 1 行 + 環境変数だけで全件 PASS し、 実行時間も自分の検証環境では約 3 分の 1 になりました。 この記事では、 Emulator から Omni に乗り換えるとローカル開発がどう変わるかを、 再現可能なレベルでまとめます。
本記事の前提: 本記事は 2026年5月20日時点 の Spanner Omni
2026.r1-betaおよび@google-cloud/spannerv8.7.1 をもとに執筆しています。 Spanner Omni はプレビュー (Pre-GA) のため、 仕様や挙動が頻繁に変わる可能性があります。 実機検証する際は Spanner Omni 公式ドキュメント で最新情報を確認してください。 また本記事内の性能数値・メモリ消費等は 以下の検証条件下 の結果であり、 マシンスペックやワークロードによって大きく変動します。 自分の環境で再計測されることをおすすめします。検証条件: Apple Silicon Mac (arm64) / Docker Desktop 29.2.1 / 物理 RAM 16 GB / 言語: TypeScript (Node.js) / フレームワーク: Hono / SDK
@google-cloud/spannerv8.7.1 / テストフレームワーク: Vitest / ワークロード: 手元の Spanner ベースのバックエンドの統合テスト 449 件 (13 ファイル、vitestで逐次実行maxWorkers: 1) / 計測 N=3 (Omni と Emulator を交互に実行して環境負荷の偏りを平均化)
この記事で分かること
- Spanner Omni は Cloud Spanner Emulator とは 別物の本物の Spanner ソフトウェア で、 ローカル開発で使う価値が大きい
- 手元の Spanner ベースのバックエンド (DDL 1,436 行 / 68 テーブル) の統合テスト 449 件を、 アプリ側コード差分 1 行 + env だけで Omni に切り替えて全件 PASS できた
- Omni には
SPANNER_SYS50 テーブルとEXPLAIN ANALYZEがフルで載っていて、 本番相当の観測が手元でできる - 自分の検証条件下では、 統合テストの実行時間が Emulator の約 3 分の 1 になった (条件依存、 後述)
- プレビュー版の制約 (90 日で書き込み停止、 TLS 非対応、 バックアップ未対応) と運用での吸収方法
はじめに
Cloud Spanner を業務で使っている皆さん、 ローカル開発で gcr.io/cloud-spanner-emulator/emulator を使っているのではないでしょうか。 自分もそうでした。 Emulator は便利なのですが、 「本番でこのクエリどう動くか手元で確認したい」 と思ったとき、 EXPLAIN ANALYZE がほぼ機能しないことに気づいて、 ため息をついた経験は Spanner ユーザーなら一度はあると思います。
そんな矢先、 Google から Spanner Omni という製品がプレビューで出てきました。 これは 「Cloud Spanner の本物のソフトウェアを、 自分のノートパソコンやオンプレミスにダウンロードして動かせる」 というものです。
自分は半信半疑で Mac の Docker 上に立てて、 手元の Spanner ベースのバックエンド (DDL 1,436 行 / 68 テーブル / 統合テスト 449 件) をそのままぶつけてみました。 結論から言うと、 env を渡すだけで全部 PASS しました。 しかも自分の検証環境では実行時間が既存 Emulator の約 3 分の 1 になりました (この数値はマシンとワークロード次第で変わるので、 後述します) 。
この記事ではその検証手順と、 Emulator からどう変わるかを、 再現可能なレベルで書き残しておきます。 同じく Spanner を使っている方の参考になればと思います。
想定読者
この記事は次のような方を想定しています。
- Cloud Spanner を業務で使っており、 ローカル開発で
cloud-spanner-emulatorを使っている方 - Emulator の制約 (
EXPLAIN ANALYZE不在、SPANNER_SYSがスカスカ、 PostgreSQL 方言非対応) に困った経験がある方 - Spanner Omni が話には聞いたことがあるが、 自分の手元のコードで使えるのか半信半疑な方
- Apple Silicon Mac で arm64 ネイティブで動くのか気になる方
- 本物の Cloud Spanner にお金をかけずに、 ローカルでクエリプランやロック競合を観測したい方
逆に 「Spanner って何?」 というレベルの方には少し前提知識が要ります。 Cloud Spanner の INTERLEAVE や allow_commit_timestamp を一度でも書いたことがあれば読み進められるはずです。
Spanner Omni とは何か
公式の説明はこうです。
Google の分散データベース技術をオンプレミス データセンター、 パブリック クラウド、 ノートパソコンにデプロイできる Spanner のダウンロード版
簡単に言えば、 本物の Cloud Spanner のサーバーソフトウェアを、 自分の環境で動かせる版 です。 Paxos によるレプリケーション、 TrueTime API、 自動シャーディング、 本番相当のクエリオプティマイザがすべて入っています。
これと混同しがちなのが、 従来からある gcr.io/cloud-spanner-emulator/emulator ですが、 両者は 設計思想が異なる別物 です。 Emulator は Cloud Spanner の API 互換を提供する開発用ツール で、 軽量で起動が速く CI で使いやすい一方、 内部実装は Cloud Spanner と別物です。 Omni は Cloud Spanner のサーバーソフトウェアそのもの をローカルで動かせる版で、 本番と同じオプティマイザ・統計・トランザクション機構が動きます。 用途で言うと Emulator は 「API レベルの動作確認」、 Omni は 「本番に近い挙動の手元再現」 という棲み分けだと自分は理解しています。
参考:
- Spanner Omni overview - Google Cloud Documentation
- Cloud Spanner Emulator - Google Cloud Documentation
Emulator と Omni の比較表
両者の違いを公式ドキュメントベースで一覧にします。
| 項目 | Cloud Spanner Emulator | Spanner Omni (2026.r1-beta) |
|---|---|---|
| 配布形式 | Docker image (gcr.io) のみ | Docker image + Helm chart + TAR (Mac arm/x86, Linux arm/x86) |
| 対応 SQL 方言 | GoogleSQL のみ | GoogleSQL + PostgreSQL + Spanner Graph (GQL) |
| TrueTime | 模擬 (固定遅延) | 本物に近い実装 |
| マルチプレックスセッション | 限定的 (デフォルト ON が推奨) | 必須 |
統計テーブル (SPANNER_SYS) |
ほぼ空 (1 テーブル) | 本番相当 (50 テーブル) |
クエリプラン (EXPLAIN ANALYZE) |
取得不可 | 取得可能 (本物のオプティマイザ) |
| インスタンス/データベース管理 API | あり | あり |
| バックアップ・復元 | 部分対応 | プレビュー版では未対応 |
| TLS | 非対応 | プレビュー版では非対応 (GA で対応予定) |
| 推奨 RAM | 256 MB- | 4 GB (Mac M シリーズ) |
| 期限 | 制限なし | デプロイ作成から 90 日でデータ書き込み停止 (プレビュー) |
| 用途 | 開発・CI | 開発・CI・性能チューニング・本番事前検証 |
「マルチプレックスセッション必須」 がハマりどころなので、 後で詳しく書きます。
セットアップ手順 (Mac M シリーズ + Docker)
ここからは実機での手順です。 自分の環境は次のとおりです。
- Apple Silicon (arm64) / Docker Desktop 29.2.1
- Mac の物理 RAM 16 GB
Omni のコンテナイメージは docker manifest inspect で確認したところ arm64 ネイティブ に対応していました。 Rosetta エミュレーション無しで動きます。
1. Docker image を pull
docker pull us-docker.pkg.dev/spanner-omni/images/spanner-omni:2026.r1-beta
サイズは約 1 GB です。 申請も認証も不要 で、 公開リポジトリから直接 pull できます。 これは大きいと思っていて、 商用版だと 「営業に連絡してください」 フローが多いのですが、 Omni は試すハードルが低いです。
2. docker-compose.yml を書く
services:
spanner-omni:
image: us-docker.pkg.dev/spanner-omni/images/spanner-omni:2026.r1-beta
container_name: spanner-omni
command: ["start-single-server"]
ports:
- "15000-15025:15000-15025"
- "15026:15026"
volumes:
- spanner-omni-data:/spanner
volumes:
spanner-omni-data:
公式 quickstart は --network host を推奨していますが、 Mac の Docker Desktop では bridge + 明示ポートマッピングの方が安定しました。 これは Mac 特有の事情で、 Linux ホストなら host モードでも問題ないはずです。
ポート用途:
| port | 用途 |
|---|---|
| 15000 | クライアントから接続する gRPC エンドポイント (デフォルト) |
| 15000-15025 | Omni 内部サービス群 |
| 15026 | Console UI |
3. 起動と疎通確認
docker compose up -d
docker exec spanner-omni /google/spanner/bin/spanner databases list
下記のような出力が得られれば成功です。
NAME STATE VERSION_RETENTION_PERIOD ...
spanner-info READY 1h ...
spanner-info という内部 DB が READY 状態になっていれば、 Omni のサーバーは正常稼働しています。 Console UI は http://localhost:15026 をブラウザで開けば使えます。
4. データベース作成と DDL 適用
docker exec spanner-omni /google/spanner/bin/spanner databases create my-db
docker cp ./schema.sql spanner-omni:/tmp/schema.sql
docker exec spanner-omni /google/spanner/bin/spanner databases ddl update my-db --ddl-file=/tmp/schema.sql
自分の検証では、 手元の 1,436 行 / 68 テーブル / 53 箇所の INTERLEAVE / 名前付きスキーマ (CREATE SCHEMA) / allow_commit_timestamp / NEW_UUID() を含む DDL をそのまま投入しました。 全部無修正で通りました。 Spanner 固有構文の互換性は心配しなくてよさそうです。
参考:
既存の統合テスト 449 件を Spanner Omni に移行する
手元の Spanner ベースのバックエンド (Hono + @google-cloud/spanner ^8.7.1 + Vitest 構成) で、 統合テスト 13 ファイル / 449 ケース を Omni に向け替えて実行します。
切替に必要だった作業
実は驚くほど少なかったです。
- アプリのソースコード差分は 1 行のみ
- 実行時に環境変数を 4 つ渡す
- テストコードは一切いじらない
それぞれ説明します。
ソース差分: 1 行
自分が触っているプロジェクトに 元から書かれていた Spanner クライアント初期化部分はこうでした
(既存実装) 。
if (config.useEmulator) {
process.env.SPANNER_EMULATOR_HOST = "localhost:9010";
}
この書き方は Emulator しか使わない時代の名残で、 env が外から SPANNER_EMULATOR_HOST=localhost:15000 のように渡されていても、 この行でハードコードの localhost:9010 に上書きされてしまいます。 Omni を使う / 使わないに関わらず、 env を尊重する設計に直すべき部分 で、 null 合体代入演算子 (??=) に変えるだけで OK です。
- process.env.SPANNER_EMULATOR_HOST = "localhost:9010";
+ process.env.SPANNER_EMULATOR_HOST ??= "localhost:9010";
これで 「env が未設定ならデフォルト 9010、 外から設定されていればそれを尊重」 になります。 Emulator を使う既存ワークフローも壊しません。
実行時の env
SPANNER_EMULATOR_HOST=localhost:15000 \
GOOGLE_CLOUD_SPANNER_MULTIPLEXED_SESSIONS=true \
GOOGLE_CLOUD_SPANNER_MULTIPLEXED_SESSIONS_FOR_RW=true \
GOOGLE_CLOUD_SPANNER_MULTIPLEXED_SESSIONS_PARTITIONED_OPS=true \
NODE_ENV=test \
npm run test:integration
SPANNER_EMULATOR_HOST は Emulator 向けに作られた env ですが、 Omni に向ければ Omni に繋がります。 クライアント SDK 視点で見れば 「TLS なしの gRPC で叩く接続先」 を切り替えているだけなので、 Emulator と Omni を同じ env で扱えます。
ハマりどころ: マルチプレックスセッション
これがハマるとそもそも接続できません 。 Spanner Omni はマルチプレックスセッションが必須なのですが、 SDK バージョンによっては明示設定が必要になります。 上記 3 つの env を true にしておくのが安全です。
自分は最初これを知らずに接続エラーで 30 分溶かしました。 クライアントが @google-cloud/spanner v8 系であれば env で有効化できます。 古いクライアントだとそもそも対応していないので、 Omni に行く前にバージョンを上げるのをおすすめします。
なお SDK v8 後期のバージョン (@google-cloud/spanner v8.7.x など) では Multiplexed セッションがデフォルト ON になっているため、 env を明示しなくても動きます。 ただし false を明示してしまうと Omni からは Please use multiplexed sessions のエラーで接続拒否されます。
結果
Test Files 13 passed (13)
Tests 449 passed (449)
全 449 件 PASS、 failure ゼロ でした。 Emulator でハマる 「本番だと挙動が違う」 系の不安が消えます。
参考:
- Use the Go client library to connect to Spanner Omni — Node.js 公式ガイドはまだ無いですが、 Go の手順から env と接続オプションの仕組みは推測できます
- Spanner Sessions documentation
性能比較: 統合テスト 449 件を 3 回ずつ
ここからは数字の話です。 ただし数字は環境に強く依存するので、 まず 計測条件 をはっきりさせます。
計測条件 (再掲)
- ホスト: Mac (Apple Silicon arm64) / Docker Desktop 29.2.1 / 物理 RAM 16 GB
- SDK:
@google-cloud/spannerv8.7.1 - ワークロード: 統合テスト 13 ファイル / 449 ケース を
vitestで 逐次実行 (maxWorkers: 1) - 計測 N=3。 Omni と Emulator を 交互に実行 して、 Mac の他プロセス負荷の偏りを平均化
- ストレージ: Docker volume (永続化あり)
結果
| 実行 | Emulator | Omni | 比 (Emu / Omni) |
|---|---|---|---|
| 1 回目 (cold) | 73.68 s | 28.09 s | 2.6 倍 |
| 2 回目 (warm) | 93.38 s | 19.39 s | 4.8 倍 |
| 3 回目 (warm) | 103.59 s | 26.41 s | 3.9 倍 |
| 中央値 | 93.38 s | 26.41 s | 3.5 倍 |
※ 上記は 自分の検証条件下 の値です。 マシン世代、 RAM 量、 Docker のリソース割り当て、 ワークロードの内訳 (読み中心 / 書き中心)、 テストの並列度、 cold/warm の状態などで変動します。 「常に Omni が 3.5 倍速い」 と断言できるものではない点に注意してください。
中央値で Omni は Emulator の約 3.5 倍速い という結果になりました。 warm 状態ほど差が広がる傾向で、 これは Emulator が累積データで遅くなる一方、 Omni はキャッシュが効くためと自分は推測しています。
メモリと CPU
メモリと CPU はこんな具合でした (docker stats を --no-stream で複数回サンプリング) 。
| 状態 | Emulator | Omni |
|---|---|---|
| 起動直後 (idle) | 112 MiB / CPU 0.02% | 1.20 GiB / CPU 27% |
| テスト実行中 | 199 MiB / CPU 35% | 1.22 GiB / CPU 5% (待機) |
Omni は idle でも 1.2 GB ほどメモリを使い、 CPU も恒常的に少し回しています。 これは Paxos の合意プロセスや SPANNER_SYS の統計収集を裏で動かしているためです。 自分の Mac M シリーズ 16 GB 機なら気にならない範囲でしたが、 8 GB 機だと厳しいかもしれません。 また CI で使う場合 (GitHub Actions の runs-on: ubuntu-latest は 16 GB) も問題なさそうですが、 メモリ制限のあるセルフホストランナーでは注意が必要です。
Emulator と Omni の決定的な差は観測可能性 (EXPLAIN ANALYZE / SPANNER_SYS)
自分が個人的に 「これがあるから Omni に乗り換える価値がある」 と思った最大のポイントがこれです。 性能の数字 (3.5 倍速い等) はマシン依存で変動しますが、 観測可能性の差は 機能の有無の問題 なので、 環境によらず再現します。
information_schema のテーブル数で比較
両方に同じ DDL (上記の 68 テーブル) を投入し、 information_schema.tables をスキーマ別に集計してみます。
SELECT table_schema, COUNT(*) FROM information_schema.tables GROUP BY table_schema;
| schema | Emulator | Omni |
|---|---|---|
ユーザー (app_admin) |
9 | 9 |
ユーザー (app_main) |
59 | 59 |
INFORMATION_SCHEMA |
28 | 47 |
SPANNER_SYS |
1 | 50 |
ユーザーが定義したテーブルは完全に揃います。 ですが、 SPANNER_SYS が Emulator では実質空 (1 テーブル) なのに対し、 Omni は 50 テーブル あります。
Omni でだけ取れる代表的な統計テーブル
QUERY_STATS_TOP_{MINUTE,10MINUTE,HOUR} スロークエリ TOP N
LOCK_STATS_TOP_{MINUTE,10MINUTE,HOUR} ロック競合 TOP N
QUERY_PROFILES_TOP_{MINUTE,10MINUTE,HOUR} プラン別の集計
QUERY_RECOMMENDATIONS オプティマイザの推奨
ACTIVE_QUERIES_SUMMARY 実行中クエリ
OLDEST_ACTIVE_QUERIES 長時間トランザクション検出
つまり、 本番でしか観測できなかった統計情報が手元で直接 SQL で叩けるということです。 スロークエリやロック競合の原因分析を開発機で再現できる のは、 Emulator では現状提供されていない領域であり、 Omni 最大の価値だと自分は感じています。
EXPLAIN ANALYZE の差
同じクエリを両方で実行して比較してみます。
SELECT * FROM app_admin.organizations WHERE is_active = TRUE LIMIT 10;
Emulator (--query-mode=PROFILE):
TOTAL_ELAPSED_TIME: 10.89 ms
CPU_TIME: Unknown
ROWS_SCANNED: Unknown
OPTIMIZER_VERSION: Unknown
None No query plan
No query plan、 CPU_TIME: Unknown、 ROWS_SCANNED: Unknown 。 Emulator は API 互換のための開発用ツールなので、 性能チューニングや実行計画の確認には現状情報が不足します。
Omni (EXPLAIN ANALYZE):
+----+----------------------------------------------------------------+---------------+
| ID | Query_Execution_Plan | Total_Latency |
+----+----------------------------------------------------------------+---------------+
| 0 | Global Limit (execution_method: Row) | 0.04 msecs |
| 1 | +- Distributed Union (distribution_table: organizations, ...) | 0.03 msecs |
| 2 | +- Serialize Result | 0.02 msecs |
| 3 | +- Local Limit | 0.01 msecs |
| 4 | +- Local Distributed Union | 0.01 msecs |
| *5 | +- Filter Scan (seekable_key_size: 0) | |
| 6 | +- Table Scan (Full scan: true) | 0.01 msecs |
+----+----------------------------------------------------------------+---------------+
Predicates:
5: Residual Condition: ($is_active = true)
cpu time: 2.7 msecs / optimizer version: 8
Distributed Union、 Full scan: true、 Residual Condition、 optimizer version まで本物の Spanner と同じプランが返ってきます。
これがあると、 コードレビューで 「このクエリ、 フルスキャンになってない?」 「インデックスが効いてる?」 を開発者が自分の手元で確認できます 。 本番にデプロイしてから Cloud Monitoring を見て初めて気づく、 というサイクルが短くなります。
参考:
プレビュー版の制約とどう付き合うか
良いことばかり書いてきましたが、 Omni はまだプレビュー段階で、 無視できない制約があります。
| 制約 | 内容 | 緩和策 |
|---|---|---|
| 90 日制約 | デプロイ作成から 90 日で書き込み停止 | Docker volume を捨てて再デプロイ。 開発機なら影響少 |
| TLS 非対応 | プレビュー版は平文通信のみ | クローズドネットワーク限定で運用。 CI と開発機なら問題ない |
| バックアップ未対応 | dump/restore できない | 開発機の DB は捨てる前提で運用する |
| エンタープライズセキュリティ未対応 | 認証・認可機能なし | 信頼境界の中で運用する |
| 本番代替不可 | プレビュー利用規約 (Pre-GA Terms) は開発・テスト・PoC のみ許可 | 本番は Cloud Spanner を使う |
特に 90 日制約 は注意しています。 自分の検証ではデプロイ日 (2026-05-17) から 90 日後 (2026-08-15) に書き込み停止になります。 長期検証する場合はデプロイ日を deployment-date.txt のようなファイルに記録しておき、 近づいたら volume を作り直す運用にした方がよいと思います。
参考:
結論
自分の検証結果を整理するとこうなります。
- ローカル開発で使う Spanner を、 Emulator から Omni に置き換える価値は十分にある
- 既存コードはほぼ無修正 (1 行 + env) 、 DDL 互換性は完全、 テストは全 PASS
- 自分の検証条件下では実行時間は Emulator の約 3 分の 1 (※ マシンとワークロード次第で変動)
-
SPANNER_SYSとEXPLAIN ANALYZEでローカル性能チューニングが現実的になる (これは環境によらず再現する Omni の本質的な強み) - 唯一の課題はメモリ消費 (1 GB+) とプレビューの 90 日制約
自分個人としては、 CI 環境は Emulator のまま (軽量)、 開発者の手元の Spanner だけ Omni に切り替える のがバランス良いと感じています。 CI で 1 GB 余分にメモリ使うのは GitHub Actions のランナーでは無視できる範囲ですが、 開発者が複数プロジェクトを並行して動かす Mac だと地味に効いてくるためです。
最後に
Spanner Omni はまだプレビューですが、 Cloud Spanner ユーザーにとっては 「ローカルでの開発体験」 が一段階上がる存在になりそうです。 今まで Emulator を使っていたところ (ローカル開発機・CI) は、 最終的に Spanner Omni に置き換わっていく と自分は考えています。 本物の Spanner ソフトウェアなのでオプティマイザ・統計・マルチプレックスセッション等の挙動が本番と揃いますし、 GA で TLS 対応と 90 日制約解除が来れば慎重に判断する理由もほぼなくなるはずです。
現時点では Omni は Emulator に比べてメモリ消費 (1.2 GiB / Emulator は 100 MiB 程度) や起動時間がやや重いので、 CI への即時導入は環境次第で判断するのが無難です。 GitHub Actions の ubuntu-latest (16 GB) のように余裕のあるランナーなら CI も Omni に置き換えられますし、 メモリの厳しいセルフホストランナーだとしばらく Emulator のままにする選択肢もあります。 自分は今回の検証で 「ローカル開発機は Omni に乗り換える」 と意思決定し、 CI は GA 後にまとめて移行する方針です。
その先で考えることも増えます。 インスタンス管理や DDL マイグレーションを複数環境で揃えるツールはどうするか。 スキーマ変更時の互換性はどう担保するか。
このあたりは答えがある問題ではなく、 各チームが自分たちの運用に合わせて作るしかない領域だと思っています。 自分のチームでも答えはまだありません。 あなたのチームではどう設計するでしょうか? まずは Mac で docker pull するところから始めてみてはいかがでしょうか。
手元で試すときの手順チェックリストとして、 この記事をストックしておくと後から見返しやすいと思います。