El problema de fuga de memoria de Copilot
(stevenharman.net)Resumen del proceso para resolver una fuga de memoria relacionada con ActiveSupport::Notifications
-
Situación en la que ocurrió la fuga de memoria
- A partir de cierto momento, el uso de memoria del Dyno
webempezó a aumentar de forma anormal - Empezó a sonar el pager y apareció una situación que parecía una fuga de memoria
- A partir de cierto momento, el uso de memoria del Dyno
-
Respuesta inmediata
- En Heroku, si se sospecha de una fuga de memoria, reiniciar el Dyno puede servir como solución temporal
- Reiniciar siguiendo el ciclo normal de deploy o reiniciar manualmente los Dynos cercanos al límite de memoria
-
Revisión del código sospechoso para identificar la causa
- Revisar los cambios de código desplegados justo antes del pico de memoria
- Desplegar uno por uno algunos fragmentos de código sospechosos para comprobar si se produce la fuga de memoria
- Como no había código que pareciera ser la causa, también se revirtieron cambios en tooling para verificarlo. Aun así, la fuga de memoria continuó
-
Análisis del patrón de aumento de memoria
- La fuga solo ocurría en los Dynos
web. Los Dynos de Sidekiq y Delayed::Job funcionaban con normalidad - No todos los Dynos
webpresentaban fuga siempre. Tras varias horas de uso normal, uno o dos, o incluso todos los Dynos, comenzaban a fugar memoria - Se sospechó que la causa no era el volumen de tráfico, sino cierto tipo de tráfico específico
- La fuga no ocurría en todos los workers de Puma dentro del Dyno; unos pocos workers estaban usando la mayor parte de la memoria total
- La fuga solo ocurría en los Dynos
-
Recolección y análisis del heap dump
- Se usó
rbtracepara recolectar un heap dump del proceso Ruby en el que estaba ocurriendo la fuga- Con
heroku ps:exec, conectarse por ssh al dyno con la fuga - Con el comando
ps, seleccionar el proceso worker de Ruby que más memoria estaba usando - Adjuntarse a ese pid con
rbtracey comenzar a rastrear la asignación de memoria (ObjectSpace.trace_object_allocations_start) - Recolectar el heap dump con
ObjectSpace.dump_all. Si el tamaño es grande, comprimir con gzip - Traer el archivo dump a la máquina local con
heroku ps:copy
- Con
- Se usó
reappara visualizar el heap dump como flamegraph- Se encontró un Thread que referenciaba 1.9GB de memoria y, debajo de él, un Array que referenciaba 32,067 objetos
- Se usó
sheappara explorar los objetos sospechosos- Se descubrió que ese Thread era un worker thread de Puma
- Un objeto
ActiveSupport::SubscriberQueueRegistryestaba referenciando unHash, y debajo había objetosStringyArray - En el
Arrayproblemático se estaban acumulando más de 32,000 objetosActiveSupport::Notifications::Event
- Se usó
-
Inferencia sobre la causa
- Se supuso que los objetos
EventdeActiveSupport::Notificationsse estaban acumulando incorrectamente en el array#children - Se estimó que, si ocurría un error dentro del bloque de
ActiveSupport::Notifications.instrument, eseEventno se eliminaba de#childreny quedaba ahí, provocando la fuga de memoria
- Se supuso que los objetos
-
Reproducción local
- Se enviaron solicitudes localmente usando el request path y los parámetros sospechosos encontrados en producción
- Se confirmó la aparición de
URI::InvalidURIErrorjunto con500 Internal Server Error - También se confirmó que el uso de memoria del dyno de producción que recibió esa solicitud aumentaba de forma abrupta
-
Análisis detallado de la causa
- Existía un bug relacionado con
Event#childrendeActiveSupport::Notificationsque fue corregido en Rails 7.1 - Además, coincidió con un bug en la gema Bugsnag que, durante el proceso de limpiar la URL de la request, lanzaba
URI::InvalidURIErrorduranteURI.parse, y eso detonaba la fuga de memoria - Como el error lanzado dentro del bloque de
ActiveSupport::Notifications.subscribeno era capturado, eseEventno se eliminaba del array#childreny seguía acumulándose, causando la fuga de memoria
- Existía un bug relacionado con
-
Solución
- Corto plazo: actualizar la versión de la gema Bugsnag para que no lance un error incluso si ocurre
URI::InvalidURIError - Largo plazo: actualizar a Rails 7.x, donde ya está corregido el bug de
ActiveSupport::Notifications
- Corto plazo: actualizar la versión de la gema Bugsnag para que no lance un error incluso si ocurre
Opinión de GN⁺
- Resulta muy llamativo el proceso para detectar el problema e ir identificando la causa de forma sistemática. También resume muy bien un flujo básico de análisis que vale la pena intentar cuando se sospecha de una fuga de memoria
- Parece que se están desarrollando activamente varias herramientas open source para recolectar, visualizar y analizar heap dumps de Ruby, como
rbtrace,reapysheap. Incluso fuera de Ruby, parece importante conocer herramientas útiles de análisis de memoria por lenguaje y saber aplicarlas a problemas reales - En muchos casos, la causa de una fuga de memoria termina siendo un bug en alguna librería o framework usado por el proyecto, pero normalmente no es fácil tener las condiciones para analizarlo, corregirlo y desplegarlo directamente. Por eso, es importante aplicar cuanto antes una forma de mitigación práctica. Acompañar el bug report con alternativas viables también es una buena estrategia
- También fue valioso que no se quedara solo en resolver la fuga de memoria, sino que profundizara hasta llegar al root cause del problema. Esa actitud analítica de revisar con cuidado el código interno del framework y rastrear la causa fundamental parece muy necesaria para cualquier desarrollador
- Al final, la causa de la fuga de memoria estaba en una actualización menor de una librería que al principio no parecía tener ninguna relación. Es un caso que muestra la importancia de la gestión de dependencias y del seguimiento de cambios. Incluso los cambios pequeños requieren analizar su impacto con cuidado y monitorear después del deploy
1 comentarios
Comentario de Hacker News
Se puede resolver con entrenamiento de ingeniería sin miedo a la gestión manual de memoria
Un caso de pérdida de 5 millones de dólares por una fuga de memoria
Herramientas para depurar fugas de memoria y formas de resolverlas
Elogios al estilo de escritura