`reset_connection` を軽量化し、プール再生成が必要な呼び出し元を `reset_pool` へ移行

rails/rails

ActiveRecordのテストヘルパー reset_connection が、既存コネクションに対して DISCARD ALL を発行するだけの軽量な実装に変わりました。これにより、PostgreSQLの too many clients already エラーを引き起こしていたコネクションリークが解消されます。

背景

Railsのナイティリ CI で activerecord postgresql ジョブが FATAL: sorry, too many clients already で断続的に失敗していました。調査の結果、PostgresqlRangeTestPostgresqlEnumTestPostgresqlCompositeTestPostgresqlDomainTest の4テストクラスがteardownで呼ぶ reset_connection がリーク源の一つと特定されました。

問題の根本は reset_connection の実装にありました。従来の実装は ActiveRecord::Base.remove_connection + establish_connection でプール全体を入れ替えるものでした。しかし transactional fixtures が有効な場合、フィクスチャ用のトランザクションが既存コネクションを保持しているため、ConnectionPool#disconnect! は排他ロックを取得できずに無言でタイムアウトし、PostgreSQLソケットがリークします。PostgresqlRangeTest だけでも46件のテストがあり、max_connections=100 の環境では積み重なって接続枯渇を起こすのに十分な量でした。

これらの4クラスが reset_connection を呼ぶ目的は、キャッシュされたクエリプランやセッション状態をクリアすることであり、アダプタインスタンスの差し替えは必要ありません。プールの再生成は、後述するように別のテストが必要とする操作です。

技術的な変更

activerecord/test/support/connection_helper.rb にて reset_connection の実装を軽量化し、プール再生成の責務を reset_pool として分離しました。

変更前:

# Used to drop all cache query plans in tests.
def reset_connection
  original_connection = ActiveRecord::Base.remove_connection
  ActiveRecord::Base.establish_connection(original_connection)
end

変更後:

# Resets state (cached plans, session settings) on the existing connection.
def reset_connection
  @connection.reset!
end

# Replaces the connection pool, yielding a fresh adapter instance.
def reset_pool
  original_connection = ActiveRecord::Base.remove_connection
  ActiveRecord::Base.establish_connection(original_connection)
end

@connection.reset! はPostgreSQLに対して DISCARD ALL を発行します。これにより、コネクションを維持したままキャッシュされたクエリプランとセッション状態がクリアされます。プールの入れ替えが不要なため、transactional fixtures との競合も発生しません。

一方、プールの再生成が必要なケースも存在します。PostgreSQLReferentialIntegrityTestMissingSuperuserPrivileges モジュールをアダプタに extend するため、teardownでそのモジュールが取り除かれた新しいアダプタインスタンスを得る必要があります。このケースでは reset! では不十分なため、reset_pool が提供する remove_connection + establish_connection の実装が引き続き使われます。実際に以下のファイルが reset_pool に移行しています:

  • activerecord/test/cases/adapters/postgresql/referential_integrity_test.rb
  • activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb(6箇所)
  • activerecord/test/cases/adapters/abstract_mysql_adapter/active_schema_test.rb
  • activerecord/test/cases/connection_adapters/mysql_type_lookup_test.rb

効果の測定は postgres:alpine(PG 18、max_connections=100)でリーク元の4ファイルのみを対象に実施されており、ピーク接続数は修正前の 7〜12 から修正後の 2(フィクスチャのベースライン)まで低減しています。

設計判断

1つのヘルパーに2つの異なる責務が混在していたという問題を、命名によって明示的に分離した設計変更です。

PR #57409 の段階では reset_connection の呼び出し元を個別に @connection.reset! に置き換える方針が検討されました。しかし本PRでは、ヘルパー側の実装を変更することで呼び出し元(リーク源の4ファイル)の変更なしに修正を適用しつつ、プール再生成を必要とする呼び出し元には reset_pool という明示的な名前を与えています。reset_connection という名前が従来「プール全体の再生成」を意味していたことへの技術的負債が解消され、名前と実装のセマンティクスが一致した状態になりました。

また、PRの説明に「4つのリーク元ファイルは diff に現れない」と明記されている点も設計上の意図を示しています。リーク修正のメインパスは reset_connection の再実装であり、それ以外の変更は「プール再生成が本当に必要な呼び出し元を reset_pool に明示的に移行する」作業として分離されています。

まとめ

本PRは、テストヘルパーの実装と命名のミスマッチを修正することで、コネクションリークという実際の障害を解消した変更です。「既存コネクションのリセット」と「プールの再生成」という2つの操作を reset_connectionreset_pool に分離したことで、今後の呼び出し元がそれぞれの副作用を意識した選択ができるようになりました。

記事メタデータ

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

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

品質レビュー結果

Review Status:
承認済み
Review Count:
1回
Reviewed by:
Gemini 2.5 Pro for DiffDaily

Review Criteria:

記事構成 ✓ PASS

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

「総論(リード文)→各論(背景、技術的変更、設計判断)→結論(まとめ)」の構成が明確で、ガイドラインを完全に満たしています。

カスタムMarkdown構文 ✓ PASS

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

ファイル名付きシンタックスハイライト、PR番号のリンク記法共に正しく使用されています。

対象読者への適合性 ✓ PASS

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

「transactional fixtures」「コネクションリーク」「DISCARD ALL」といった専門用語を前提としており、専門知識を持つエンジニアという対象読者に適合しています。

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

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

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

Diff内容との照合 ✓ PASS

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

記事内で引用されている`activerecord/test/support/connection_helper.rb`のコード変更は、提供されたDiff情報と完全に一致しています。また、`reset_pool`への移行ファイルリストも正確です。

技術用語の正確性 ✓ PASS

技術用語の正確な使用

「コネクションリーク」「transactional fixtures」「DISCARD ALL」など、PRで使われている技術用語を正確かつ適切な文脈で使用しています。

説明の技術的正確性 ✓ PASS

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

コネクションリークの原因、`reset!`の役割、`reset_pool`が必要なケースなど、技術的な説明はPR Descriptionの内容と整合性が取れており、正確です。

事実の突合 ✓ PASS

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

CIでのエラー、原因特定、修正内容、効果測定の数値(ピーク接続数の低減)など、記事内のすべての主張がPR情報によって裏付けられており、ハルシネーションは見られません。

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

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

PR番号(#57432, #57409)、効果測定の数値(ピーク接続数 7〜12→2)、PGバージョン(PG 18)など、すべての数値・固有名詞が正確です。

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

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

記事のタイトルはPRの主題「`reset_connection`を軽量化し、旧実装を`reset_pool`として分離する」内容を的確に要約しています。

外部知識の正確性 ✓ PASS

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

記事に含まれる情報はすべてPR情報(Title, Description, Diff)に基づいており、PRに記載のない外部知識の追加はありません。

時間表現の正確性 ✓ PASS

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

「断続的に失敗していました」(過去の問題)、「実装に変わりました」(今回の変更)など、時間表現はPRの文脈と一致しており正確です。