gem の PR から学んだ ActiveRecord と lease_connection の話
これは フィヨルドブートキャンプ Advent Calendar 2025 の21日目の記事です。
昨日の20日目の記事は、SKMT さんの Q&Aのすゝめ でした。
ここまで自力で調べた上で質問できていると、回答する側の負担も減り、欲しい答えをピンポイントで得られるんだなと、質問力の大切さを改めて実感する記事でした!
本記事は、ActiveRecord に追加された lease_connection がなぜ必要になったのかを調べ、その背景と挙動を整理したものになります。
はじめに
AnnotateRb という gem の PR を眺めている中で、Use #lease_connection if available #292 という PR が目に留まりました。
この PR では、これまで ActiveRecord::ConnectionHandling#connection を利用していた箇所が ActiveRecord::ConnectionHandling#lease_connection に置き換えられています。
それを見て、正直なところ最初は「なぜ lease_connection に変える必要があるんだろう?
これまで通り connection ではダメなのかな?」と素朴に思ったことが、調べるきっかけになりました。
ActiveRecord における connection 管理の前提
ActiveRecordではデータベースとの接続は ActiveRecord::ConnectionAdapters::ConnectionPool によって管理されています。
アプリケーションは通常、この ConnectionPool から connection を checkout(取得)し、処理が終わると checkin(返却)する、という流れで動作します。
多くの Rails アプリケーションでは、このライフサイクルを意識する必要はありません。
リクエストやジョブの開始・終了に合わせて、Rails が暗黙的に connection を管理してくれるためです。
ただし重要なのは、connection は常に新しく取得されるものではない、という点です。
ConnectionPool はスレッド(あるいは Fiber)に紐づいて connection を管理しており、状況によっては既に取得済みの connection が再利用されます。
何が問題だったのか
この変更の背景には、ActiveRecord::Base.with_connection を中心とした connection 管理の見直しがあります。
with_connection は、ブロック内で connection を明示的に借り、処理が終わったら確実に返却するための API です。
これは、従来 Rails が暗黙的に行ってきた 「リクエスト単位での connection 管理」を、より小さなスコープに分解するというものです。
しかしこの変更によって、.connection が返す connection のライフサイクルが曖昧になるケースが生まれました。
具体的には、with_connection の中で .connection を呼び出した場合でも、ブロック終了時に connection が pool に返却された扱いになり得る一方で、
コード上ではその connection を参照し続けられてしまう、という状態不整合が起こり得たのです。
conn = nil
ActiveRecord::Base.with_connection do
conn = ActiveRecord::Base.connection
end
# pool: 「connection は返却された」
# 別の場所・別スレッド
ActiveRecord::Base.with_connection do |other|
# pool: 「じゃあ同じ connection を渡そう」
# conn には 最初の connection への参照が残っている
end
このように、「コード上では connection を保持しているが、pool 側では既に返却済みとして扱われている」という状態は、同じ connection が複数箇所で使われてしまう危険性がありました。
#51192 では下記のような危険性があると説明されています。
- .connection の戻り値がスコープ外まで保持され、pool の管理と実際の利用が食い違う可能性がある
- segfault やデータ漏洩といった深刻な問題が起こり得る
connection の意味が変わった
上記の問題を受けて、Active Record 側で修正が行われました。
具体的には.connection が 常に permanently leased connection を返す ように変更されています。
これは、.connection が「必要に応じて connection を取得する API」ではなく、
「このスレッド(または Fiber)に紐づいた connection を明示的に占有する API」という意味を持つようになりました。
with_connection のような「一時的に借りて返す」API が導入されたことで、with_connection 内で利用する .connection まで一時的なものとして扱われてしまうと、
connection の管理責任が曖昧になり、安全でない状態を生みかねません。
そのため、.connection を呼んだ時点でその connection を permanently leased として扱う、という挙動に統一されています。
lease_connection が追加された理由
この意味のズレを明確にするために追加されたのが .lease_connection です。
「この API は leased connection を返す」ということを名前で明示する必要がありました。
.connection が内部的に leased connection を返すのであれば、それをそのまま表現した名前にする方が分かりやすい、という判断です。
そのため、.connection は .lease_connection にリネームされ、旧メソッド名である .connection は互換性のために残されています。
Deprecation
ここで、.connection が deprecated なのかどうかを整理します。
まず、ActiveRecord::ConnectionAdapters::ConnectionPool#connection は deprecated とされています。
今後は同クラスの #lease_connection を使用することが前提です。
一方で、ActiveRecord::Base.connection は soft deprecation の扱いになっています。
- warning は表示されない
- 削除時期は未定
- 今後は
lease_connectionを使う想定
すぐ使えなくなるという状態ではありませんが、新しいコードで積極的に使う API ではなくなっているという位置づけになります。
refs: Deprecate ActiveRecord::Base.connection in favor of .lease_connection
さいごに
AnnotateRb の PR を見たとき、最初は「なぜ connection を置き換える必要があるのか」という程度の違和感でした。
しかし調べていくと、これは AnnotateRb 側の都合ではなく、Rails 側で .connection の意味そのものが明確に定義し直された結果であることが分かりました。
.lease_connection を使う、という変更は、単なるメソッド名の置き換えではなく、Rails が意図する connection 管理の前提に合わせる、という意味を持っていそうです。
この PR を見なかったら何も知らずに connection を使っていたので、良い経験になりました。
明日の フィヨルドブートキャンプ Advent Calendar 2025 は lef さんです!