PostgreSQL の FATAL 例外を ConnectionFailed に明示的に変換
PostgreSQL が接続を強制終了する FATAL エラーを、Rails が汎用の StatementInvalid ではなく ConnectionFailed として扱えるようにし、接続切断を早期に検知できるようになりました。
背景
FATAL はサーバ側が接続を終了させることを意味し、これを StatementInvalid にマッピングすると、アプリケーションは実際には切断された接続を「失敗したクエリ」として扱い、次回以降の利用で予期しないエラーが発生します。PR #57057 はこの認識のずれを修正し、致命的エラーを正確に ConnectionFailed へ変換することを目的としています。
この変更により、データベースが切断した瞬間に例外が上がり、プールへ返却される前に再接続がトリガーされるため、接続切れが遅延なく検出できるようになります。
技術的な変更
@needs_reconnect フラグを AbstractAdapter に導入し、接続が再接続を要する状態かどうかを明示的に管理します。initialize で初期化し、reconnect! や disconnect! の処理でフラグをリセット、verify! でフラグが立っている場合に再接続を実行するロジックを追加しました。
perform_query では従来の exec_params 系メソッドをやめ、send_query_params / send_query_prepared / send_query に置き換えてクエリ送信後に自前で get_result を呼び出すように変更しました。これによりクエリ実行直後に結果オブジェクトを取得でき、FATAL エラーが notice_receiver 経由で届いた場合でも捕捉できます。
新たに導入された get_result は raw_connection.get_result をループで取得し、結果が複数あっても最後のものに対して result.check を呼び出すことで、PGRES_FATAL_ERROR を検出して例外を上げます。さらに connection_terminating_severity? ヘルパーで FATAL または PANIC の重大度を判定し、ErrorResultSnapshot がエラーフィールドやメッセージを保持できるようにしました。
テストコードも拡充され、@needs_reconnect が立った接続でクエリを走らせたときに自動的に再接続されること、kill_connection_from_server によるバックエンド強制終了が ActiveRecord::ConnectionFailed として伝搬することを検証しています。
設計判断
フラグ方式で再接続要件を管理する選択は、既存の接続プールロジックに最小限の侵入で済む点が評価されています。needs_reconnect? は内部的に使用され、外部 API として公開しないことで実装の自由度を保ちつつ、接続状態の明示化を実現しました。
低レベル libpq 関数の直接使用は、exec_params が内部で結果取得を行うため致命的エラーを拾い損ねる問題を回避するための手段です。これによりエラー報告経路を一本化し、ConnectionFailed への正確なマッピングが可能になりましたが、コードパスが増えることでテストカバレッジが必要になるというトレードオフがあります。
エラースナップショットの導入は、PG の診断情報を保持しつつ例外オブジェクトにラップすることで、上位層でのエラーハンドリングをシンプルに保つ設計です。これにより ActiveRecord::ConnectionFailed の cause に元の PG::AdminShutdown などが正しく添付され、デバッグ情報が失われません。
まとめ
この PR は PostgreSQL の FATAL エラーを正確に捕捉し ConnectionFailed として扱う仕組みを追加し、接続切断時の例外伝搬を改善しました。@needs_reconnect フラグと低レベルクエリ送信ロジックの組み合わせにより、既存アプリケーションへの影響を最小限に抑えつつ、接続の健全性検知を強化しています。