テストの実行順序制御でコネクションリークを修正

rails/rails

LoadAsyncMultiThreadPoolExecutorTestLoadAsyncMixedThreadPoolExecutorTest において、トランザクショナルフィクスチャとコネクションプールの差し替えタイミングがずれることで発生していたPostgreSQLコネクションリークを、before_setup / after_teardown への移行で修正しました。

背景

Railsのナイトリービルド(activerecord postgresql ジョブ)が FATAL: sorry, too many clients already で断続的に失敗していました。調査の結果、コネクションリークの発生源の一つがこの2つのテストクラスにあることが特定されました。

問題の発端は 5e047f2(「Clean up async test」、2023年2月)です。このコミット以前は setupensure 節で establish_connection の呼び出しを即座に元に戻していたため、:multi_thread_pool 設定は実際には機能していませんでした。ensure の除去によりテストが正しく動作するようになりましたが、同時にコネクションリークが顕在化しました。

リークのメカニズムは次の3段階で起きていました:

  1. トランザクショナルフィクスチャsetup の実行前に元の arunit / arunit2 プールに対してトランザクションを開始する
  2. setup 内で Base.establish_connection(config_hash) を呼び出し、コネクションプールを差し替える。この時点でフィクスチャのトランザクションは古いコネクションを掴んだまま残る
  3. ConnectionPool#disconnect! がそのコネクションを排他的に取得できずにサイレントタイムアウトし、PGソケットが解放されない

このリークにより、テスト実行ごとにピーク時で最大28本のコネクションが消費される状態でした(max_connections=100 の環境での測定値)。他のテストファイルでのリークと合わさることで、接続数の枯渇を引き起こしていました。

技術的な変更

setup / teardownbefore_setup / after_teardown に置き換え、super の呼び出し位置を調整することで、フィクスチャのライフサイクルとプール差し替えのタイミングを正しく整合させました。

変更前(LoadAsyncMultiThreadPoolExecutorTest):

def setup
  @old_config = ActiveRecord.async_query_executor
  ActiveRecord.async_query_executor = :multi_thread_pool
  # ...
  ActiveRecord::Base.establish_connection(config_hash1)
  ARUnit2Model.establish_connection(config_hash2)
end

def teardown
  ActiveRecord.async_query_executor = @old_config
  clean_up_connection_handler
  ActiveRecord::Base.establish_connection(:arunit)
  # ...
end

変更後:

def before_setup
  @old_config = ActiveRecord.async_query_executor
  ActiveRecord.async_query_executor = :multi_thread_pool
  # ...
  ActiveRecord::Base.establish_connection(config_hash1)
  ARUnit2Model.establish_connection(config_hash2)

  super  # ← superを末尾で呼ぶことでフィクスチャが差し替え後のプールを使う
end

def after_teardown
  super  # ← superを先頭で呼んでフィクスチャのロールバックを完了させてから
  ActiveRecord.async_query_executor = @old_config
  clean_up_connection_handler
  ActiveRecord::Base.establish_connection(:arunit)
  # ...
end

before_setupsuper を末尾に置くことで、プールの差し替えが完了した後にトランザクショナルフィクスチャが起動します。これにより、フィクスチャのトランザクションは最初から差し替え後のプールに対して開かれるため、disconnect! が「使用中のコネクション」に遭遇することがなくなります。after_teardown では super を先頭に置き、フィクスチャのロールバックが完了してからプールを元に戻す順序を保証しています。

LoadAsyncMixedThreadPoolExecutorTest でも同様のパターンが適用されており、ENV["RAILS_ENV"] やコネクション設定の復元を含む teardown ロジック全体が after_teardown に移行しています。

設計判断

setup ではなく before_setup を使うという選択は、Minitest のフック実行順序を活用したものです。Minitest では before_setup → setup → (テスト本体) → teardown → after_teardown の順でフックが実行されます。ActiveRecord のトランザクショナルフィクスチャは setup のタイミングでトランザクションを開始するため、それより前に実行される before_setup でプールを差し替えれば、フィクスチャは必ず正しいプールを見ることになります。

コネクションプールのすり合わせや disconnect! のタイムアウト処理を変更するのではなく、テスト側の実行順序を修正するアプローチを選んだことも注目に値します。根本原因は「フィクスチャよりも後にプールを差し替えていた」というタイミングの問題であり、プール実装を変更せずにテストコードの修正だけで解決しています。

まとめ

この修正は、before_setup / after_teardown というMinitest標準のフックを適切に使うことで、コネクションプールとトランザクショナルフィクスチャのライフサイクルを正しく整合させたものです。ピーク時に25〜28本あったコネクション数がフィクスチャのベースライン(2本)まで削減されており、ナイトリービルドの安定化に直接寄与する変更です。

記事メタデータ

Generated by:
Claude Sonnet 4.6 for DiffDaily
LLM Trace:
e536cbad

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

品質レビュー結果

Review Status:
リトライ後承認
Review Count:
2回 (改善を経て承認)
Reviewed by:
Gemini 2.5 Pro for DiffDaily

Review Criteria:

記事構成 ✓ PASS

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

リード文(総論)→セクション群(各論)→まとめ(結論)の構成が明確です。背景、技術的変更、設計判断、まとめの各要素が論理的に配置されており、非常に理解しやすい構造になっています。

カスタムMarkdown構文 ✓ PASS

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

ファイル名付きシンタックスハイライト(```ruby:filepath)およびGitHubのコミットIDリンク記法が正しく使用されています。

対象読者への適合性 ✓ PASS

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

Minitestのフック、ActiveRecordのコネクションプール、トランザクショナルフィクスチャといった専門用語を前提としており、専門知識を持つエンジニアという対象読者に適合しています。

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

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

各セクションが総論→各論で構成され、各段落はトピックセンテンスで始まっています。1段落1トピックの原則も守られており、可読性が高いです。

Diff内容との照合 ✓ PASS

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

記事内のコードブロックは、提供されたDiffの内容を正確に反映しています。`before_setup` / `after_teardown`への変更と`super`の呼び出し位置が正しく示されています。

技術用語の正確性 ✓ PASS

技術用語の正確な使用

「トランザクショナルフィクスチャ」「コネクションプール」「PGソケット」などの技術用語が、PRの文脈に沿って正確に使用されています。

説明の技術的正確性 ✓ PASS

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

コネクションリークのメカニズムや、`before_setup` / `after_teardown` を使用する解決策の説明は、PRの記述と一致しており、技術的に正確です。

事実の突合 ✓ PASS

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

記事内の主張はすべてPR Description(動機、リークのメカニズム、測定結果など)によって裏付けられており、ハルシネーションは検出されませんでした。

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

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

PR番号(#57434)、コミットID(5e047f2)、コネクション数の測定値(最大28本、修正後2本)など、すべての数値・固有名詞が正確です。

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

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

記事のタイトル「テストの実行順序制御でコネクションリークを修正」は、PRの主題と修正のアプローチを的確に要約しており、内容と一致しています。

外部知識の正確性 ✓ PASS

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

Minitestのフック実行順序に関する説明は、PRの意図を解説するために必要な自明な知識であり、PRに記載のない外部知識の捏造は見られません。

時間表現の正確性 ✓ PASS

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

「このコミット以前は」「〜により顕在化しました」といった時間表現は、PRに記載された事実の前後関係を正確に反映しています。