セッション数

このページでは、クライアント ライブラリの作成時、REST API または RPC API の使用時、Google クライアント ライブラリの使用時のベスト プラクティスなど、Spanner でのセッションの高度なコンセプトについて説明します。

セッションの概要

セッションとは、Spanner データベース サービスとの通信チャネルを表します。Spanner データベースのトランザクション(データの読み取り、書き込み、変更)は、セッションを使って実行されます。各セッションは 1 つのデータベースに適用されます。

セッションで同時に実行されるトランザクションは 1 つまたは複数です。複数のトランザクションを実行する場合、セッションは多重化されたセッションと呼ばれます。

スタンドアロンの読み取り、書き込み、クエリは内部で 1 つのトランザクションを使用します。

セッション プールのパフォーマンス上のメリット

セッションの作成には高額なコストがかかります。データベース オペレーションごとのコストの発生を避けるため、クライアントは使用可能なセッションのプールであるセッション プールを維持する必要があります。プールは既存のセッションを保存し、リクエストされたときに適切なタイプのセッションを返すとともに、未使用セッションのクリーンアップも処理する必要があります。セッション キャッシュを実装する方法の例については、Go クライアント ライブラリJava クライアント ライブラリなど、いずれかの Spanner クライアント ライブラリのソースコードをご覧ください。

セッションは長時間の使用を目的としているため、セッションをデータベース オペレーションに使用した後は、クライアントがセッションを再利用のためプールに戻す必要があります。

gRPC チャネルの概要

Spanner クライアントは gRPC チャネルを通信に使用します。1 つの gRPC チャネルは、TCP 接続とほぼ同等です。1 つの gRPC チャネルで同時に処理できるリクエストは最大 100 件です。つまり、アプリケーションが実行する同時リクエスト数を 100 で割った数以上の gRPC チャネルが必要になります。

Spanner クライアントは、作成時に gRPC チャネルのプールを作成します。

Google クライアント ライブラリを使用する際のベストプラクティス

ここでは、Spanner に Google クライアント ライブラリを使用する場合のベスト プラクティスについて説明します。

プール内のセッション数と gRPC チャネル数を構成する

クライアント ライブラリには、セッション プール内のデフォルトのセッション数と、チャネルプール内のデフォルトの gRPC チャネル数があります。どちらのデフォルトも、ほとんどのケースに適しています。各プログラミング言語におけるデフォルトの最小セッション数、最大セッション数、およびデフォルトの gRPC チャンネル数は以下のとおりです。

C++

MinSessions: 100
MaxSessions: 400
NumChannels: 4

C#

MinSessions: 100
MaxSessions: 400
NumChannels: 4

Go

MinSessions: 100
MaxSessions: 400
NumChannels: 4

Java

MinSessions: 100
MaxSessions: 400
NumChannels: 4

Node.js

Node.js クライアントは、複数の gRPC チャネルをサポートしていません。そのため、1 つのクライアントで 100 セッションを超えてセッション プールのサイズを大きくするのではなく、複数のクライアントを作成することをおすすめします。

MinSessions: 25
MaxSessions: 100

PHP

PHP クライアントは、構成可能な数の gRPC チャネルをサポートしていません。

MinSessions: 1
MaxSessions: 500

Python

Python では、セッションの管理に使用できる 4 種類のセッション プールをサポートしています。

Ruby

Ruby クライアントは、複数の gRPC チャネルをサポートしていません。そのため、1 つのクライアントで 100 セッションを超えてセッション プールのサイズを大きくするのではなく、複数のクライアントを作成することをおすすめします。

MinSessions: 10
MaxSessions: 100

アプリケーションで使用するセッション数は、アプリケーションが実行する同時実行トランザクションの数と同じです。デフォルト セッション プールの設定を変更するのは、1 つのアプリケーション インスタンスでデフォルト セッション プールが処理できるよりも多くの同時トランザクションを実行することが予想される場合のみです。

同時実行性の高いアプリケーションの場合は、次の設定をおすすめします。

  1. MinSessions を、1 つのクライアントが実行すると予想される同時トランザクション数に設定します。
  2. MaxSessions を、1 つのクライアントが実行できる同時トランザクションの最大数に設定します。
  3. アプリケーションの存続期間中に予想される同時実行数があまり変化しない場合は、MinSessions=MaxSessions に設定します。これにより、セッション プールのスケールアップやスケールダウンを防ぐことができます。セッション プールをスケールアップまたはスケールダウンすると、リソースの一部が消費されます。
  4. NumChannelsMaxSessions / 100 に設定します。1 つの gRPC チャネルで同時に処理できるリクエストは最大 100 件です。レイテンシの長いテール(p95/p99 レイテンシ)が観測された場合は、gRPC チャネルの輻輳を示している可能性があるため、この値を増やします。

アクティブなセッションの数を増やすと、Spanner データベース サービスとクライアント ライブラリで追加のリソースが使用されます。アプリケーションの実際のニーズを超えてセッション数を増やすと、システムのパフォーマンスが低下する可能性があります。

セッション プールを増加する / クライアントの数を増やす

アプリケーションのセッション プールサイズによって、単一のアプリケーション インスタンスが実行できる同時実行トランザクションの数が決まります。1 つのアプリケーション インスタンスが処理できる最大同時実行数を超えてセッション プール サイズを増やすことはおすすめしません。アプリケーションがプール内のセッション数を超えるリクエストのバーストを受信した場合、セッションが使用可能になるのを待機しつつリクエストはキューに入れられます。

クライアント ライブラリによって使用されるリソースは次のとおりです。

  1. 各 gRPC チャネルが 1 つの TCP 接続を使用する。
  2. 各 gRPC 呼び出しにはスレッドが必要。クライアント ライブラリで使用されるスレッドの最大数は、アプリケーションが実行する同時実行クエリの最大数と同じです。これらのスレッドは、アプリケーションが独自のビジネス ロジックに使用するスレッドの上に追加されます。

1 つのアプリケーション インスタンスが処理できるスレッドの最大数を超えてセッション プールのサイズを増やすことはおすすめしません。代わりに、アプリケーション インスタンスの数を増やします。

書き込みセッションの割合を管理する

Spanner は、ほとんどのクライアント ライブラリで読み取り / 書き込みトランザクションにセッションの一部を予約しています。この予約部分を書き込みセッションの割合といいます。アプリがすべての読み取りセッションを使い切ると、Spanner は読み取り専用トランザクションであっても読み取り / 書き込みセッションを使用します。読み取り / 書き込みセッションでは spanner.databases.beginOrRollbackReadWriteTransaction が必要です。ユーザーに spanner.databaseReader IAM ロールが割り当てられている場合、呼び出しは失敗し、Spanner は次のエラー メッセージを返します。

generic::permission_denied: Resource %resource% is missing IAM permission:
spanner.databases.beginOrRollbackReadWriteTransaction

クライアント ライブラリで書き込みセッションの割合を維持するには、書き込みセッションの割合を設定します。

C++

C++ セッションはすべて同じです。読み取りセッションまたは読み取り / 書き込み専用セッションはありません。

C#

C# のデフォルトの書き込みセッションの割合は 0.2 です。この割合は、SessionPoolOptions の WriteSessionsFraction フィールドを使用して変更できます。

Go

Go セッションはすべて同じです。読み取りセッションまたは読み取り / 書き込み専用セッションはありません。

Java

Java セッションはすべて同じです。読み取りセッションまたは読み取り / 書き込み専用セッションはありません。

Node.js

Node.js セッションはすべて同じです。読み取りセッションまたは読み取り / 書き込み専用セッションはありません。

PHP

PHP セッションはすべて同じです。読み取りセッションまたは読み取り / 書き込み専用セッションはありません。

Python

Python では、読み取りセッションと読み取り / 書き込みセッションの管理に使用できる 4 種類のセッション プールタイプをサポートしています。

Ruby

Ruby のデフォルトの書き込みセッションの割合は 0.3 です。この割合は、クライアントの初期化メソッドを使用して変更できます。

クライアント ライブラリを作成する場合、または REST / RPC を使用する場合のベスト プラクティス

ここでは、Spanner のクライアント ライブラリでセッションを実装する場合と、REST API または RPC API でセッションを使用する場合のベスト プラクティスについて説明します。

これらのベスト プラクティスは、クライアント ライブラリを開発している場合、または REST API または RPC API を使用している場合にのみ適用されます。Spanner 用の Google クライアント ライブラリのいずれかを使用する場合は、Google クライアント ライブラリを使用する際のベスト プラクティスをご覧ください。

セッション プールの作成とサイズ設定

クライアント プロセスに最適なセッション プールのサイズを特定するため、下限は予想される同時実行トランザクションの数に、上限は 100 などの初期テスト数に設定します上限が十分でない場合は、数を増やします。アクティブなセッション数を増やすと、Spanner データベース サービスで追加のリソースが使用されます。未使用セッションをクリーンアップしないと、パフォーマンスが低下します。RPC API を使用しているユーザーの場合は、gRPC チャネルごとのセッション数を 100 未満にすることをおすすめします。

削除されたセッションを処理する

セッションを削除するには、次の 3 つの方法があります

  • クライアントがセッションを削除する。
  • Spanner データベース サービスが、アイドル状態が 1 時間を超えたセッションを削除する。
  • セッションが 28 日を越えた場合、Spanner データベースサービスによってセッションが削除される場合がある。

削除されたセッションを使用しようとすると、NOT_FOUND になります。このエラーが発生した場合は、新しいセッションを作成して使用します。新しいセッションをキャッシュに追加して、削除済みのセッションをキャッシュから削除します。

アイドル状態のセッションを維持する

Spanner データベース サービスは未使用のセッションを削除できます。アイドル状態のセッションを維持する必要がある場合(データベースの使用が短期間で増加することが予想される場合など)、セッションが削除されないように設定できます。セッションを維持するには、SQL クエリ SELECT 1 など、低コストなオペレーションを実行します。近いうちに使用する予定がないアイドル状態のセッションは Spanner に削除させ、必要になったときに新しいセッションを作成します。

たとえば、データベースの負荷が定期的に高くなる場合にセッションを維持します。毎日午前 9 時から午後 6 時までデータベースの負荷が高くなる場合、この期間はアイドル状態のセッションを残し、ピーク時の対応に備えます。午後 6 時以降は、Spanner にアイドル状態のセッションを削除させます。毎日、午前 9 時前に、予想される需要に合わせて新しいセッションを作成します。

また、Spanner を使用するアプリケーションがあり、接続のオーバーヘッドを回避する必要がある場合にもセッションを維持します。セッションを維持しておくと、接続のオーバーヘッドを回避できます。

クライアント ライブラリのユーザーにセッションの詳細を隠す

クライアント ライブラリを作成する場合、クライアント ライブラリのユーザーにセッションの詳細を公開しないでください。クライアントがセッションの作成や維持などの複雑な処理を行うことなく、データベースを呼び出せるようにする必要があります。クライアント ライブラリのユーザーにセッションの詳細を隠すクライアント ライブラリについては、Java 用の Spanner クライアント ライブラリをご覧ください。

べき等でない書き込みトランザクションのエラーを処理する

リプレイ保護のない書き込みトランザクションが複数回ミューテーションに適用される場合があります。ミューテーションがべき等でない場合、ミューテーションが複数回適用されると、エラーが発生します。たとえば、書き込み前に行が存在していなくても、ALREADY_EXISTS エラーで挿入が失敗することがあります。バックエンド サーバーがミューテーションを commit していても、クライアントに接続できないと、この問題が発生します。この場合、ミューテーションが再試行され、ALREADY_EXISTS エラーが発生します。

独自のクライアント ライブラリの実装または REST API の使用時にこの問題を回避するには、次の方法が考えられます。

  • 書き込みをべき等にする。
  • リプレイ保護のある書き込みを使用する。
  • upsert ロジックを実行するメソッドを実装する。新規の場合に挿入を行い、既存の場合に更新を行う。
  • クライアントの代わりにエラーを処理する。

安定した接続を維持する

最高のパフォーマンスを得るには、セッションをホストするために使用する接続が安定している必要があります。セッションをホストする接続が変更されると、セッション メタデータの更新中に Spanner がセッションでアクティブなトランザクションを中止し、データベースに余分な負荷がかかる場合があります。いくつかの接続が散発的に変更されても問題ありませんが、多数の接続が同時に変更される状況は避ける必要があります。クライアントと Spanner 間でプロキシを使用する場合は、セッションごとに接続の安定性を維持する必要があります。

アクティブなセッションをモニタリングする

ListSessions コマンドを使用すると、REST API または RPC API を使用して、コマンドラインからデータベース内のアクティブなセッションをモニタリングできます。ListSessions を使うと、特定のデータベースのアクティブなセッションを確認できます。これは、セッション リークの原因を特定する必要がある場合に便利です(セッション リークは、セッションが作成はされるが再利用のためセッション プールに戻されないというインシデントです)。

ListSessions を使用すると、セッションが作成されたときやセッションが最後に使用されたときなど、アクティブなセッションに関するメタデータを表示できます。このデータを分析することで、セッションのトラブルシューティングを行う際に正しい方向へ向かうことができます。ほとんどのアクティブなセッションに最新の approximate_last_use_time がない場合は、アプリケーションでセッションが正しく再利用されていない可能性があります。approximate_last_use_time フィールドの詳細については、RPC API リファレンスをご覧ください。

ListSessions の使用方法の詳細については、REST API リファレンスRPC API リファレンスgcloud コマンドライン ツール リファレンスをご覧ください。

セッション リークの自動クリーンアップ

セッション プール内のすべてのセッションを使用すると、新しいトランザクションはセッションがプールに返されるまで待機します。作成されたセッションが再利用のためにセッション プールに戻されなかった場合、これはセッション リークと呼ばれます。セッションリークが発生すると、オープン セッションを待機しているトランザクションが無限に停止し、アプリケーションがブロックされます。セッションリークは、問題のあるトランザクションが非常に長い時間実行され、commit されていないことが原因で発生することがよくあります。

これらの非アクティブなトランザクションを自動的に解決するよう、セッション プールを設定できます。クライアント ライブラリで非アクティブな移行を自動的に解決できるようになると、セッション リークの原因となる可能性のあるトランザクションが特定され、セッション プールから削除されて、新しいセッションに置き換えられます。

ロギングによって、問題のあるトランザクションを特定することもできます。ロギングが有効になっている場合、セッション プールの 95% 以上が使用されていると、デフォルトで警告ログが共有されます。セッションの使用状況が 95% を超える場合は、セッション プールで許可される最大セッションを増やす必要があるか、セッション リークが発生している可能性があります。警告ログには、想定よりも長く実行されているトランザクションのスタック トレースが含まれており、セッション プールの使用率が高い原因を特定するのに役立ちます。警告ログは、ログ エクスポータの構成に応じてプッシュされます。

クライアント ライブラリで非アクティブなトランザクションを自動的に解決する

クライアント ライブラリで警告ログを送信して自動的に非アクティブなトランザクションを解決することも、クライアント ライブラリで警告ログの受信のみを有効にすることもできます。

Java

警告ログを受信して無効なトランザクションを削除するには、setWarnAndCloseIfInactiveTransactions を使用します。

 final SessionPoolOptions sessionPoolOptions = SessionPoolOptions.newBuilder().setWarnAndCloseIfInactiveTransactions().build()

 final Spanner spanner =
         SpannerOptions.newBuilder()
             .setSessionPoolOption(sessionPoolOptions)
             .build()
             .getService();
 final DatabaseClient client = spanner.getDatabaseClient(databaseId);

警告ログの受信のみを行うには、setWarnIfInactiveTransactions を使用します。

 final SessionPoolOptions sessionPoolOptions = SessionPoolOptions.newBuilder().setWarnIfInactiveTransactions().build()

 final Spanner spanner =
         SpannerOptions.newBuilder()
             .setSessionPoolOption(sessionPoolOptions)
             .build()
             .getService();
 final DatabaseClient client = spanner.getDatabaseClient(databaseId);

Go

警告ログを受信して無効なトランザクションを削除するには、SessionPoolConfigInactiveTransactionRemovalOptions を使用します。

 client, err := spanner.NewClientWithConfig(
     ctx, database, spanner.ClientConfig{SessionPoolConfig: spanner.SessionPoolConfig{
         InactiveTransactionRemovalOptions: spanner.InactiveTransactionRemovalOptions{
         ActionOnInactiveTransaction: spanner.WarnAndClose,
         }
     }},
 )
 if err != nil {
     return err
 }
 defer client.Close()

警告ログの受信のみを行うには、customLogger を使用します。

 customLogger := log.New(os.Stdout, "spanner-client: ", log.Lshortfile)
 // Create a logger instance using the golang log package
 cfg := spanner.ClientConfig{
         Logger: customLogger,
     }
 client, err := spanner.NewClientWithConfig(ctx, db, cfg)

多重化されたセッション

多重化されたセッションを使用すると、1 つのセッションで多数の同時リクエストを作成できます。多重化されたセッションは、複数の gRPC チャネルで使用する ID です。余計なボトルネックが発生しません。多重化されたセッションには次のような利点があります。

  • よりシンプルなセッション管理プロトコルにより、バックエンド リソースの消費量を削減する。たとえば、セッションの所有権のメンテナンスとガベージ コレクションに関連するセッション メンテナンス アクティビティを回避できます。
  • アイドル状態のときに keep-alive リクエストを必要としない長時間セッション。

多重化されたセッションは、以下でサポートされます。

  • Java と Go のクライアント ライブラリ
  • Java クライアント ライブラリと Go クライアント ライブラリに依存する Spanner エコシステム ツール(PGAdapter、JDBC、Hibernate、database/sql ドライバ、GORM など)。

  • Java クライアント ライブラリと Go クライアント ライブラリに依存する Spanner エコシステム ツール(PGAdapter、JDBC、Hibernate、データベースまたは SQL ドライバ、GORM など)。OpenTelemetry 指標を使用すると、既存のセッション プールと多重化されたセッション間でトラフィックがどのように分割されているかを確認できます。OpenTelemetry には、true に設定すると多重化されたセッションを示す指標フィルタ is_multiplexed があります。

多重化されたセッションは、すべてのタイプのトランザクションでサポートされています。

クライアント ライブラリは、古いセッションでトランザクションが送信されないように、多重化されたセッションを 7 日ごとにローテーションします。

多重化されたセッションはデフォルトで無効になっています。クライアント アプリケーションで多重化されたセッションを使用するには、環境変数を使用して多重化されたセッションを有効にする必要があります。Java または Go を使用して多重化されたセッションを有効にするには、多重化されたセッションを有効にするをご覧ください。

考慮事項

空の読み取りまたは書き込みトランザクション ボディ、またはすべてのクエリまたは DML ステートメントが失敗したトランザクションを commit しようとしている場合は、多重化されたセッションで考慮すべきシナリオがいくつかあります。マルチプレックス セッションでは、各 commit リクエストにサーバー生成の pre-commit トークンを含める必要があります。クエリまたは DML を含むトランザクションの場合、サーバーがクライアント ライブラリに有効なトークンを返すには、以前に成功したクエリまたは DML トランザクションが 1 つ以上ある必要があります。クエリまたは DML トランザクションが成功しなかった場合、クライアント ライブラリは commit の前に暗黙的に SELECT 1 を追加します。

ミューテーションのみを含む多重化されたセッションの読み取りまたは書き込みトランザクションで、ミューテーションの 1 つがスキーマに存在しないテーブルまたは列に対するものである場合、クライアントは NOT_FOUND エラーではなく INVALID_ARGUMENT エラーを返す可能性があります。

多重化されたセッションを有効にする

クライアント アプリケーションで多重化されたセッションを使用するには、まず環境変数を設定して有効にする必要があります。

多重化されたセッションを有効にするには、GOOGLE_CLOUD_SPANNER_MULTIPLEXED_SESSIONS 環境変数を TRUE に設定します。このフラグを使用すると、ReadOnly トランザクションの多重化セッションもサポートされます。

export GOOGLE_CLOUD_SPANNER_MULTIPLEXED_SESSIONS=TRUE

多重化されたセッションでパーティショニングされたオペレーションのサポートを有効にするには、GOOGLE_CLOUD_SPANNER_MULTIPLEXED_SESSIONS_PARTITIONED_OPS 環境変数を TRUE に設定します。

export GOOGLE_CLOUD_SPANNER_MULTIPLEXED_SESSIONS_PARTITIONED_OPS=TRUE

多重化されたセッションの読み取り / 書き込みトランザクションのサポートを有効にするには、GOOGLE_CLOUD_SPANNER_MULTIPLEXED_SESSIONS_FOR_RW 環境変数を TRUE に設定します。

export GOOGLE_CLOUD_SPANNER_MULTIPLEXED_SESSIONS_FOR_RW=True

多重化されたセッションでトランザクションをサポートするには、GOOGLE_CLOUD_SPANNER_MULTIPLEXED_SESSIONSTRUE に設定する必要があります。

通常のセッションと多重化されたセッションのトラフィックを確認する

Opentelemetry には、多重化されたセッションのトラフィックが表示される is_multiplexed フィルタがあります。通常のセッションを表示するには、このフィルタを true to view multiplexed sessions and false に設定します。

  1. Spanner Opentelemetry の始める前にの説明に記載されている手順に沿って、Spanner 用 Opentelemetry を設定します。
  2. Metrics Explorer に移動します。

    [Metrics Explorer] に移動

  3. [指標] プルダウンで、generic でフィルタします。

  4. [汎用タスク] をクリックし、[Spanner] > [Spanner/num_acquired_sessions] に移動します。

  5. [フィルタ] フィールドで、次のオプションから選択します。

    a. is_multiplexed = false に設定して、通常のセッションを表示します。 b. is_multiplexed = true に設定して、多重化されたセッションを表示します。

    次の図が示すのは、多重化されたセッションが選択された [フィルタ] オプションです。

Spanner で OpenTelemetry を使用する際の詳細については、OpenTelemetry の活用による Spanner オブザーバビリティの民主化OpenTelemetry を使用して Spanner コンポーネントのレイテンシを調査するをご覧ください。

is-multiplexed フィルタが表示された Opentelemetry ダッシュボード。