DiffDaily

Deep & Concise - OSS変更の定点観測

[rails/rails] PostgreSQL接続のリセット時にschema_search_pathが再適用されない問題を修正

rails/rails

Context

Rails 8.1において、PostgreSQLアダプタでActiveRecord::Base.connection.reconnect!を呼び出すか、コネクションプールによる接続リセットがトリガーされた際に、database.ymlで設定したschema_search_pathが再適用されない不具合が発見されました。

この問題は、PostgreSQLが新しいセッションを作成する際にsearch_pathを含むすべてのセッションレベルの状態をリセットするにもかかわらず、PostgreSQLAdapterが以前適用した@schema_search_pathをキャッシュしており、schema_search_path=メソッドが不必要に処理をスキップすることで発生していました。結果として、PostgreSQLはデフォルトの"$user", publicにフォールバックし、複数のスキーマに依存するアプリケーションが正常に動作しなくなっていました。

Technical Detail

問題の根本原因

PostgreSQLAdapter@schema_search_pathインスタンス変数でsearch_pathをキャッシュしていますが、接続がリセットまたは再接続される際にこのキャッシュがクリアされていませんでした。これにより、schema_search_path=メソッド内のガード節が誤って早期リターンし、新しいセッションに対してsearch_pathが設定されない状態が発生していました。

実装された修正

今回の修正では、PostgreSQLのセッション状態が無効化されるタイミングで@schema_search_pathキャッシュをクリアする処理を追加しています。

キャッシュクリア処理の追加:

def clear_cache!(new_connection: false)
  super
  @schema_search_path = nil if new_connection
end

clear_cache!メソッドにnew_connectionパラメータが追加され、新しい接続が確立される場合(PG::Connection#resetによる再接続やDISCARD ALLによるセッションリセット時)に@schema_search_pathをnilにリセットします。

これにより、configure_connectionメソッドが実行される際に、キャッシュされた値が存在しないため、設定されたsearch_pathが正しく再適用されます。

PostgreSQL 18+向けの最適化

def schema_search_path
  @schema_search_path ||=
    with_raw_connection { |conn| conn.parameter_status("search_path") } ||
    query_value("SHOW search_path")
end

PostgreSQL 18以降では、parameter_statusメソッドがsearch_pathをサポートするようになったため、SQLクエリを発行する代わりにこのメソッドを優先的に使用するように変更されました。これにより、不要なネットワークラウンドトリップを削減できます。フォールバックとして従来のSHOW search_pathクエリも保持されています。

テストケースの追加

def test_schema_search_path_is_reapplied_after_reconnect
  db_config = ActiveRecord::Base.configurations.configs_for(env_name: "arunit", name: "primary")

  connection = ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.new(
    db_config.configuration_hash.merge(schema_search_path: "public,foo")
  )

  connection.connect!

  assert_equal "public, foo", connection.select_value("SHOW search_path")

  connection.reconnect!

  assert_equal "public, foo", connection.select_value("SHOW search_path")
ensure
  connection&.disconnect!
end

reconnect!reset!の両方のシナリオに対するテストケースが追加され、接続リセット後もsearch_pathが正しく維持されることを検証しています。

Impact

この修正により、以下の動作が保証されます:

  • reconnect!メソッド呼び出し後もdatabase.ymlで設定したschema_search_pathが維持される
  • コネクションプールによる自動リセット時も設定が保持される
  • 通常動作時の不要なSET search_path呼び出しは回避される(既存のガードロジックが保持されている)
  • PostgreSQL 18以降ではより効率的なparameter_statusメソッドが使用される

この問題はpostgresqlpostgisの両方のアダプタに影響していましたが、この修正により両方で解決されます。