Los cambios en el pool de conexiones de Rails 7.2 pueden ralentizar tu app
Después de actualizar a Rails 7.2, podrías notar que tu app está un poco más lenta. No dramáticamente, pero mediblemente—alrededor del 5-6% en benchmarks del mundo real. Todo se reduce a cómo ActiveRecord ahora gestiona las conexiones de base de datos.
El Cambio
En Rails 7.1, cada hilo mantenía su conexión de base de datos durante toda la duración de una petición.
Rails 7.2 cambió esto: las conexiones ahora se toman prestadas para cada consulta y se devuelven inmediatamente después. El objetivo era mejorar el uso compartido de conexiones en entornos multi-hilo donde las conexiones son escasas.
El cambio se introdujo en PR #50793, reemplazando ActiveRecord::Base.connection con ActiveRecord::Base.with_connection para acceso temporal y lease_connection para mantenerlas más tiempo.
Por Qué Esto Causa Ralentizaciones
El ciclo de tomar/devolver no es gratis. Cada vez que una conexión vuelve al pool, se ejecutan callbacks checkin y el sistema de callbacks asigna objetos. Una petición con 10 consultas ahora hace 10 check-ins en lugar de cero.
Los benchmarks del issue #55728 muestran el impacto:
Rails 7.1: 98.55 req/sec
Rails 7.2: 92.45 req/sec (6% más lento)
Rails 7.2 + workaround: 99.99 req/sec (vuelve a la normalidad)
Sentirás esto más si estás ejecutando workers de un solo hilo como Unicorn, tienes una base de datos local o de baja latencia, y ejecutas muchas consultas por petición.
La Solución
Para workers de un solo hilo donde el uso compartido de conexiones no importa, toma prestada una conexión al inicio de cada petición:
class ApplicationController < ActionController::Base
before_action :lease_database_connection
private
def lease_database_connection
ActiveRecord::Base.lease_connection
end
end
Esto mantiene la conexión durante toda la petición, evitando el ciclo de tomar/devolver.
Para trabajos en segundo plano, la misma idea:
class ApplicationJob < ActiveJob::Base
before_perform do
ActiveRecord::Base.lease_connection
end
end
Cuándo No Aplicar Esto
Si estás ejecutando Puma multi-hilo con más hilos que conexiones de base de datos, el nuevo comportamiento es realmente lo que quieres—permite que los hilos compartan conexiones. Lo mismo aplica para peticiones de larga duración que no tocan mucho la base de datos. Mantener conexiones que no necesitas priva a otros hilos.
Bajo el Capó
La mayor parte de la sobrecarga está en run_callbacks(:checkin). Incluso sin callbacks personalizados, Rails ejecuta toda la maquinaria de callbacks—asignación de objetos, dispatch de métodos, todo.
Jean Boussier (byroot) ha estado trabajando en optimizaciones para Rails main, pero estas no serán portadas a 7.2 ya que son ajustes de rendimiento en lugar de correcciones de bugs.
Midiendo Tu App
Antes de cambiar nada, verifica si esto te afecta:
# 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
Compara los tiempos de petición con y sin lease_connection. Si no ves diferencia, no te molestes con la solución.
Para despliegues con Unicorn o Puma de un solo hilo que experimentan respuestas más lentas después de actualizar, prueba lease_connection. Es seguro y te devuelve al comportamiento de 7.1.
Sigue el issue #55728 si quieres seguir el trabajo de optimización en curso.