Rails 7.2 Connection Pool Changes May Slow Down Your App
After upgrading to Rails 7.2, you might notice your app is a bit slower. Not dramatically, but measurably—around 5-6% in real-world benchmarks. It comes down to how ActiveRecord now manages database connections.
The Change
In Rails 7.1, each thread held onto its database connection for the duration of a request.
Rails 7.2 flipped this: connections are now checked out for each query and returned immediately after. The goal was better connection sharing in multi-threaded environments where connections are scarce.
The change was introduced in PR #50793, replacing ActiveRecord::Base.connection with ActiveRecord::Base.with_connection for temporary access and lease_connection for longer holds.
Why This Causes Slowdowns
The check-in/check-out cycle isn’t free. Every time a connection returns to the pool, checkin callbacks run and the callback system allocates objects. A request with 10 queries now does 10 check-ins instead of zero.
Benchmarks from issue #55728 show the impact:
Rails 7.1: 98.55 req/sec
Rails 7.2: 92.45 req/sec (6% slower)
Rails 7.2 + workaround: 99.99 req/sec (back to normal)
You’ll feel this most if you’re running single-threaded workers like Unicorn, have a local or low-latency database, and execute many queries per request.
The Fix
For single-threaded workers where connection sharing doesn’t matter, lease a connection at the start of each request:
class ApplicationController < ActionController::Base
before_action :lease_database_connection
private
def lease_database_connection
ActiveRecord::Base.lease_connection
end
end
This holds the connection for the entire request, skipping the check-in/check-out dance.
For background jobs, same idea:
class ApplicationJob < ActiveJob::Base
before_perform do
ActiveRecord::Base.lease_connection
end
end
When to Skip This
If you’re running multi-threaded Puma with more threads than database connections, the new behavior is actually what you want—it lets threads share connections. Same goes for long-running requests that don’t touch the database much. Holding connections you don’t need starves other threads.
Under the Hood
Most of the overhead is in run_callbacks(:checkin). Even with no custom callbacks, Rails runs the full callback machinery—object allocations, method dispatch, the works.
Jean Boussier (byroot) has been working on optimizations for Rails main, but these won’t be backported to 7.2 since they’re performance tweaks rather than bug fixes.
Measuring Your App
Before changing anything, check if this even affects you:
# config/initializers/connection_benchmark.rb
if Rails.env.development?
ActiveSupport::Notifications.subscribe("sql.active_record") do |*args|
event = ActiveSupport::Notifications::Event.new(*args)
Rails.logger.debug "SQL (#{event.duration.round(2)}ms)"
end
end
Compare request times with and without lease_connection. If you don’t see a difference, don’t bother with the fix.
For Unicorn or single-threaded Puma deployments seeing slower responses after upgrading, give lease_connection a try. It’s safe and brings you back to 7.1 behavior.
Follow issue #55728 if you want to track the ongoing optimization work.