テスト実行時の接続プールリーク防止のためのクリーンアップロジック追加

rails/rails

このPRは、テスト実行中に発生していたデータベース接続プールのリークを検出し、too many clients エラーを回避するために、接続プールの明示的な切断処理を追加します。

背景

テスト CI が ActiveRecord::ConnectionNotEstablished: connection to server … FATAL: sorry, too many clients already で失敗していたことが報告されています。原因はテスト実行中に不要になったプールが切断されずに残り、PostgreSQL の接続上限に到達したためです。

従来の teardown_shared_connection_pool では、プール設定を元に戻すだけで、削除されたロールに紐付くプールを明示的に切断する処理が欠如していました。そのため、テストが終了しても内部的に保持されたソケットが解放されず、次回実行時に累積して接続数が増加していました。

技術的な変更

teardown_shared_connection_pool の拡張

teardown_shared_connection_poolremoved_pool_configs 配列を導入し、ロールが存在しない場合に対象プール設定を収集します。最後に全プールマネージャの残存プール設定と比較し、削除対象であれば PoolConfig.disconnect! を呼び出して接続をクローズします。

変更前:

pool_manager = handler.send(:connection_name_to_pool_manager)[name]
pool_manager.set_pool_config(role, shard_name, pool_config) if pool_manager.get_pool_config(role, shard_name)

変更後:

pool_manager = pool_managers[name]
if pool_manager.get_pool_config(role, shard_name)
  pool_manager.set_pool_config(role, shard_name, pool_config)
else
  removed_pool_configs << pool_config
end

さらに、remaining_pool_configs を算出し、removed_pool_configs に含まれるが残存リストに無いプール設定について disconnect! を実行します。

clean_up_connection_handler の拡張

clean_up_connection_handler でも同様のロジックを追加し、ロール削除時に取得した removed_pool_configs を収集します。削除後に残りのプール設定と突き合わせ、不要となったプールを確実に切断します。

変更前:

handler.instance_variable_get(:@connection_name_to_pool_manager).each do |owner, pool_manager|
  pool_manager.role_names.each do |role_name|
    next if ...
    pool_manager.remove_role(role_name)
  end
end

変更後:

pool_managers = handler.instance_variable_get(:@connection_name_to_pool_manager)
removed_pool_configs = []
pool_managers.each do |owner, pool_manager|
  pool_manager.role_names.each do |role_name|
    next if ...
    removed_pool_configs.concat(pool_manager.remove_role(role_name)&.values || [])
  end
end
remaining_pool_configs = pool_managers.values.flat_map(&:pool_configs)
removed_pool_configs.compact.uniq.each do |pool_config|
  pool_config.disconnect! unless remaining_pool_configs.include?(pool_config)
end

テストの追加と修正

  • connection_handler_test.rbtest_clean_up_connection_handler_disconnects_pools を追加し、25 回のプール生成/チェックアウト/チェックイン 循環で例外が発生しないことを検証しました。
  • test_fixtures_test.rbtest_teardown_shared_connection_pool_disconnects_pool_configs_for_removed_roles を追加し、読み取りロール用プールが disconnect! 後に connected? が false になることを確認しています。
  • 既存テストは setup_shared_connection_poolteardown_shared_connection_pool の呼び出し順序を調整し、クリーンアップが正しく行われることを保証しています。

設計判断

明示的な切断 を導入することで、テストプロセスが終了した瞬間にデータベース接続が解放され、外部リソースのリークを防止します。接続プールは内部で再利用されることが前提ですが、テスト環境では頻繁にロールが入れ替わるため、自動再接続 に任せるだけでは不十分でした。

変更は 既存 API のシグネチャや公開インターフェイスを変更せず、内部実装のみを拡張する形で行われました。PoolConfig.disconnect! の呼び出しは内部ロジックに留め、アプリケーションコード側への影響はありません。これにより、既存テストやユーザーコードはそのまま動作しつつ、CI の安定性が向上します。

まとめ

テスト実行時の接続プールリークを防止するために、teardown_shared_connection_poolclean_up_connection_handler にプール設定の切断ロジックを追加しました。これにより、不要になったプールが確実に閉じられ、too many clients エラーの再発が防がれます。テストコードも併せて拡充され、変更の正当性が自動的に検証されます。

記事メタデータ

Generated by:
gpt-oss-120b for DiffDaily
LLM Trace:
3fcb787a

この記事はAIによって自動生成されています。内容の正確性については、必ずソースコードやPRを確認してください。

品質レビュー結果

Review Status:
承認済み
Review Count:
1回
Reviewed by:
gpt-oss-120b for DiffDaily

Review Criteria:

記事構成 ✓ PASS

Title, Context, Technical Detailの存在と明確さ

リード文、背景、技術的変更、設計判断(任意)、まとめの5要素が揃っており、総論→各論→結論の流れが明確です。

カスタムMarkdown構文 ✓ PASS

シンタックスハイライト・GitHubリンク記法の正確性

コードブロックは ````ruby:filepath`` の形式で正しく記載され、PRリンクは [#57088](URL) の形で正しくハイパーリンク化されています。

対象読者への適合性 ✓ PASS

エンジニア向けの適切な技術レベルと表現

Rails の内部実装やテストフレームワークに関する記述が中心で、専門エンジニア向けの内容になっています。

パラグラフ・ライティング ✓ PASS

トピックセンテンス・1段落1トピック・段落長

各セクションは総論パラグラフ→各論パラグラフ→結論パラグラフで構成され、段落はトピックセンテンスで始まり、長さも適切です。空行で区切られています。

Diff内容との照合 ⚠ WARNING

コードブロックとDiff内容の一致

test_fixtures.rb の変更後コードブロックが `remaining_pool_configs` を算出し `disconnect!` を呼び出す部分を省略しています。Diff の全容を正確に反映していない点があります。

技術用語の正確性 ✓ PASS

技術用語の正確な使用

PoolConfig、connection handler、disconnect! などの用語は正しく使用され、PR と整合しています。

説明の技術的正確性 ✓ PASS

技術的主張の正確性と論理性

接続プールのリークがテストで発生し、追加した切断ロジックで解決するという説明は PR の意図と合致しています。

事実の突合 ✓ PASS

PR情報による主張の裏付け(ハルシネーション検出)

記事の主張はすべて PR のタイトル・説明・Diff の内容で裏付けられており、推測や外部知識の付加はありません。

数値・固有名詞の確認 ✓ PASS

PR番号・コミットID・バージョン等の正確性

PR 番号や 25 回のループ回数など、数値は Diff と一致しています。

タイトル・説明との一致 ✓ PASS

記事タイトル・説明とPR内容の一致

記事タイトルは PR の目的を日本語で要約したもので、内容と矛盾していません。

外部知識の正確性 ✓ PASS

PRに記載のない外部知識(LTS、サポート状況など)の不使用

Rails のバージョンサポートやリリーススケジュール等、PR に記載のない外部情報は含まれていません。

時間表現の正確性 ✓ PASS

時間表現がPR情報と一致しているか

時間表現の歪曲はなく、PR の記述通りに現在・過去が表現されています。