ゲーム データベースとして Cloud Spanner を使用する場合のベスト プラクティス

このドキュメントでは、ゲームの状態を保存するプライマリ バックエンド データベースとして Spanner を使用する場合のベスト プラクティスについて説明します。一般的なデータベースの代わりに Spanner を使用して、プレーヤーの認証データとインベントリ データを保存できます。このドキュメントは、長期的な状態の保存を扱うゲーム バックエンド エンジニアと、それらのシステムをサポートし、Google Cloud でバックエンド データベースをホストすることを考えているゲーム インフラストラクチャのオペレーターと管理者を対象としています。

マルチプレーヤー オンライン ゲームは進化を続け、プレーヤーのエンタイトルメント、状態、インベントリ データを追跡するため、より複雑なデータベース構造を必要としています。プレーヤー数も増加し、ゲームの複雑さも増しています。このため、スケーリングと管理がデータベース ソリューションの課題になっています。シャーディングクラスタリングが必要になることも少なくありません。一般に、価値のあるゲーム内アイテムやプレーヤーの進歩データを追跡するにはトランザクションが必要になりますが、分散データベースでこの課題を解決するのは容易ではありません。

Spanner は、グローバルに分散され、強整合性を備えたエンタープライズ クラスの最初のスケーラブルなデータベース サービスです。クラウド用に設計され、リレーショナル データベースの構造と非リレーショナル データベースの水平スケーラビリティを兼ね備えています。多くのゲーム会社が、本番環境のシステムでゲームの状態と認証の両方を管理するのに、このデータベースが適していることを認識しています。Cloud Console を使用してノードを追加することで、パフォーマンスやストレージを増やすことができます。Spanner は、強整合性によりグローバルなレプリケーションを透過的に処理できるため、リージョンのレプリカを管理する必要はありません。

このドキュメントでは、次の内容について説明します。

  • Spanner の重要なコンセプトと、ゲームでよく使用されるデータベースとの違い。
  • Spanner をゲームに使用する場合の条件。
  • Spanner をゲームで使用する場合に避けるべきパターン。
  • ゲームのデータベースとして Spanner を使用した場合のデータベース オペレーションの設計。
  • Spanner で最高のパフォーマンスを実現するためのデータのモデル化とスキーマの作成。

用語

エンタイトルメント
プレーヤーが資格を持つゲーム、拡張、アプリケーション内購入。
個人を特定できる情報(PII)
ゲームでは、一般にメールアドレスや支払情報(クレジット カード番号や請求先住所)を含む情報のことを意味します。市場によっては、国民識別番号が含まれる場合があります。
ゲーム データベース(ゲーム DB)
ゲームの進行状況とインベントリを格納するデータベース。
認証データベース(認証 DB)
プレーヤーのエンタイトルメントとプレーヤーが購入時に使用する PII を格納するデータベース。認証 DB は、アカウント DB またはプレーヤー DB ともいいます。このデータベースはゲーム DB と統合されている場合もありますが、複数のタイトルを持つスタジオやパブリッシャーでは分離されているのが一般的です。
トランザクション
データベース トランザクション - オールオアナッシングの一連の書き込み操作。トランザクションが成功した場合はすべての更新が有効になりますが、失敗した場合はトランザクションによる更新を一切含まない状態に戻ります。ゲームでは、支払いを処理する場合や、価値のあるゲーム内インベントリまたは通貨の所有権を割り当てる場合に、データベース トランザクションが重要になります。
リレーショナル データベース管理システム(RDBMS)
相互に参照するテーブルと行から構成されるデータベース システム。ゲームで使用されるリレーショナル データベースとしては、SQL Server、MySQL、Oracle®(あまり一般的ではない)などがあります。よく知られた方法論とトランザクションの整合性を保証する強力な機能を使用できるため、これらのデータベースがよく利用されています。
NoSQL データベース(NoSQL DB)
関係で構造化されていないデータベース。これらのデータベースは、データモデルの変更に柔軟に対応できるため、ゲームで使用されるケースが増えています。NoSQL データベースには、MongoDB と Cassandra があります。
主キー
インベントリのアイテム、プレーヤー アカウント、購入トランザクションの一意の ID を含む列。
インスタンス
1 つのデータベース。たとえば、1 つのクラスタで同じデータベース ソフトウェアの複数のコピーが実行されていても、ゲームのバックエンドから見ると、これらは 1 つのインスタンスとみなされます。
ノード
このドキュメントでは、データベース ソフトウェアのコピーを実行する 1 台のマシンを意味します。
レプリカ
データベースの 2 番目のコピー。データの復元と高可用性のため、または読み取りスループットの向上のため、レプリカが頻繁に使用されます。
クラスタ
複数のマシンで実行されるソフトウェアの複数のコピー。ゲームのバックエンドから見ると、これらのコピーは 1 つのインスタンスと見なされます。クラスタは、スケーラビリティと可用性のために使用されます。
シャード
データベースのインスタンス。多くのゲームスタジオは同じ種類のデータベース インスタンスを複数実行し、各インスタンスはゲームデータのサブセットを保持します。このようなインスタンスをシャードといいます。シャーディングは通常、パフォーマンスやスケーラビリティのために行われます。より複雑なアプリケーションを構築できますが、管理の効率性は低下します。Spanner のシャーディングは、スプリットを使用して実装されます。
スプリット
Spanner は、データをスプリットというチャンクに分割します。個々のスプリットは互いに独立して移動し、異なるサーバーに割り当てられます。スプリットは、トップレベルの(つまり、インターリーブされていない)テーブルの行からなる範囲として定義されます。これらの行は主キーによって順序付けられます。この範囲の開始キーと終了キーは「スプリットの境界」と呼ばれます。Spanner は、スプリットの境界を自動的に追加および削除するため、データベース内のスプリット数はそれに応じて変化します。Spanner は、負荷に基づいてデータを分割します。スプリット内の多くのキー間で分散されている、負荷の高い読み込みまたは書き込みが検出されると、自動的にスプリットの境界が追加されます。
ホットスポット
Spanner などの分散データベースで、データベースに送信されるすべてのクエリの大部分を受け取るレコードが 1 つのスプリットに集中している状態。パフォーマンスが低下するため、このシナリオは好ましいものではありません。

ゲームに Spanner を使用する

ゲームに RDBMS を採用する際は、ほとんどの場合、ゲーム DB や認証 DB、またはその両方を効果的に置き換えることができる Spanner が最適です。

ゲーム DB

Spanner は、世界規模でトランザクションの分散処理を行うため、ゲームのインベントリ システムに最適なデータベースです。大規模なゲーム バックエンドでは、ゲーム内通貨や、取引、販売、贈与が可能なアイテム、プレーヤー間で交換可能なアイテムをどのように扱うかが課題となります。ゲームの人気が急速に高まり、従来のシングルノード データベースでは対応できなくなることも少なくありません。ゲームの種類によっては、プレーヤーの負荷を処理するのに必要なオペレーションの数や格納するデータ量がデータベースの能力を超える可能性があります。このため、多くのゲームのデベロッパーは、データベースのシャーディングやテーブル数の追加を行い、パフォーマンスの向上を図っています。しかし、このような解決策では運用が複雑になり、メンテナンスのオーバーヘッドが高くなります。

この複雑さを解消するため、完全に別のゲーム リージョンを実行し、リージョン間でデータを移動させない方法がよく利用されています。この場合、各リージョンのインベントリは別々のデータベースに分割されるため、別のゲーム リージョンのアイテムと通貨を取引することはできません。この設定では、開発効率と操作の単純化が優先され、プレーヤーのエクスペリエンスが犠牲になります。

一方で、地理的に分割されたデータベースでクロス リージョンの取引を許可することもできますが、多くの場合、複雑原価が増大します。この設定では、トランザクションを複数のデータベース インスタンスに分散する必要があるため、アプリケーション側のロジックが複雑になり、エラーが発生しやすくなります。複数のデータベースでトランザクション ロックを取得しようとすると、パフォーマンスが大幅に低下する可能性もあります。さらに、アトミック トランザクションに依存できないため、プレーヤーがゲーム内通貨やアイテムの複製を不正に行い、ゲーム エコシステムやコミュニティに被害を及ぼす可能性があります。

Spanner を使用すると、インベントリと通貨のトランザクションを簡単に行うことができます。Spanner に世界規模のゲームデータをすべて格納している場合でも、従来の ACID(アトミック性、整合性、独立性、永続性)特性を超える強固な強整合性で読み書きトランザクションを実現します。Spanner のスケーラビリティにより、より高いパフォーマンスやストレージが必要な場合でも、ノードを追加するだけで対応できます。データを個別のデータベース インスタンスにシャーディングする必要はありません。多くのゲームでは、高可用性とデータの復元性を実現するためにデータベースをクラスタ化していますが、これらの処理は Spanner によって透過的に処理されるため、追加の設定や管理作業を行う必要はありません。

認証 DB

認証 DB にも Spanner を使用できます。特に、スタジオやパブリッシャー レベルの 1 つの RDBMS で標準化を行う場合に便利です。ゲーム用の認証 DB に Spanner の規模は必要ない場合もありますが、トランザクションの整合性やデータの高可用性は魅力的です。Spanner のデータ レプリケーションを使用すると、レプリケーションを透過的かつ同期的に行うことができます。Spanner には、99.99% の可用性と 99.999% の可用性を提供する構成があり、後者の場合、非稼働率は 1 年で 5 分 30 秒未満になります。プレーヤー セッションの開始時に毎回必要になる認証を処理するには、このタイプの可用性が最適です。

ベスト プラクティス

このセクションでは、ゲームデザインで Spanner を使用する場合の推奨事項について説明します。Spanner の独自の機能をフルに活用するには、ゲームデータのモデル化が重要な作業となります。Spanner にはリレーショナル データベースのセマンティクスでアクセスできますが、パフォーマンスを向上させるには、スキーマの設計がポイントになります。スキーマ設計の推奨事項については、Spanner のドキュメントに詳しく記載されています。以下では、ゲーム DB のベスト プラクティスについて説明します。

このドキュメントで説明するプラクティスは、お客様の導入事例による体験に基づいています。

プレーヤーとキャラクターの ID として UUID を使用する

通常、プレーヤー テーブルではプレーヤーごとに 1 行が使用され、そこにゲーム内通貨、進行状況などのデータが含まれているため、インベントリ テーブルの個別の行に簡単にマッピングできない状態になっています。終了することのない大規模なマルチプレーヤー ゲームのように、複数のキャラクターの進行状況を個別に保存できるゲームの場合、通常、このテーブルにはプレーヤーごとではなく、キャラクターごとに 1 行が使用されます。それ以外のパターンは同じです。

キャラクター テーブルの主キーには、グローバルで一意の文字またはプレーヤー ID(キャラクター ID)を使用することをおすすめします。また、DB ノード間でプレーヤー データを分散して Spanner のパフォーマンスを向上させるため、Universally Unique Identifier(UUID)v4 の使用もおすすめします。

インベントリ テーブルにインターリーブを使用する

多くの場合、インベントリ テーブルにはキャラクターの装備品、カード、ユニットなどのゲーム内アイテムが格納されています。通常は、1 人のプレーヤーが自分のインベントリにアイテムを多数所有しています。テーブル内では、1 つの行がそれぞれのアイテムを表します。

他のリレーショナル データベースと同様に、Spanner のインベントリ テーブルでは、次のようにアイテムの主キーとしてグローバルに一意な ID が使用されています。

itemID type playerID
7c14887e-8d45 1 6f1ede3b-25e2
8ca83609-bb93 40 6f1ede3b-25e2
33fedada-3400 1 5fa0aa7d-16da
e4714487-075e 23 5fa0aa7d-16da
d4fbfb92-a8bd 14 5fa0aa7d-16da
31b7067b-42ec 3 26a38c2c-123a

このサンプル テーブルでは、データを読みやすくするため、itemIDplayerID の文字列は途中で切り捨てられています。実際のインベントリ テーブルには、この例に含まれていない別の列も含まれます。

RDBMS でアイテムの所有権を追跡する場合、一般的な方法では、現在のオーナーのプレーヤー ID を保持する外部キーとして列を使用します。この列は、個々のデータベース テーブルの主キーになります。Spanner では、インターリーブを使用して関連するプレーヤー テーブルの行の近くにあるインベントリを格納し、パフォーマンスの向上を図ることができます。インターリーブ テーブルを使用する場合は、次の点に注意してください。

  • プレーヤー行のデータと、そのすべての子孫のインベントリ行のデータは合わせて 4 GiB 未満にする必要があります。この制限は、適切なデータモデル設計の問題によるものではありません。
  • オーナーなしでオブジェクトは生成できません。制限が事前にわかっていれば、オーナーなしのオブジェクトをゲーム設計で避けることができます。

ホットスポットを回避するためのインデックスの設計

多くのゲーム デベロッパーは、特定のクエリを最適化するため、複数のインベントリ フィールドにインデックスを実装しています。Spanner では、そのインデックスのデータを使って行を作成または更新すると、インデックス付きの列数に比例して追加の書き込み負荷が発生します。Spanner のパフォーマンスを向上させるには、使用頻度の低いインデックスを削除するか、データベースのパフォーマンスに影響を与えない方法でインデックスを実装します。

次の例では、プレーヤーのハイスコアを長期間格納するテーブルを作成しています。

CREATE TABLE Ranking (
        PlayerID STRING(36) NOT NULL,
        GameMode INT64 NOT NULL,
        Score INT64 NOT NULL
) PRIMARY KEY (PlayerID, GameMode)

このテーブルには、プレーヤー ID(UUIDv4)、ゲームモード、ステージまたはシーズンを表す番号、プレーヤーのスコアが格納されます。

ゲームモードをフィルタリングするクエリを高速化する場合は、次のようなインデックスを作成します。

CREATE INDEX idx_score_ranking ON Ranking (
        GameMode,
        Score DESC
)

全員が 1 という同じゲームモードでプレイしている場合、GameMode=1 のときにホットスポットが発生します。このゲームモードのランキングを取得する場合、インデックスで GameMode=1 を含む行のみがスキャンされるので、ランキングをすばやく取得できます。

前のインデックスの順序を変更すると、このホットスポットの問題を解決できます。

CREATE INDEX idx_score_ranking ON Ranking (
        Score DESC,
        GameMode
)

このインデックスでは、スコアが可能な範囲で分散されるため、同じゲームモードのプレーヤーによるホットスポットが発生しません。ただし、GameMode=1 かどうか判断するため、クエリですべてのモードのスコアがスキャンされるため、前のインデックスよりも速くスコアが返されることはありません。

インデックスの並べ替えで、ゲームモードのホットスポットは解決されましたが、まだ改善の余地があります。次の設計を見てください。

CREATE TABLE GameMode1Ranking (
        PlayerID STRING(36) NOT NULL,
        Score INT64 NOT NULL
) PRIMARY KEY (PlayerID)

CREATE INDEX idx_score_ranking ON Ranking (
        Score DESC
)

ゲームモードをテーブル スキーマから移動し、可能であれば、モードごとに 1 つのテーブルを使用することをおすすめします。これにより、モードのスコアを取得するときに、そのモードのスコアを含むテーブルにのみクエリを実行できます。このテーブルにスコアでインデックスを作成することで、ホットスポットの発生を回避してスコア範囲をすばやく検索できます(スコアが十分に分散している場合)。このドキュメントの執筆時点で、Spanner のデータベースあたりの最大テーブル数は 2,560 で、この値はほとんどのゲームで十分な数です。

テナントごとにデータベースを分ける

他のワークロードでは、異なる主キー値を使用して Spanner でマルチテナンシーを設計することをおすすめしていますが、ゲームデータの場合は、テナントごとに個別のデータベースを使用する従来のアプローチをおすすめします。ライブサービス ゲームで新しいゲーム機能をリリースする場合、スキーマの変更がよく行われていますが、データベース レベルでテナントを分離しておくことで、スキーマの更新を簡単に行うことができます。また、テナントデータのバックアップや復元は、データベース全体が対象になるため、このような処理にかかる時間も最適化できます。

スキーマの差分更新を回避する

従来のリレーショナル データベースとは異なり、Spanner はスキーマの更新中も動作します。古いスキーマに対するクエリはすべて返され(通常よりも早く返される可能性はありますが)、新しいスキーマに対するクエリは使用可能になったときに返されます。前述の制約を考慮することで、Spanner でスキーマが更新されている間も、ゲームの実行を継続するように更新プロセスを設計できます。

ただし、スキーマの処理中に別のスキーマの変更をリクエストすると、新しい更新はキューに追加され、スキーマの更新がすべて完了するまで処理されません。短時間で多くの増分更新を実行する代わりに、より大規模なスキーマ更新を計画することで、この状況を回避できます。データ検証が必要なスキーマ更新の実行方法など、スキーマ更新の詳細については、Spanner スキーマ更新ドキュメントをご覧ください。

データベースのアクセスとサイズを考慮する

Spanner を使用するゲームサーバーとプラットフォーム サービスを開発する場合、ゲームがデータベースにアクセスする方法を検討します。また、不要なコストを回避するため、データベースのサイズも検討する必要があります。

ネイティブのドライバとライブラリを使用する

Spanner を使用する場合は、コードとデータベースのインターフェースを検討する必要があります。Spanner は、多くの一般的な言語に対応したネイティブ クライアント ライブラリを提供しています。データ操作言語(DML)とデータ定義言語(DDL)のステートメントをサポートする JDBC ドライバも使用できます。新しい開発で Spanner を使用する場合は、Spanner 用の Cloud クライアント ライブラリを使用することをおすすめします。一般的なゲームエンジンの統合では言語を柔軟に選択することはできませんが、Spanner にアクセスするプラットフォーム サービスではゲーム会社が Java や Go を使用することがあります。高スループット アプリケーションの場合、同じ Spanner クライアントを複数の順次リクエストに使用できるライブラリを選択してください。

テスト環境と本番環境の要件に合わせてデータベースをサイズ調整する

開発時は、単一ノードの Spanner インスタンスで機能テストを含むほとんどのアクティビティを実行できます。

本番環境での Spanner の要件を評価する

開発からテスト、さらに本番環境に移行する際に、Spanner の要件を見直し、ゲームで実際のプレーヤーのトラフィックを処理できることを確認する必要があります。

また、本番環境に移行する前に負荷テストを行い、バックエンドが本番環境の負荷を処理できることを確認する必要があります。本番環境で期待される負荷の 2 倍に設定して負荷テストを実施し、ゲームのユーザー数が予想を超えた場合に備えておくことをおすすめします。

実データを使用して負荷テストを実行する

合成データで負荷テストを実行するだけでは不十分です。本番環境で想定されているものとできるだけ近いデータとアクセス パターンを使用して負荷テストを行う必要があります。合成データだけでは、Spanner のスキーマ設計の潜在的なホットスポットが見つからない可能性があります。Spanner が実データをどのように処理するかを確認するには、実際のプレーヤーにベータ版テストを行ってもらうのが最も効率的です。

以下に、ゲームスタジオのプレーヤー テーブル スキーマの一例を示します。この例は、ベータ版テストで負荷をテストする重要性をよく表しています。

負荷テストで使用するプレーヤー名と属性のリスト。

このスタジオは、2 年前の傾向に基づいてこのデータを用意しました。この会社は、この新しいゲームのデータも同じスキーマで表現できると想定しました。

各プレーヤー レコードには、ゲーム内でのプレーヤーの進捗状況を追跡する数値属性(ランク、プレイ時間など)が関連付けられています。前の表で使用されている属性の例では、新しいプレーヤーに開始値 50 が設定されています。この値は、プレーヤーがゲームを進めるにつれて 1~100 の値に変わります。

このスタジオでは、ゲームのプレイ中の重要なクエリを高速化するため、この属性にインデックスを付けたいと考えています。

このデータに基づいて、PlayerID を主キーとして使用し、Attribute をセカンダリ インデックスにする次の Spanner テーブルを作成しました。

CREATE TABLE Player (
        PlayerID STRING(36) NOT NULL,
        Attribute INT64 NOT NULL
) PRIMARY KEY (PlayerID)

CREATE INDEX idx_attribute ON Player(Attribute)

Attribute=23 のプレーヤーを最大 10 人まで検索するため、インデックスに次のようなクエリを実行しました。

SELECT PlayerID
        FROM Player@{force_index=idx_attribute}
        WHERE Attribute = 23
        LIMIT 10

スキーマ設計の最適化のドキュメントに記載されているように、Spanner はテーブルと同じ方法でインデックス データを保存し、インデックス エントリごとに 1 行を使用します。負荷テストでは、次の図に示すように複数の Spanner スプリット間でセカンダリ インデックスの読み取りと書き込み負荷を分散しています。

Spanner スプリット間で属性別に分散されたプレーヤー。

負荷テストで使用する合成データは、最終的なゲームの定常状態とよく似ていて、Attribute 値が適度に分散されています。ゲームデザインでは、すべてのプレーヤーが Attribute=50 からスタートしています。新しいプレーヤーはそれぞれ Attribute=50 で始まるため、新しいプレーヤーが参加すると、idx_attribute セカンダリ インデックスの同じ部分に挿入されます。更新が同じ Spanner スプリットにルーティングされるため、ゲームのリリース時にホットスポットが発生します。これでは、Spanner を効率的に使用できていません。

リリース時に多くのプレーヤーが同じ属性に存在するため、1 つの Spanner スプリットでホットスポットが発生。

次の図では、リリース後に IndexPartition 列をスキーマに追加し、ホットスポットの問題を解決しています。プレーヤーは利用可能な Spanner スプリットに均等に分散されています。テーブルとインデックスを作成するための更新されたコマンドは次のようになります。

CREATE TABLE Player (
        PlayerID STRING(36) NOT NULL,
        IndexPartition INT64 NOT NULL
        Attribute INT64 NOT NULL
) PRIMARY KEY (PlayerID)

CREATE INDEX idx_attribute ON Player(IndexPartition,Attribute)

スキーマに IndexPartition 列を追加することで、リリース時にプレーヤーを均等に分散。

クエリを効率的に実行するため、IndexPartition 値の範囲は制限する必要がありますが、効率的に分散させるには、少なくともスプリット数の 2 倍の範囲を設定する必要があります。

この場合、ゲーム アプリケーションですべてのプレーヤーに 1 から 6 までの IndexPartition を手動で割り当てています。

また、各プレーヤーに乱数を割り当てる方法や、PlayerID 値のハッシュから派生した値を割り当てる方法もあります。アプリケーション レベルでシャーディングを行う方法詳細については、DBA が Cloud Spanner について知っておくべきこと、パート 1: キーとインデックスをご覧ください。

改善後のインデックスを使用するように前のクエリを更新すると、次のようになります。

SELECT PlayerID
        FROM Player@{force_index=idx_attribute}
        WHERE IndexPartition BETWEEN 1 and 6
        AND Attribute = 23
        LIMIT 10

ベータ版テストを実施していないため、スタジオでは間違った前提でテストを行っていたことに気づきませんでした。インスタンスで処理できる 1 秒あたりのクエリ数(QPS)を検証するには合成データによる負荷テストが有効ですが、スキーマを検証してリリースを成功させるには、実際のプレーヤーによるベータ版テストを行う必要があります。

ピーク時の需要に合わせて本番環境のサイズを調整する

有名なゲームの多くは、リリース直後にトラフィックのピークに達します。プラットフォーム サービスや専用のゲームサーバーだけでなく、データベースにもスケーラブルなバックエンドを構築する必要があります。App Engine などの Google Cloud ソリューションを使用すると、大規模なフロントエンド API サービスをビルドできます。Spanner では、オンラインでノードの追加や削除を行うことができますが、自動スケーリングに対応したデータベースではありません。十分なノードをプロビジョニングして、トラフィックの急増に対応する必要があります。

負荷テストや公開ベータ版テストで収集したデータに基づいて、リリース時にリクエストの処理に必要になるノード数を見積もることができます。想定よりも多くのプレーヤーが参加した場合に備えて、いくつかのノードをバッファとして追加しておくことをおすすめします。データベースのサイズは、CPU の平均使用率が 65% を超えないように調整する必要があります。

リリース前にデータベースの準備を行う

リリース前の準備として、データベースのウォームアップを行う必要があります。

コールド状態の Spanner は、事前にウォームアップしておかないと、リリースに必要なノードが提供されません。

Spanner は分散型データベースで、データベースのサイズが大きくなると、データはスプリットというまとまりに分割されます。個々のスプリットはそれぞれ独立した存在で、別のサーバーに割り当てられます。また、物理的に別の場所に配置される場合があります。詳しくは、データベースのスプリットをご覧ください。

スプリットは行の範囲で定義されます。つまり、スプリットにはテーブルのサブセットが含まれます。Spanner は、負荷とサイズに基づいてデータを分割します。スプリットは、Spanner ノード間で動的に移動し、データベース全体の負荷を分散させます。Spanner に挿入されるデータが多いほど、より多くのスプリットが生成されます。

次の図には 4 つのノードがあります。

コールド状態の Spanner では 1 つのノードにデータが存在。

Spanner にデータがないため、データの書き込みが開始すると、1 つのノードにのみデータが書き込まれます。現在、Spanner はコールド状態です。

次の図では、スプリットが別のノードに移動しています。

Spanner がウォーム状態になると、データが分割され、ノード間で分散。

データがシステムに入力されると、Spanner はデータの分割を開始し、プロビジョニングされた 4 つのノード間で負荷を分散します。これで、Spanner がウォーム状態になりました。

Spanner がウォーム状態になり、スプリットがノード全体で均等に分散されたら、ゲームをリリースします。データベースをウォームアップするには、次の操作を行います。

  1. 負荷テストに使用するテーブルの主キーが、本番環境の実際のトラフィックに使用しているテーブルの主キーと同じであることを確認します。
  2. リリースの 2 日前に負荷テストを行います。少なくとも 1 時間は、想定されるピーク時の負荷でテストを行います。この負荷テストにより、Spanner は負荷に応じて分割を行い、複数のスプリットを生成します。
  3. 負荷テストが完了したら、負荷テストで生成された行をテーブルから削除します。ただし、テーブル自体は削除しないでください。これにより、リリース時に使用可能なスプリットが残ります。

パフォーマンスをモニタリングして状況を把握する

本番環境のデータベースには、総合的なモニタリングとパフォーマンスの指標が必要です。Spanner では、Cloud Monitoring の組み込みの指標を使用できます。可能であれば、付属の gRPC ライブラリをゲームのバックエンド プロセスを組み込むことをおすすめします。これらのライブラリには OpenCensus トレースが含まれています。OpenCensus トレースを使用すると、Cloud Trace やサポートされている他のオープンソース トレースツールにクエリトレースを表示できます。

Cloud Monitoring では、データ ストレージや CPU 使用率など、Spanner の使用状況の詳細を確認できます。この CPU 使用率または確認されたレイテンシに基づいて Spanner のスケーリングを決めることをおすすめします。最適なパフォーマンスを実現するために推奨される CPU 使用率については、ベスト プラクティスをご覧ください。

Spanner はクエリ実行プランを提供します。これらのプランは Cloud Console で確認できます。クエリのパフォーマンスを理解するためにサポートが必要な場合は、サポートに問い合わせることもできます。

Spanner は、バックグラウンドでデータを透過的に分割し、データのアクセス パターンに基づいてパフォーマンスを最適化します。このため、パフォーマンスを評価する際は、テストサイクルをできる限り短くする必要があります。持続的で現実的なクエリの負荷を使用して、パフォーマンスを評価してください。

データを削除する場合は、テーブルを再作成せずに行を削除する

Spanner でテーブルを新しく作成した状態では、負荷またはサイズに基づく分割はまだ行われません。データを削除するためにテーブルを削除してから再作成すると、Spanner はテーブルの正しい分割を判断するためにデータとクエリが必要になり、処理に時間がかかります。同じ種類のデータでテーブルを再作成する場合は(たとえば、パフォーマンス テストを連続して行う場合など)、不要になったデータを含む行に対して DELETE クエリを実行します。同様の理由で、スキーマを更新する場合は付属の Cloud Spanner API を使用してください。新しいテーブルを作成して別のテーブルやバックアップ ファイルからデータをコピーするなど、手動による操作は避ける必要があります。

コンプライアンス要件を満たすデータの局所性を選択する

多くのゲームは GDPR など、その地域のデータ保護法を遵守する必要があります。GDPR の要件に対応するため、Google Cloud と GDPR のホワイトペーパーを参照して、正しい Spanner リージョン構成を選択してください。

次のステップ