こんにちは、バックエンドエンジニアのごましおです。
Google Cloudは2025年4月28日、Spannerデータベースの新機能「事前分割(Pre-splitting)」の一般提供を開始しました。
事前分割の概要 | Spanner | Google Cloud
この機能は、大規模かつ予測可能なトラフィック増加に備えて、データベースの分割点(split points)を事前に設定することで、パフォーマンスの安定性を確保するためのものです。
今回は、この事前分割機能について検証した結果をご紹介します。
コロプラとSpanner分割の歩み
コロプラでは2018年からSpannerを利用しており、特に新しいゲームのリリースタイミングに合わせて実施しているウォームアップに関しては、このテックブログや勉強会でも発信しております。
- Spannerウォームアップについて/ColoplTech-02-03 | Speaker Deck
- 大規模モバイルゲームのローンチを支える技術 を実施しました! | COLOPL Tech Blog
そんな我々にとって待望の事前分割機能がついに公式でGAになったということで、早速検証してみました。
Spannerにおける分割(splitting)とは
Spannerは通常、トラフィック変化に応じて自動的にデータを分割(スプリット)または統合(マージ)することで、インスタンス内のリソース全体に負荷を分散します。しかし、製品リリースなどの大規模イベントに備える場合、この自動分割だけでは十分なパフォーマンスを確保できないケースがあります。
事前分割機能の登場以前の手動分割
事前分割機能が登場する以前は、大規模なトラフィック増加に備えるために、手動でSpannerに負荷をかけて分割を促す必要がありました。この手法では、cloudspannerecosystem/gcsb などのツールを使用して、人工的な負荷をかけることでデータの分割を促していました。
しかし、この手動での分割方法には以下のような課題がありました。
- 時間とコストの問題: 十分な分割を促すためには長時間の負荷試験が必要で、その間のリソース消費によるコストが高くなる
- 分割数の制御が困難: 負荷をかけても、狙った数の分割数にすることが難しく、結果の予測性が低い
- 運用の複雑さ: 負荷テストの設定や監視、調整などの運用負担が大きく、エンジニアリングリソースを多く消費してしまう
事前分割機能を適切に活用することで、これらの課題を解決できます。
用語
まず、公式ドキュメントで使用されている用語を整理しておきましょう。
| 英語 | 日本語 | 説明 |
|---|---|---|
| splits | スプリット | 分割された領域のこと |
| split points | 分割点 | データを分割する境界となるポイント |
| splitting | 分割 | データを分割する処理 |
| Pre-splitting | 事前分割 | トラフィック増加の前にデータを分割すること |
| merging | 統合 | 分割されたデータを統合する処理 |
| rebalancing | 再調整 | データの分布を調整する処理 |
例: 分割点(split points)を10個作ると、11個のスプリット(splits)が作成されます。
運用上の注意点
事前分割機能を利用するうえで、いくつかの重要な注意点があります。
タイミング
分割ポイントは、予想されるトラフィック増加の 7 日前から 12 時間前までに作成する必要があります。
という記載があります。 これに伴い、負荷試験の実施方法やリリース時の準備も従来の手法とは変わってきます。
負荷試験
分割点を作って同日中に試験するのは難しいため、試験前日までに分割点を作成しておくなどの工夫が必要になります。
リリース準備
従来の手法では、スプリット作成のためのウォームアップは製品リリースの2日前に実行することとなっていました。これはGoogle Cloud公式ブログなどでも言及されています。
Cloud Spanner のウォームアップ ツールとベンチマーク ツールでアプリケーションのリリースを簡単に | Google Cloud Japan ブログ
これはSpannerのスプリットは負荷がかかっていない状態が続くと内部的に統合(merging)されてしまうため、日を空けすぎるとせっかく作成したスプリットが意味を失ってしまうという事情があったものと考えられます。従来の手法ではリリース日によっては休日作業が必要になってしまっていましたが、事前分割機能を活用することで余裕を持ったリリーススケジュールを設定できるようになります。
パフォーマンスへの一時的な影響
分割は即時には行われません。分割と再調整がトラフィックに対応できない場合、使用可能なコンピューティング リソースとメモリリソースがスプリットによって使い果たされる可能性があります。この場合、Spanner の作業スケジューラがキューに入れるリクエストがさらに増え、レイテンシが増加し、タイムアウトやトランザクションの中止につながる可能性があります。
という記載があります。分割点は作成してすぐに効果のある機能ではなく、作成後にSpanner内部で行われる再調整に時間とリソースが使われる機能です。すでにアクセスが来ている状態で、負荷を軽減するために応急処置的に使える機能ではないということに注意する必要があります。
実際にアクセスが来ているテーブルやインデックスに対してオンラインで分割点を追加すると、スロークエリが発生するなど一時的な性能劣化につながることが確認できました。対象のテーブルやインデックス、およびそのインターリーブ配下のテーブルが影響を受ける可能性があります。
アクセスが来ていないテーブルやインデックスに対する分割点の作成がデータベース全体のパフォーマンスに与える影響については、現時点で結論が出ていないため、本稿では記載を控えさせていただきます。
リソースの管理
テーブル、インデックス、データベースを削除する前に、対応する追加されたすべてのスプリット ポイントが期限切れになっていることを確認する必要があります。これを行うには、分割の有効期限を現在の時刻に設定します。これは、インスタンス レベルの割り当てを再利用するために必要です。
という記載があります。負荷試験中はテーブルを再作成することもよくありますが、その際に分割点を忘れずに削除(有効期限を過去に更新)するようなオペレーションにしなければなりません。
実装方法
ここでは公式の手順を参考にしつつ、gcloudコマンドを使用した分割点の作成手順をご紹介します。
分割ポイントを作成、管理する | Spanner | Google Cloud
分割点の作成手順
1. 負荷試験によるスプリット数の見積もり
まず、事前分割なしで想定負荷の試験を数時間程度を目安に実施します。この時点ではスプリットが存在しないため、スロークエリの発生などパフォーマンスが安定しないですが気にしません。CPU使用率が上がりすぎないようにノード(Processing Units)数は調整しましょう。
以下の User テーブルと INDEX を例とします。userId, token はそれぞれ UUID v4 の値が入るとします。
CREATE TABLE User ( userId STRING(36) NOT NULL PRIMARY KEY, token STRING(36), ); CREATE UNIQUE INDEX User_token_unique ON User(token);
試験実施後、対象テーブルのフルスキャンクエリを発行して実行計画から実行数(Number of executions)を確認します。
SELECT COUNT(*) FROM User@{FORCE_INDEX=_BASE_TABLE}; SELECT COUNT(*) FROM User@{FORCE_INDEX=User_token_unique};

実行計画は Google Cloud Console や gcloud コマンドでも確認できますが、より詳細な情報を確認するには spanner-cli が便利です。これは Google Cloud 公式ではなく、コミュニティによって開発されているオープンソースのコマンドラインツールです。2025/5/21 時点では「Do not use this tool for production databases as the tool is still alpha quality.」という記載があるため、本番環境での使用には注意が必要です。
spanner-cli で確認する場合は EXPLAIN ANALYZE を使用します。なお、spanner-cliでは「Number of executions」ではなく単に「Executions」というカラム名で表示されます。
spanner> EXPLAIN ANALYZE SELECT COUNT(*) FROM User@{FORCE_INDEX=_BASE_TABLE};
+----+---------------------------------------------------------------------------------------------------------+---------------+------------+---------------+
| ID | Query_Execution_Plan | Rows_Returned | Executions | Total_Latency |
+----+---------------------------------------------------------------------------------------------------------+---------------+------------+---------------+
| 0 | Serialize Result (execution_method: Row) | 1 | 1 | 76.39 msecs |
| 1 | +- Global Stream Aggregate (execution_method: Row, scalar_aggregate: true) | 1 | 1 | 76.39 msecs |
| 2 | +- Distributed Union (distribution_table: User, execution_method: Row, split_ranges_aligned: false) | 0 | 1 | 76.38 msecs |
| 3 | +- Local Stream Aggregate (execution_method: Row, scalar_aggregate: true) | 0 | 31 | 0.81 msecs |
| 4 | +- Local Distributed Union (execution_method: Row) | 0 | 31 | 0.76 msecs |
| 5 | +- Table Scan (Full scan: true, Table: User, execution_method: Row, scan_method: Automatic) | 0 | 31 | 0.51 msecs |
+----+---------------------------------------------------------------------------------------------------------+---------------+------------+---------------+
この実行数がスプリットと深い相関関係にあるそうです。以降この実行数=スプリット数のターゲットとしていきます。
2. 対象テーブルの再作成
事前分割を作成する前に、負荷試験で使ったテーブルやINDEXは再作成(DROP + CREATE)しておきます。
3. インスタンスのノード数を調整する
1ノードにつき分割点を10作るのが推奨されています。例えば、25スプリットが必要な場合は、最低3ノードのインスタンスが必要になります。
4. 分割点を記載したファイルを用意する
今回はUserテーブルとtokenインデックスを31分割するため、それぞれ30個の分割点を作成します。分割点を作成するためのファイルは以下のようになります。
user_splits.txt
TABLE User ('08000000-0000-0000-0000-000000000000')
TABLE User ('11000000-0000-0000-0000-000000000000')
...
TABLE User ('F8000000-0000-0000-0000-000000000000')
token_splits.txt
INDEX User_token_unique ('08000000-0000-0000-0000-000000000000')
INDEX User_token_unique ('11000000-0000-0000-0000-000000000000')
...
INDEX User_token_unique ('F8000000-0000-0000-0000-000000000000')
今回はUUIDのカラムを対象にしているため、UUIDの範囲を31等分するような値を用意します。実際のカラム型とデータ範囲に合わせて用意してください。
5. gcloudコマンドで分割点を作成する
gcloud spanner databases splits add your-database \ --instance=your-instance \ --splits-file=user_splits.txt \ --project=your-project-id gcloud spanner databases splits add your-database \ --instance=your-instance \ --splits-file=token_splits.txt \ --project=your-project-id
分割点作成に関する上限として、1ノードにつき1分間に10個の分割点までと制限されています。例えば、3ノードに対してUserテーブル用の30個とtokenインデックス用の30個を一度に作成できません。ファイルを2つ用意して個別に実行する必要があります。また、Userテーブル用の30個を作ったら1分間隔を空けてtokenインデックス用の30個を作成する必要があります。
# 1ノードにつき1分間に10個の分割点作成の上限に達した場合 $ gcloud spanner databases splits add your-database \ --instance=your-instance \ --splits-file=user_splits.txt \ --project=your-project-id ERROR: (gcloud.spanner.databases.splits.add) RESOURCE_EXHAUSTED: Total user split point count processed per minute limit is 30.Total number of splits processed in the last minute was 30, the request contains 30 excessive split points.
6. 分割点を確認する
以下のクエリを実行して作成した分割点を確認できます。
SELECT * FROM SPANNER_SYS.USER_SPLIT_POINTS
7. 実行計画で分割点が使われることを確認する
分割数を見積もるときと同様のフルスキャンクエリを発行して、実行計画から分割点が使われていることを確認します。実行数(Number of executions)が作成した分割数になっていればOKです。
SELECT COUNT(*) FROM User@{FORCE_INDEX=_BASE_TABLE}; SELECT COUNT(*) FROM User@{FORCE_INDEX=User_token_unique};
分割点の削除
分割点を削除するためのAPIは提供されていないため、有効期限を過去に設定するという方法で削除します。
gcloud spanner databases splits add your-database \ --instance=your-instance \ --splits-file=user_splits.txt \ --split-expiration-date=2023-01-01T00:00:00Z \ --project=your-project-id gcloud spanner databases splits add your-database \ --instance=your-instance \ --splits-file=token_splits.txt \ --split-expiration-date=2023-01-01T00:00:00Z \ --project=your-project-id
通常の有効期限設定も同じコマンドで行えます。指定しなかった場合のデフォルトは10日間で、最大で30日後まで指定可能です。
検証結果
ちょうど負荷試験を行っていたプロジェクトがあったため、従来の手法でウォームアップした状態と事前分割機能を用いてウォームアップした状態それぞれで負荷試験を実施してみました。試行回数は多くないですが、従来の手法と比較して事前分割機能を用いたウォームアップでも同等の安定性が得られました。
その他の確認ポイント
1. 分割点作成にかかる時間
分割点の作成自体は数秒で完了し、作成後すぐにスプリットが使われる実行計画になることが確認できました。公式ドキュメントでは作成後に最低12時間空けるよう推奨されており、この間にデータの再配置などが裏側で行われているものと思われます。
2. 分割点が期限切れになったときの挙動
アクセスがある状態で分割点が期限切れになっても、即座にスプリットなしの状態にはなりませんでした。つまり、期限が切れていきなり性能が劣化するということはありません。ドキュメントによれば、管理がユーザーからSpannerに移るだけとのことで、従来のスプリットと同じく負荷に応じて自動的に分割・統合が行われていくと考えられます。
まとめ
Spannerの事前分割機能は、明示的に削除するAPIがないなどオペレーション面でやや不便な点もあるものの、基本的には我々が求めていた機能が提供されたと言える評価でした。負荷試験やリリース前の準備にかかるコストの大幅削減が期待できます。
今回はgcloudコマンドを使って分割点を作成しましたが、クライアントライブラリーでも提供されているため、機能実装することでより使いやすい形で利用できる可能性もあります。
大規模なトラフィック増加を見込むサービスのリリースなどを計画している場合、Spannerの事前分割機能を活用することで、より安定したパフォーマンスの確保が期待できます。
参考資料
- 事前分割の概要 | Spanner | Google Cloud - 公式ドキュメント
- 分割ポイントを作成、管理する | Spanner | Google Cloud - 公式ドキュメント
- cloudspannerecosystem/gcsb - Spannerの負荷テストツール
- Cloud Spanner のウォームアップ ツールとベンチマーク ツールでアプリケーションのリリースを簡単に - 事前分割機能が登場する以前のウォームアップに関する情報
ColoplTechについて
コロプラでは、勉強会やブログを通じてエンジニアの方々に役立つ技術や取り組みを幅広く発信していきます。
connpass および X で情報発信していますので、是非メンバー登録とフォローをよろしくお願いいたします。
また、コロプラではゲームや基盤開発のバックエンド・インフラエンジニアを積極採用中です!
興味を持っていただいた方はぜひお気軽にご連絡ください。