Les changements du pool de connexions Rails 7.2 peuvent ralentir votre app
Après la mise à niveau vers Rails 7.2, vous pourriez remarquer que votre app est un peu plus lente. Pas dramatiquement, mais mesurабlement—environ 5-6% dans les benchmarks réels. Cela se résume à la façon dont ActiveRecord gère maintenant les connexions à la base de données.
Le Changement
Dans Rails 7.1, chaque thread conservait sa connexion à la base de données pendant toute la durée d’une requête.
Rails 7.2 a inversé cela : les connexions sont maintenant empruntées pour chaque requête et rendues immédiatement après. L’objectif était un meilleur partage des connexions dans les environnements multi-threads où les connexions sont rares.
Le changement a été introduit dans PR #50793, remplaçant ActiveRecord::Base.connection par ActiveRecord::Base.with_connection pour un accès temporaire et lease_connection pour des utilisations plus longues.
Pourquoi Cela Cause des Ralentissements
Le cycle d’emprunt/retour n’est pas gratuit. Chaque fois qu’une connexion retourne au pool, les callbacks checkin s’exécutent et le système de callbacks alloue des objets. Une requête avec 10 requêtes SQL fait maintenant 10 check-ins au lieu de zéro.
Les benchmarks du issue #55728 montrent l’impact :
Rails 7.1: 98.55 req/sec
Rails 7.2: 92.45 req/sec (6% plus lent)
Rails 7.2 + workaround: 99.99 req/sec (retour à la normale)
Vous ressentirez cela le plus si vous exécutez des workers mono-thread comme Unicorn, avez une base de données locale ou à faible latence, et exécutez de nombreuses requêtes par demande.
La Solution
Pour les workers mono-thread où le partage de connexions n’a pas d’importance, empruntez une connexion au début de chaque requête :
class ApplicationController < ActionController::Base
before_action :lease_database_connection
private
def lease_database_connection
ActiveRecord::Base.lease_connection
end
end
Cela maintient la connexion pendant toute la requête, évitant le cycle d’emprunt/retour.
Pour les jobs en arrière-plan, même idée :
class ApplicationJob < ActiveJob::Base
before_perform do
ActiveRecord::Base.lease_connection
end
end
Quand Ne Pas Appliquer Cela
Si vous exécutez Puma multi-thread avec plus de threads que de connexions à la base de données, le nouveau comportement est en fait ce que vous voulez—il permet aux threads de partager les connexions. Même chose pour les requêtes longues qui ne touchent pas beaucoup la base de données. Garder des connexions dont vous n’avez pas besoin prive les autres threads.
Sous le Capot
La majeure partie de la surcharge est dans run_callbacks(:checkin). Même sans callbacks personnalisés, Rails exécute toute la machinerie de callbacks—allocations d’objets, dispatch de méthodes, tout.
Jean Boussier (byroot) travaille sur des optimisations pour Rails main, mais celles-ci ne seront pas portées vers 7.2 car ce sont des ajustements de performance plutôt que des corrections de bugs.
Mesurer Votre App
Avant de changer quoi que ce soit, vérifiez si cela vous affecte :
# 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
Comparez les temps de requête avec et sans lease_connection. Si vous ne voyez pas de différence, ne vous embêtez pas avec la correction.
Pour les déploiements Unicorn ou Puma mono-thread constatant des réponses plus lentes après la mise à niveau, essayez lease_connection. C’est sûr et vous ramène au comportement de 7.1.
Suivez le issue #55728 si vous voulez suivre le travail d’optimisation en cours.