Suivez et déboguez vos requêtes SQL ActiveRecord avec des annotations

J’ai récemment découvert une fonctionnalité pratique introduite dans Rails 6. Cette fonctionnalité vous permet d’annoter vos requêtes SQL avec des commentaires directement intégrés dans la requête elle-même. Je sais, je suis un peu en retard à la fête, mais je soupçonne que je ne suis pas le seul, alors voici un récapitulatif rapide.
Dans notre application, nous avons un hash profondément imbriqué appelé public_data qui est souvent utilisé pour rendre le payload JSON d’un enregistrement, ainsi que ses associations, dans le contrôleur.
Ce hash public_data est construit à partir d’un mélange d’attributs et de méthodes de modèle. Cependant, il appelle également les méthodes public_data des modèles associés, qui peuvent, à leur tour, rappeler la public_data du modèle d’origine. Cette complexité circulaire a été un défi, et bien que nous ayons commencé à passer au gem Barley serializer pour une meilleure structure, nous comptons toujours sur cette méthode dans certaines parties de notre codebase pour la compatibilité ascendante.
Donc, lorsque @user.public_data est appelé dans le contrôleur, il déclenche souvent une longue chaîne de requêtes de base de données. Traquer où une relation spécifique est appelée - que ce soit dans User, Profile, ou Subscription - peut être intimidant.
Bien que les logs Rails soient utiles, ils nécessitent de trier la sortie pour corréler l’entrée de log avec la requête SQL. Cela devient encore plus délicat lors du débogage dans des environnements distants et de la recherche dans de vastes logs dans des outils comme New Relic ou CloudWatch.
Entrez ActiveRecord::Relation#annotate…
Cette simple méthode vous permet d’ajouter des commentaires à vos requêtes SQL, en intégrant des informations contextuelles directement dans la requête.
Voici comment cela fonctionne :
users = User.active.where(score: ..5).annotate("called from Admin::UsersController")
#> SELECT * FROM `users` WHERE `last_visit_at` >= '2024-11-10 16:53:05'
# AND `score` <= 5 /* called from Admin::UsersController */ ORDER BY `id` ASC
Cela fournit une indication claire de l’origine de la requête. Vous pouvez également inclure un contexte supplémentaire, tel que la locale actuelle, l’utilisateur ou l’ID de commande, en fonction de vos besoins.
Par exemple, ajouter une annotation à l’intérieur de la méthode public_data sur mon modèle Subscription facilite grandement le suivi de l’origine de la requête dans la chaîne.
Une autre excellente fonctionnalité est la possibilité d’empiler les annotations. Disons que nous ajoutons une annotation dans le scope active du modèle User :
class User < ApplicationRecord
scope :active, -> { where(last_visit_at: 1.week.ago..).annotate("user active in the past week") }
end
Appeler la même requête qu’auparavant générerait :
users = User.active.where(score: ..5).annotate("called from Admin::UsersController")
#> SELECT * FROM `users` WHERE `last_visit_at` >= '2024-11-10 16:53:05'
# AND `score` <= 5 /* user active in the past week */ /* called from Admin::UsersController */
# ORDER BY `id` ASC
Sympa, non ? Cette approche apporte plus de clarté et de structure à vos logs, les rendant plus faciles à interroger et à regrouper dans les visualiseurs de logs. De plus, ces annotations apparaissent dans vos logs de base de données, offrant un moyen transparent de les corréler avec les logs d’application.
Les applications possibles incluent :
- Suivi des requêtes dans les tâches en arrière-plan
- Balisage du contexte du contrôleur, des en-têtes ou de tout autre élément dans les requêtes
- Débogage des requêtes N+1 ou lentes
- Explicitation de la portée des requêtes
- Et tellement d’autres choses créatives !
Cela dit, les annotations s’accompagnent d’un léger compromis en termes de lisibilité des requêtes. Il est essentiel d’être délibéré sur quoi et quand logger, et de revoir périodiquement si les annotations restent pertinentes et précieuses.
Même si elles sont utilisées exclusivement pour le débogage local, les annotations SQL sont un excellent ajout à votre boîte à outils.
MoskitoHero