タイトルが長い。そしてタイトルで全て語ってしまった。
経緯
Day 1 のこちらのセッションを拝聴させてもらいました。
途中 X に投稿したポストの会話が以下。
話逸れるけど、たとえばRDSでfailoverしてwriter/readerが入れ替わった場合、プールされたコネクションが入れ替わり前のインスタンスに繋がったままになる事象があると思うがそれにはどう対応しているんだろう? #kaigionrails_blue
— Koji NAKAMURA (@kozy4324) 2025年9月26日
こーじさん、聞いていただきありがとうございます!
— Koya Masuda (@koxya) 2025年9月26日
鋭いですね〜
その辺も課題でして、↓のようなことをしないとなと考え中ですhttps://t.co/pqfGqFOS4l
RailsとAmazon Aurora利用時のフェイルオーバー問題
koyaさんが言及している記事に書いてある通りなので説明はそちらに譲るとして、自分が携わっているプロダクトでも最近この話題があって気になってました。
DBMS が特定されていれば、入れ替わった時に発生するエラーは識別可能なのでそれを検知して再接続すればいいのはそれはそう。とは言えもっと統一的な解決策はないものかとここ数日考えていました。
以下のような話もある。
これはフェイルオーバー問題そのものではないが、接続期間の Lifetime を設けてみてはどうかという機能提案。議論は進んでなさそうではある。
コアの実装を変更せずに Lifetime を実現する PoC
さて AbstractAdapter には checkout と checkin のコールバックが設定できるようになっている(checkout と checkin の概念とは何かという話は割愛、ActiveRecord::ConnectionAdapters::ConnectionPool のソースコードを読んでみよう!)。これを利用することで Lifetime 的なものを実現できるのではないかと思い付いた。
ActiveRecord::ConnectionAdapters::AbstractAdapter.set_callback(:checkout, :after) do
reconnection_interval = @config[:reconnection_interval]
next unless reconnection_interval
@window_start_at ||= Process.clock_gettime(Process::CLOCK_MONOTONIC)
if (Process.clock_gettime(Process::CLOCK_MONOTONIC) - @window_start_at) > reconnection_interval
reconnect!
@window_start_at = Process.clock_gettime(Process::CLOCK_MONOTONIC)
end
end
例えば database.yml で reconnection_interval を秒数で設定するという思想。ポイントとしては @config[:reconnection_interval] から値を取得するところで、例えばアプリケーションが複数データベースに接続している場合、あるデータベースには5秒で再接続としたいが別のデータベースには30秒で再接続したい、といったことがこれで実現できる。(と思っている。おいおいちゃんと検証する)
試してみる
rails console で試してみる。TracePoint を使って reconnect メソッドが呼ばれているかを確認してみよう。database.yml は以下のように設定する。
default: &default
adapter: sqlite3
pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
timeout: 5000
reconnection_interval: 5
TracePoint の設定はこんな感じ。
trace = TracePoint.new(:call){|tp|puts "[#{tp.event}] #{tp.defined_class}##{tp.method_id}" if tp.defined_class&.to_s&.include?("SQLite3Adapter")&&tp.method_id&.to_s&.include?("reconnect")}.tap{|trace|trace.enable}
やってみた。
— Koji NAKAMURA (@kozy4324) 2025年9月27日
せっかくなので Gem にしてみた
というわけでこれ。
実装は前述したコールバックを設定する実装だけなので config/initializer あたりにコピペするだけでもいいんだけど、Gem として公開されていれば GitHub Issues にどういった問題があったのかということが記録として残せるでしょうという魂胆です。
おわりに
Kaigi Effect しました!ちなみにこのブログ記事は Day 2 中に書いてます。まだまだ Kaigi on Rails 2025 楽しみましょう!