Le problème de fuite mémoire de Copilot
(stevenharman.net)Récapitulatif du processus de résolution d’une fuite mémoire liée à ActiveSupport::Notifications
-
Situation dans laquelle la fuite mémoire s’est produite
- À partir d’un certain moment, l’utilisation mémoire du Dyno
weba commencé à augmenter anormalement - Le pager s’est mis à sonner, laissant penser à une fuite mémoire
- À partir d’un certain moment, l’utilisation mémoire du Dyno
-
Réponse immédiate
- Sur Heroku, lorsqu’une fuite mémoire est suspectée, redémarrer le Dyno permet une résolution temporaire
- Redémarrer selon le cycle de déploiement habituel ou redémarrer manuellement les Dyno proches de la limite mémoire
-
Examen du code suspect pour identifier la cause
- Revue des changements de code déployés juste avant le pic de mémoire
- Déploiement un par un de plusieurs morceaux de code suspectés afin de vérifier si la fuite mémoire se reproduisait
- Comme aucun code ne semblait en cause, les changements d’outillage ont aussi été annulés et vérifiés. Mais la fuite mémoire persistait
-
Analyse du schéma d’augmentation mémoire
- La fuite ne se produisait que sur les Dyno
web. Les Dyno Sidekiq et Delayed::Job étaient normaux - Tous les Dyno
webne fuyaient pas en permanence. Après quelques heures d’usage normal, une ou deux instances, ou parfois toutes, commençaient à fuir - Il a été suspecté qu’elle était déclenchée par un certain type de trafic plutôt que par le volume de trafic
- Tous les workers Puma d’un Dyno n’étaient pas touchés : un petit nombre de workers utilisait l’essentiel de la mémoire totale
- La fuite ne se produisait que sur les Dyno
-
Collecte et analyse d’un heap dump
- Utilisation de
rbtracepour collecter un heap dump du processus Ruby en cours de fuite- Connexion en SSH au dyno en fuite avec
heroku ps:exec - Sélection, via la commande
ps, du processus worker Ruby consommant le plus de mémoire - Attachement à ce pid avec
rbtrace, puis démarrage du traçage des allocations mémoire (ObjectSpace.trace_object_allocations_start) - Collecte du heap dump avec
ObjectSpace.dump_all. Compression en gzip si la taille est importante - Récupération locale du fichier dump avec
heroku ps:copy
- Connexion en SSH au dyno en fuite avec
- Utilisation de
reappour visualiser le heap dump sous forme de flamegraph- Découverte d’un Thread référençant 1,9 Go de mémoire et, en dessous, d’un Array référençant 32 067 objets
- Utilisation de
sheappour explorer les objets suspects- Il s’est avéré que ce Thread était un worker thread de Puma
- Un objet
ActiveSupport::SubscriberQueueRegistryréférençait unHash, sous lequel se trouvaient des objetsStringetArray - Dans l’
Arrayproblématique, plus de 32 000 objetsActiveSupport::Notifications::Events’étaient accumulés
- Utilisation de
-
Déduction sur la cause
- Il a été supposé que des objets
EventdeActiveSupport::Notificationss’accumulaient à tort dans le tableau#children - Si une erreur se produit dans le bloc
ActiveSupport::Notifications.instrument, l’Eventcorrespondant ne serait pas retiré de#children, provoquant ainsi la fuite mémoire
- Il a été supposé que des objets
-
Reproduction en local
- Envoi en local d’une requête avec le path et les paramètres suspects observés en production
- Confirmation de la survenue de
URI::InvalidURIErroravec une500 Internal Server Error - Confirmation que l’utilisation mémoire du dyno de production ayant reçu cette requête augmentait brutalement
-
Analyse détaillée de la cause
- Il existait dans Rails 7.1 un bug corrigé lié à
Event#childrendeActiveSupport::Notifications - À cela s’ajoutait un bug du gem Bugsnag : lors du nettoyage de l’URL de requête,
URI.parselevaitURI::InvalidURIError, ce qui déclenchait la fuite mémoire - Comme l’erreur levée dans le bloc
ActiveSupport::Notifications.subscriben’était pas interceptée, l’Eventconcerné n’était pas retiré du tableau#childrenet continuait à s’y accumuler
- Il existait dans Rails 7.1 un bug corrigé lié à
-
Solution
- Court terme : mettre à niveau la version du gem Bugsnag afin qu’il ne lève pas d’erreur même si
URI::InvalidURIErrorse produit - Long terme : mettre à niveau vers Rails 7.x, où le bug de
ActiveSupport::Notificationsest corrigé
- Court terme : mettre à niveau la version du gem Bugsnag afin qu’il ne lève pas d’erreur même si
L’avis de GN⁺
- Le processus de détection du problème puis d’identification méthodique de sa cause est impressionnant. L’article résume très bien les analyses de base à tenter lorsqu’on soupçonne une fuite mémoire
- Il semble qu’un grand nombre d’outils open source pour collecter, visualiser et analyser les heap dumps Ruby (
rbtrace,reap,sheap, etc.) soient activement développés. Même en dehors de Ruby, il est important de connaître les outils d’analyse mémoire utiles à chaque langage et de savoir les appliquer aux problèmes rencontrés - En pratique, la cause d’une fuite mémoire est souvent un bug dans une bibliothèque ou un framework utilisé, mais on n’a pas toujours les moyens d’analyser puis de corriger ce bug soi-même avant de déployer. Il est donc important d’appliquer au plus vite une solution de contournement. Fournir une alternative praticable avec un bug report est aussi une bonne approche
- Il est appréciable que l’auteur ne se soit pas contenté de corriger la fuite mémoire, mais ait cherché en profondeur la root cause du problème. Cette rigueur d’analyse, qui consiste à examiner attentivement le code interne du framework pour remonter jusqu’à la cause fondamentale, est une qualité importante pour les développeurs
- Au final, la cause de la fuite mémoire provenait d’une simple mise à jour de bibliothèque qui semblait au départ sans rapport. C’est un bon exemple de l’importance de la gestion des dépendances et du suivi des changements. Même une modification mineure mérite une analyse d’impact attentive et une surveillance après déploiement
1 commentaires
Commentaire Hacker News
Cela peut être résolu par une formation d’ingénierie, sans peur de la gestion manuelle de la mémoire
Cas d’une perte de 5 millions de dollars causée par une fuite mémoire
Outils de débogage des fuites mémoire et pistes de résolution
Éloge du style d’écriture