セッション数

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

セッションの概要

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

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

独立した読み取り、書き込み、クエリは、内部で 1 つのトランザクションを使用します。

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

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

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

gRPC チャネルの概要

gRPC チャネルは、Spanner クライアントが通信に使用します。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. [NumChannels] を [MaxSessions / 100] に設定します。1 つの gRPC チャネルで最大 100 件のリクエストを同時に処理できます。テール レイテンシ(p95/p99 のレイテンシ)が高い場合は、この値を増やします。これは、gRPC チャネルの輻輳を示している可能性があるためです。

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

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

アプリケーションのセッション プール サイズによって、1 つのアプリケーション インスタンスで同時実行可能なトランザクションの数が決まります。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/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% を超える場合は、セッション プールで許可される最大セッションを増やす必要があるか、セッション リークが発生している可能性があります。警告ログには、想定よりも長く実行されたトランザクションのスタック トレースが記録され、セッション プールの使用率が高い原因の特定に役立ちます。警告ログは、ログ エクスポータの構成に応じて push されます。

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

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

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

警告ログを受信して非アクティブなトランザクションを削除するには、InactiveTransactionRemovalOptionsSessionPoolConfig を使用します。

 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)

多重化されたセッション

多重化されたセッションでは、一度に作成できる同時リクエストの数に制限はありません。多重化セッションには、以下の利点があります。

  • バックエンド リソースの要件を削減します。たとえば、セッション所有権のメンテナンスやガベージ コレクションに関連するセッション メンテナンス アクティビティを回避します。
  • アイドル状態のときに keep-alive リクエストを必要としない長時間セッション。
  • 一般的なクライアント ライブラリでは、クライアントごとに 1 つの多重セッションを使用できます。使用中の通常セッションの数は、一部のオペレーションで多重化セッションを使用するクライアントでは、通常のセッションのみを使用するクライアントよりも少なくなります。
  • 1 つの gRPC チャネルだけにアフィニティを持つ必要はありません。クライアントは、同じ多重化セッションの複数のチャネルにリクエストを送信できます。

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

  • Java クライアント ライブラリ
  • Java クライアント ライブラリに依存する Spanner エコシステム ツール(PGAdapter、JDBC、Hibernate など)
  • 読み取り専用トランザクション用の Spanner API

OpenTelemetry 指標を使用して、既存のセッション プールと多重化されたセッションの間でトラフィックがどのように分割されるかを確認します。OpenTelemetry には、true に設定すると多重化されたセッションを表示する指標フィルタ is_multiplexed があります。

JDBC と PG アダプタでは、多重化されたセッションがデフォルトで有効になっています。Java クライアント ライブラリの場合、デフォルトでは無効になっています。多重化セッションを有効にするには、Java クライアント ライブラリを使用します。Java を使用して多重化セッションを有効にするには、多重化セッションを有効にするをご覧ください。

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

Java クライアントを使用して多重化セッションを有効にするには、GOOGLE_CLOUD_SPANNER_MULTIPLEXED_SESSIONStrue に設定します。

export GOOGLE_CLOUD_SPANNER_MULTIPLEXED_SESSIONS=TRUE

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

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

  1. Spanner Opentelemetry の始める前にセクションの手順を使用して、Spanner の Opentelemetry を設定します。
  2. Metrics Explorer に移動します。

    Metrics Explorer に移動

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

  4. [Generic Task] をクリックし、[Spanner] > [Spanner/num_acquired_sessions] に移動します。

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

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

    次の画像は、多重化されたセッションが選択された [フィルタ] オプションを示しています。

Spanner での OpenTelemetry の使用について詳しくは、OpenTelemetry を活用して Spanner オブザーバビリティを民主化するOpenTelemetry によるトレースをご覧ください。

多重化フィルタを示す Opentelemetry ダッシュボード。