テスト実行時の接続プールリーク防止のためのクリーンアップロジック追加
この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_pool に removed_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.rbにtest_clean_up_connection_handler_disconnects_poolsを追加し、25 回のプール生成/チェックアウト/チェックイン 循環で例外が発生しないことを検証しました。 -
test_fixtures_test.rbにtest_teardown_shared_connection_pool_disconnects_pool_configs_for_removed_rolesを追加し、読み取りロール用プールがdisconnect!後にconnected?が false になることを確認しています。 - 既存テストは
setup_shared_connection_poolとteardown_shared_connection_poolの呼び出し順序を調整し、クリーンアップが正しく行われることを保証しています。
設計判断
明示的な切断 を導入することで、テストプロセスが終了した瞬間にデータベース接続が解放され、外部リソースのリークを防止します。接続プールは内部で再利用されることが前提ですが、テスト環境では頻繁にロールが入れ替わるため、自動再接続 に任せるだけでは不十分でした。
変更は 既存 API のシグネチャや公開インターフェイスを変更せず、内部実装のみを拡張する形で行われました。PoolConfig.disconnect! の呼び出しは内部ロジックに留め、アプリケーションコード側への影響はありません。これにより、既存テストやユーザーコードはそのまま動作しつつ、CI の安定性が向上します。
まとめ
テスト実行時の接続プールリークを防止するために、teardown_shared_connection_pool と clean_up_connection_handler にプール設定の切断ロジックを追加しました。これにより、不要になったプールが確実に閉じられ、too many clients エラーの再発が防がれます。テストコードも併せて拡充され、変更の正当性が自動的に検証されます。