セッション リークの見つけ方: Cloud Spanner でアプリケーションが応答しないときのデバッグ方法
Astha Mohta
Software Engineer, Google Cloud
※この投稿は米国時間 2023 年 11 月 28 日に、Google Cloud blog に投稿されたものの抄訳です。
Cloud Spanner は、ミッション クリティカルな業務に対応可能なフルマネージドのリレーショナル データベース サービスです。トランザクションの整合性をグローバル スケールで提供し、自動的な同期レプリケーションによって高可用性を確保します。お客様は、Google が開発、サポートしているオープンソースのクライアント ライブラリを使って、自社のアプリを Spanner に接続できます。Spanner クライアント ライブラリを使用しているアプリケーションで、トランザクションやクエリがブロックされる、または、長時間待機してもトランザクションが完了しないという問題にお困りでしたら、この投稿をぜひお読みください。そうした場合、セッション リークが発生している可能性があります。ここでは、セッションおよびセッション プールについて改めてご説明するとともに、セッション リークのデバッグ方法や解決方法をご紹介します。
セッションとは
セッションとは、Cloud Spanner データベース サービスとの通信チャネルを表します。データベース テクノロジーの種類によっては、通信チャネルを接続と呼ぶ場合もあります。セッションを使って、Cloud Spanner データベースのトランザクション(データの読み取り、書き込み、変更)が実行されます。個々のセッションは 1 つのデータベースに関連付けられ、データベースからセッションへのマッピングは 1 対多の関係になります。Spanner では、セッションは一度に 1 つのトランザクションのみを実行できます。スタンドアロンの読み取り、書き込み、クエリは、それぞれ 1 つのトランザクションとカウントされます。
セッション プールとは
セッションの作成には高額な費用がかかります。データベース オペレーションのたびに費用が発生することを避けるため、クライアント ライブラリはセッション プール(すぐに使用可能なセッションの集まり)を使用するように設定されています。ここで、既存セッションを格納したキャッシュを維持しながら、リクエストに応じて適切なタイプのセッションを返したり、使用されていないセッションのクリーンアップを処理したりしています。
セッションは長時間有効であるため、データベース オペレーションのために使用した後は、再利用のためにキャッシュに戻す必要があります。このセッション プールで、セッションの最小数および最大数や、保持するアイドル セッションの数などを設定できます。
セッション リークとは
クライアント ライブラリの Spanner オブジェクトには、セッション数の上限が設けられています。たとえば、Golang クライアント ライブラリと Java クライアント ライブラリのセッション数の上限値はそれぞれ、デフォルトで 100、400 に設定されています。これらの値は、クライアント サイドで Database
オブジェクトを作成するときに SessionPoolOptions
を渡すことで設定できます。
セッション プールからセッションがすべてチェックアウトされているときは、セッションがプールに返されるまで、新しいトランザクションは待機する必要があります。セッションがプールに返されないとき(それに伴いセッションリークが発生しているとき)は、トランザクションが無限に待機することになり、アプリケーションがブロックされます。
セッション リークの原因
セッション リークの最もよくある原因は、アプリケーションによってトランザクションが開始されたものの、コミットやロールバックが行われていないことです。トランザクション コードの末尾で、トランザクションに対して必ずコミットを実行するようにしましょう。
Spanner には、読み取り専用と、読み取り / 書き込みの 2 種類のトランザクションがあります。読み取り / 書き込みのトランザクションで読み取りを実行する場合も、コミットが必要です。
以下の例で示されているように、トランザクションの終了時にはコミットを実行する必要があります。コミットやロールバックを呼び出さないと、セッション リークにつながります。
以下は Java の例です。Java の try-with-resources 構文は、完了時にセッションを解放します。ただし、すべてのリソース(ResultSet など)に対して close() メソッドを明示的に呼び出す場合はその限りではなく、そこでトランザクションが実行されない場合、リークが発生します。
セッション リークのデバッグおよび解決方法
ロギング
デフォルトでロギング オプションが有効になっており、セッション プールの使用率が 95% を超えると警告ログが表示されます。この場合、2 つの可能性が考えられます。1 つ目は、クライアント サイドのデータベース オブジェクトを使って実行しているクエリの数が、セッション プールで対応可能な数を上回っているようなケースで、この場合は、セッション プールのセッション数の上限値を増やす必要があります。もう 1 つは、セッション リークが発生している可能性が考えられます。
どのトランザクションがセッション リークを起こしているかをデバッグできるよう、ログには実行時間が想定外に長くなっているトランザクションのスタック トレースも表示されます。Java と Golang では、ログ エクスポータの設定に基づき、ログがプッシュされます。
非アクティブなトランザクションを自動的に削除する
非アクティブなトランザクションを自動的に削除するオプションを選択すると、実行時間が予想よりもはるかに長い、リークを引き起こしている可能性のあるトランザクションが自動的に特定、クローズされます。Java や Golang では、セッションがプールから削除されて、新しいセッションで置き換えられます。
どのトランザクションがクローズされたかを詳しく調べるには、どの言語でも、ログでトランザクションのスタック トレースを確認して、リークの可能性を特定し、デバッグするという手順を取ります。
コードサンプル
以下のサンプルに、上記で説明した機能を有効 / 無効にする方法を示します。
Java
GoLang
ログ メッセージ
Java
Golang
ここで紹介したベスト プラクティスとログを使うことで、アプリケーションの信頼性を高められます。これらの方法で、セッション リークにつながるようなアプリケーションの問題を特定してデバッグし、アプリケーションの動作や応答が停止しないようにしましょう。セッションについて詳しくは、Spanner のドキュメントをお読みください。
ー Google Cloud、ソフトウェア エンジニア Astha Mohta