Sentry-javascript: Fugas de memoria de @ sentry / nodo en el nodo 10.13.0

Creado en 22 nov. 2018  ·  50Comentarios  ·  Fuente: getsentry/sentry-javascript

Paquete + Versión

  • [x] @sentry/browser
  • [x] @sentry/node

Versión:

4.3.4

Descripción

Intenté integrar sentry en el proyecto ah next.js. Lo probé usando esta plantilla https://github.com/sheerun/next.js/tree/with-sentry-fix/examples/with-sentry y descubrí lo que parece ser una pérdida de memoria en Sentry. Si revisa este proyecto y agrega memwatch

if (!dev) {
  memwatch.on("leak", info => {
    console.log("memwatch::leak");
    console.error(info);
  });

  memwatch.on("stats", stats => {
    console.log("memwatch::stats");
    console.error(Util.inspect(stats, true, null));
  });
}

y bombardear el servidor con solicitudes, solicité los siguientes recursos:

    "/",
    "/_next/static/r1zovjaZ1TujaA0hJEp91/pages/_app.js",
    "/_next/static/r1zovjaZ1TujaA0hJEp91/pages/_error.js",
    "/_next/static/r1zovjaZ1TujaA0hJEp91/pages/index.js",
    "/_next/static/runtime/main-1eaa6d1d0c8e7d048efd.js",
    "/_next/static/chunks/commons.b34d260fee0c4a698139.js",
    "/_next/static/runtime/webpack-42652fa8b82c329c0559.js"

Con esto, el uso de la memoria crece sin cesar para mí. Tan pronto como elimino la solicitud y el errorHandler de server.js, la pérdida de memoria se detiene. Entonces parece estar conectado a esos 2

In Progress Bug

Comentario más útil

@michalkvasnicak Investigué esto y no es directamente causado por Sentry.

Nuestro transporte @sentry/node depende de https-proxy-agent , que depende de agent-base que requiere su archivo patch-core.js _y_ esto es lo que crea una fuga: encogerse de hombros:

https://github.com/TooTallNate/node-agent-base/issues/22

Puede verificar esto fácilmente copiando su contenido en su prueba y ejecutándolo con estadísticas de pila:

const url = require("url");
const https = require("https");

https.request = (function(request) {
  return function(_options, cb) {
    let options;
    if (typeof _options === "string") {
      options = url.parse(_options);
    } else {
      options = Object.assign({}, _options);
    }
    if (null == options.port) {
      options.port = 443;
    }
    options.secureEndpoint = true;
    return request.call(https, options, cb);
  };
})(https.request);

https.get = function(options, cb) {
  const req = https.request(options, cb);
  req.end();
  return req;
};

it("works", () => {
  expect(true).toBe(true);
});

Probablemente tendremos que bifurcarlo o escribir una solución.

Todos 50 comentarios

@abraxxas ¿ pueden ayudarme a reproducir este problema? ¿Cómo está provocando exactamente esta fuga de memorias?
Probé tu descripción sin suerte, solo produce eventos de estadísticas, nunca filtre uno.

@kamilogorek Gracias por tu respuesta. Lo que hice fue lo siguiente. Revisé este ejemplo https://github.com/sheerun/next.js/tree/with-sentry-fix/examples/with-sentry y agregué memwatch al server.js

if (!dev) {
  memwatch.on("leak", info => {
    console.log("memwatch::leak");
    console.error(info);
  });

  memwatch.on("stats", stats => {
    console.log("memwatch::stats");
    console.error(Util.inspect(stats, true, null));
  });
}

luego ejecuté el ejemplo usando el nodo 10.x (con 8.xi no observó problemas de memoria) y solicité los siguientes recursos usando nuestro kit de pruebas de gatling:

    "/",
    "/_next/static/r1zovjaZ1TujaA0hJEp91/pages/_app.js",
    "/_next/static/r1zovjaZ1TujaA0hJEp91/pages/_error.js",
    "/_next/static/r1zovjaZ1TujaA0hJEp91/pages/index.js",
    "/_next/static/runtime/main-1eaa6d1d0c8e7d048efd.js",
    "/_next/static/chunks/commons.b34d260fee0c4a698139.js",
    "/_next/static/runtime/webpack-42652fa8b82c329c0559.js"

(tenga en cuenta que los hash pueden cambiar para usted)

pero debería poder lograr los mismos resultados con este enfoque muy simple https://www.simonholywell.com/post/2015/06/parallel-benchmark-many-urls-with-apachebench/

después de unos pocos miles de solicitudes, nuestro uso de memoria fue de casi 1 GB y nunca se redujo, incluso en inactivo. Tan pronto como elimino la solicitud y el errorHandler de server.js, la pérdida de memoria se detiene. Entonces parece estar conectado a esos 2. ¿Quizás tuvo muy pocas solicitudes o usó el nodo 8.x?

@abraxxas confirmado. El nodo 10 + ~ 300req para cada recurso hace el trabajo. Investigaré más. ¡Gracias!

@kamilogorek interesante , pero si miras el enorme tamaño de tu
reproducción, verá que se mantiene alrededor de 20 MB sin los controladores
pero aumenta rápidamente con ellos.

Creo que la advertencia de pérdida de memoria sin los controladores ocurre solo porque
debido a las solicitudes hay un aumento constante de la memoria.

Todavía existe una gran diferencia en el uso de memoria entre la versión con
centinela y fuera.

El jueves 29 de noviembre de 2018 a las 12:45, Kamil Ogórek < [email protected] escribió:

@abraxxas https://github.com/abraxxas lo reproduje con éxito,
sin embargo, parece que el servidor todavía filtra objetos de solicitud por sí solo,
incluso sin los manejadores Sentry.

https://streamable.com/bad9j

La tasa de crecimiento es un poco mayor, ya que adjuntamos dominio y nuestro propio alcance
objetar a la solicitud, pero GC lo utilizaría junto con la solicitud.

-
Estás recibiendo esto porque te mencionaron.
Responda a este correo electrónico directamente, véalo en GitHub
https://github.com/getsentry/sentry-javascript/issues/1762#issuecomment-442804709 ,
o silenciar el hilo
https://github.com/notifications/unsubscribe-auth/AIbrNlgPjPd5Jra1aahR-Dthf7XvbCexks5uz8jjgaJpZM4YvOA2
.

@abraxxas ignora mi comentario anterior (el que

Parece ser el problema en el propio núcleo de Node. Consulte este comentario https://github.com/getsentry/sentry-javascript/issues/1762#issuecomment -444126990

@kamilogorek ¿Ha tenido la oportunidad de investigar esto todavía? Nos está causando una gran pérdida de memoria. Después de mirar el montón, esta línea parece que podría ser la raíz del problema:

https://github.com/getsentry/sentry-javascript/blob/c27e1e32d88cc03c8474fcb1e12d5c9a2055a150/packages/node/src/handlers.ts#L233

El inspector mostró miles de entradas en la lista eventProcessors
image

No tengo ningún contexto sobre cómo se diseñan las cosas, pero hemos notado que las solicitudes no tienen el alcance correcto y están dando los metadatos incorrectos (ver # 1773) por lo que parece que todo se está administrando en el estado global y no se limpia cuando un finaliza la solicitud

@abraxxas @tpbowden hay un problema con la filtración del módulo de dominio en el núcleo de Node. Seguiremos monitoreándolo e intentaremos encontrar una solución temporal antes de que se solucione en el núcleo. Problema relacionado: https://github.com/nodejs/node/issues/23862

@kamilogorek ¿Tiene alguna idea para una solución temporal o una solución temporal para esto? El progreso en el problema del nodo parece bastante lento

Actualmente estamos usando PM2 para reiniciar los procesos de Node.js cuando la memoria alcanza un cierto umbral. https://pm2.io/doc/en/runtime/features/memory-limit/#max -memory -reshold-auto-reload

Uso de laboratorio para pruebas unitarias. Las fugas aún están presentes. Sé que las fugas pueden ser dolorosas de depurar. ¿Existe una ETA para solucionarlo?

1 pruebas completas
Duración de la prueba: 1832 ms
Se detectaron las siguientes filtraciones: __ extensions, __assign, __rest, __decorate, __param, __metadata, __awaiter, __generator, __exportStar, __values, __read, __spread, __await, __asyncGenerator, __asyncDeleblate__Valor, __asyncDelegator__Valor, __asyncDelegator__

npm ERR! código ELIFECYCLE
npm ERR! errno 1
npm ERR! [email protected] prueba: lab build/test
npm ERR! Estado de salida 1
npm ERR!
npm ERR! Falló en el script de prueba
npm ERR! Probablemente esto no sea un problema con npm. Es probable que haya una salida de registro adicional arriba.

npm ERR! Puede encontrar un registro completo de esta ejecución en:
npm ERR! /Users/sunknudsen/.npm/_logs/2019-02-13T14_41_28_595Z-debug.log

@sunknudsen El problema está realmente solucionado en NodeJS, consulte https://github.com/nodejs/node/issues/23862 y https://github.com/nodejs/node/pull/25993. Probablemente necesitemos esperar un lanzamiento.

@garthenweb ¿Afecta esto a @sentry/node debido a cómo se desarrolló el paquete? Uno de los proyectos que estoy desarrollando en hapi (y se basa en muchas otras dependencias) no produce fugas (al menos no las detecta el laboratorio ).

@sunknudsen Más o menos. Es la combinación del paquete de dominio (obsoleto) y promesas hasta donde yo tengo entendido. Ver https://github.com/getsentry/sentry-javascript/blob/master/packages/node/src/handlers.ts#L233

En mi caso, acabo de eliminar el middlewares centinela de mi servidor (express) para solucionarlo.

Esto no es un problema en el nodo, pero en Sentry, ya que deshabilitar los controladores Sentry soluciona el problema;

image

@MartijnHols Actualmente estamos trabajando en una versión importante que debería reducir significativamente la huella de memoria de nuestro SDK. Si te sientes aventurero, puedes probarlo https://github.com/getsentry/sentry-javascript/pull/1919

@HazAT Gracias, lo instalé en producción anoche (a las 23:10 en el gráfico) y volví a habilitar los controladores con los resultados a continuación. Normalmente hay un ligero aumento en el uso de la CPU alrededor de las 23: 00-24: 00 (como puede ver en el día anterior), pero esto parecía mayor. El uso estándar de la CPU también es mucho más complicado que sin los controladores. No estoy seguro de si esto se debe a los cambios en la nueva versión o si se están habilitando los controladores. Intentaré volver a desactivar los controladores en unas horas. Se detectan alrededor de 2,5 errores por hora.

image

@MartijnHols ¡ Gracias por probarlo!

Dos cosas a tener en cuenta, la solución de pérdida de memoria para dominios en el nodo aterrizó en el nodo recientemente en 11.10 .
Además, tuvimos que anular la publicación de 5.0.0-beta1 porque se etiquetó por error como latest , 5.0.0-rc.1 ahora es la última versión de next .
Por favor, intente 5.0.0-rc.1 . Hicimos un pequeño cambio en la forma en que los eventos se ponen en cola que deberían mejorar mucho la carga / memoria.

La actualización al nodo 11.12 parece haber estabilizado la memoria y el uso de la CPU. Parece que ahora no hay ninguna diferencia discernible en el uso de recursos cuando se compara con tener los controladores deshabilitados, tal vez incluso un poco mejor. Parece que también detecta errores con toda la información que necesito (podría tener más "migas de pan" de consola, lo cual es bueno). No estoy seguro de qué más puedo verificar para 5.0.0. Te avisaré si tengo algún problema, pero probablemente no.

LGTM. ¡Gracias!

Estaría feliz de probar esto también. @HazAT , ¿sabe si la corrección en 11.10 ya se ha transferido a la versión LTS activa 10.x ?

@adriaanmeuris He leído que alguien preguntó si se 10.x , no estoy seguro de si lo harán.
ref: https://github.com/nodejs/node/pull/25993#issuecomment -463957701

Resolví el problema creando el cliente y el alcance manualmente en un middleware express

Creé un archivo services/sentry que exporta una función

import {
  NodeClient as SentryClient, Hub, Integrations, Scope,
} from '@sentry/node';
import config from 'config';

const sentryClient = new SentryClient({
  ...config.sentry,
  frameContextLines: 0,
  integrations: [new Integrations.RewriteFrames()],
});

export default () => {
  const scope = new Scope();
  const client = new Hub(sentryClient, scope);
  return Object.freeze({ client, scope });
};

y en un middleware guardo el cliente / alcance centinela en el objeto de solicitud como este

app.use((req, res, next) => {
  req.sentry = getSentry();
  req.sentry.scope.setTag('requestId', req.requestId);
  req.sentry.scope.setExtra('More info', 'XXXXXX');
  next();
});
....
// and use the express error handler
app.use((err, req, res, next) => {
  const code = err.code || 500;
  res.status(code).json({
    code,
    error: err.message,
  });
  if (code >= 500) {
    req.sentry.client.captureException(err);
  }
  next();
});

Con esto parece que la fuga de memoria está arreglada

image

@couds Esta implementación es realmente buena, aunque hay una cosa que debes considerar.

Por cada solicitud, obtendrá un nuevo cliente y no detectamos ningún error global / migas de pan automáticas (o cualquier otra cosa que hagan las integraciones predeterminadas).

@HazAT Gracias, lo sé, pero es un pequeño precio a pagar. para resolver esa gran pérdida de memoria, pero para minimizar esta pérdida, hice algunas cosas.

  1. Intento manejar todas las excepciones. Prefiero no confiar en los eventos unCoughException / promise.
  2. Para las migas de pan, las agrego manualmente siempre que tenga acceso al objeto req.
  3. Sube los mapas de origen a Sentry con @sentry/webpack-plugin
  4. Agregue etiquetas / extra en cada solicitud que ayudará a identificar el problema

Una cosa más, olvidé pegar una etiqueta más que estoy agregando que es la transacción (para simular el controlador predeterminado)

req.sentry.scope.setTag('transaction', `${req.method}|${req.route ? req.route.path : req.path}`);

Espero que esto ayude a alguien, Sentry es una gran herramienta e hice lo que pude para evitar quitarla de mi pila.

@HazAT @kamilogorek todavía estamos viendo el gran crecimiento de la memoria en [email protected] y [email protected] - ¿estás seguro de que este parche de nodejs lo solucionó?

@tpbowden ¿Puede proporcionar una reproducción O al menos decir cómo se ve su código?
Además, ¿está utilizando otros complementos relacionados con Sentry?

@HazAT He intentado reproducir, pero está siendo causado por un servidor bastante complejo con mucho middleware (el lado del servidor renderiza React). Con el middleware Sentry instalado, estamos alcanzando un gran crecimiento de la memoria (con una carga pesada, puede aumentar en ~ 10 MB / segundo). Cuando eliminamos Sentry.Handlers.requestHandler() middleware de nuestro servidor, la memoria es constante en ~ 200 MB

No estamos usando ningún complemento, solo @sentry/node . ¿Puedes pensar en algo que pueda intentar para ayudar a reproducir esto?

@HazAT Parece que está relacionado con empaquetar Sentry usando webpack en el servidor; solo ocurre cuando la aplicación ha sido creada por Webpack. Agregar @sentry/node a externals solucionó nuestro problema de memoria. ¿Alguna idea de por qué sucede esto?

La filtración aún se puede reproducir en el nodo 11.14.0 usando @ sentry / node 5.1.0

Para nosotros, la filtración se debe a la interacción entre @ sentry / node e i18next-express-middleware. Nuestra aplicación express se parece a https://github.com/i18next/react-i18next/blob/master/example/razzle-ssr/src/server.js.

Si ponemos .use(Sentry.Handlers.requestHandler()); encima de .use(i18nextMiddleware.handle(i18n)) , obtenemos una pérdida de memoria. Si ponemos centinela debajo, entonces no tenemos una fuga.

Tenemos el mismo problema. Lo probé con node 10.15.3 y 11.14.0 . Aquí hay un repositorio mínimo para reproducir el problema https://github.com/michalkvasnicak/sentry-memory-leak-reproduction. Simplemente ejecute yarn test o yarn test:watch que informará el uso del montón. Siempre aumenta.

yarn test
image

yarn test:watch

image

image

@michalkvasnicak Gracias por tomarse el tiempo para crear un ejemplo, aunque tengo algunas preguntas.

Realmente no está probando nada con respecto al SDK

const Sentry = require('@sentry/node');

it('works', () => {
  expect(true).toBe(true);
});

Necesita el paquete, pero eso es todo.
No estoy seguro ya que todavía no tengo experiencia con jest ejecutando pruebas de fugas, pero ¿qué se puede filtrar con solo requerir el paquete?
No estoy seguro, tal vez me esté perdiendo algo, pero esperaría que la memoria crezca al cargar un paquete nuevo.

Con respecto a lo que puede filtrarse, creamos algunas variables globales pero las necesitamos para rastrear el estado.

@HazAT sí, es extraño pero es suficiente para hacer que las pruebas tengan fugas, jest fallará si se detecta la fuga. Tenemos el mismo problema en nuestra base de código donde simplemente comentar la importación de @sentry/node resolvió el problema con la fuga.

@michalkvasnicak Investigué esto y no es directamente causado por Sentry.

Nuestro transporte @sentry/node depende de https-proxy-agent , que depende de agent-base que requiere su archivo patch-core.js _y_ esto es lo que crea una fuga: encogerse de hombros:

https://github.com/TooTallNate/node-agent-base/issues/22

Puede verificar esto fácilmente copiando su contenido en su prueba y ejecutándolo con estadísticas de pila:

const url = require("url");
const https = require("https");

https.request = (function(request) {
  return function(_options, cb) {
    let options;
    if (typeof _options === "string") {
      options = url.parse(_options);
    } else {
      options = Object.assign({}, _options);
    }
    if (null == options.port) {
      options.port = 443;
    }
    options.secureEndpoint = true;
    return request.call(https, options, cb);
  };
})(https.request);

https.get = function(options, cb) {
  const req = https.request(options, cb);
  req.end();
  return req;
};

it("works", () => {
  expect(true).toBe(true);
});

Probablemente tendremos que bifurcarlo o escribir una solución.

¿Alguna solución para este?

https://nodejs.org/en/blog/release/v10.16.0/

Se corrigieron algunas fugas de memoria, ¿alguien puede probarlo?

Tengo el mismo problema en el nodo 11.10, pero parece estar solucionado en el nodo 12.3.1

Hay un PR abierto para el problema agent-base : https://github.com/TooTallNate/node-agent-base/pull/25

Podemos bifurcar esto y anular la dependencia en las resoluciones de Yarn o encontrar una manera de fusionarlo.

Podría ser un poco fuera de tema, pero también podría causar algunas filtraciones de que no hay limpieza de dominio en Handlers.ts, solo domain.create?
El artículo mediano o SO sugiere que debería haber removeListeners y domain.exit. Pero no encontró una respuesta definitiva a eso.

ACTUALIZACIÓN: Ahora veo que los controladores están usando domain.run que llama internamente a domain.exit. Aún eliminar los oyentes / emisores puede marcar la diferencia, pero honestamente no tengo idea.

Actualicé a Node 12.4.0 y sigo viendo un mal comportamiento que parece estar relacionado con Sentry.

Aquí hay algunas ejecuciones del doctor de la clínica del nodo, hechas con la opción --autocannon durante 90 segundos.

Solo parece realmente tener fugas cuando los controladores de solicitudes están en su lugar. Si observa la parte inferior de los canales de GC en la carrera sin los controladores, todos están aproximadamente al mismo nivel (65-70 MB), donde la carrera con los controladores parece subir alrededor de 5 MB con cada ciclo de GC.

@ tstirrat15 esta solución aún no se ha publicado, por lo que probablemente sea por eso que todavía tiene este problema. ¿Puedes probar con el último maestro si esa es una opción?

@ tstirrat15 5.4.2 con la corrección incluida se ha lanzado, pruébalo :)

Aquí hay otra ejecución con v5.4.2 en su lugar. Todavía parece un poco goteante ...

GC siempre se activa correctamente y restaura la memoria a la línea de base. El aumento del uso de memoria se debe a las rutas de navegación recopiladas de los eventos y la cola de eventos, pero se detendrá en 100 rutas y no aumentará más. Sería bueno si pudiéramos ver un volcado de ~ 15-30 minutos y ver si la memoria máxima se detiene en algún momento.

Hmm ... suena bien. Llevaré este PR a producción y veré si el comportamiento cambia. ¡Gracias!

Parece que está relacionado con empaquetar Sentry usando webpack en el servidor; solo ocurre cuando la aplicación ha sido creada por Webpack. Agregar @ sentry / node a externos ha solucionado nuestro problema de memoria. ¿Alguna idea de por qué sucede esto?

@tpbowden Tienes razón en esto, tengo el mismo problema. Estaba ejecutando el SDK v5.15.0 y el nodo v12.3.1, y se suponía que ambos incluían todas las correcciones necesarias mencionadas aquí.

Estoy agrupando todas las dependencias dentro de mi paquete de servidor con webpack. De esta manera, puedo enviar una imagen de la ventana acoplable sin node_modules, pero algo está estropeando el SDK sentry y pierde memoria cuando se incluye de esta manera.

Puede ser un error causado por algún proceso de optimización. Mi conjetura es que probablemente sea más terso. Es probable que alguna optimización esté arruinando el uso del módulo de dominio, y el cierre de la devolución de llamada pasada a scope.addEventProcessor ya no se recolecta como basura, por lo que cada solicitud realizada pierde una buena cantidad de memoria.

También estoy usando razzle.js, que está un poco atrasado en las versiones webpack / terser, tal vez ya esté arreglado.

Esto ya no parece ser un error del lado del centinela. Continuaré investigando esto y abriré un problema cuando corresponda y mantendré este hilo actualizado.

Esto ya no parece ser un error del lado del centinela. Continuaré investigando esto y abriré un problema cuando corresponda y mantendré este hilo actualizado.

¡Manténganos informados, gracias!

@kamilogorek ¿Podría _eventProcessors dentro de la instancia de Scope? No lo encuentro. Parece que todas las solicitudes están agregando una devolución de llamada del procesador de eventos a esta matriz y nunca se eliminan. Si supiera cómo se supone que deben eliminarse, podría ayudarme a comprender mejor el error.

Screen Shot 2020-03-23 at 15 49 03

¿O tal vez es todo el alcance que se supone que es único y se recolecta basura para cada solicitud? Parece que cada solicitud obtiene la misma instancia de alcance 🤔

¡Decir ah! Creo que encontré algo.

Usamos dynamicRequire:
https://github.com/getsentry/sentry-javascript/blob/fd26d9fa273002502706b03fc1a9a46864cd8440/packages/hub/src/hub.ts#L465-L468

Pero cuando entro en el código dynamicRequire:
https://github.com/getsentry/sentry-javascript/blob/fd26d9fa273002502706b03fc1a9a46864cd8440/packages/utils/src/misc.ts#L28-L31

require no está definido en mod 🤯

¡Entonces entra en el bloque catch de la función getHubFromActiveDomain y en su lugar usa getHubFromCarrier() !

Dado que en mi configuración _todo_ está empaquetado por el paquete web, probablemente haya algunas suposiciones sobre el objeto mod que está roto por el paquete web. ¿Tiene una idea de cómo podría solucionarse esto? 🤔

Resumen

Usamos dynamicRequire:
Screen Shot 2020-03-24 at 12 05 04

mod.require no está definido:
Screen Shot 2020-03-24 at 12 20 01

Cómo se ve el objeto mod:
Screen Shot 2020-03-24 at 12 20 38

Terminamos usando getHubFromCarrier:
Screen Shot 2020-03-24 at 12 21 22

Parcheé manualmente el módulo Hub directamente en mi carpeta node_modules. Eliminé la línea usando dynamicRequire y solo agregué import domain from 'domain'; en la parte superior del archivo y ... ¡ahora funciona perfectamente! ¡No más fugas! 🎉

¿Quizás el truco dynamicRequire era necesario antes, pero ya no es necesario con las versiones más nuevas de webpack? 🤔

También intenté reemplazar:

const domain = dynamicRequire(module, 'domain');

con:

const domain = require('domain');

y también funciona bien. No sé cuál de esas dos soluciones preferiría.

¿Le gustaría que abra un PR con esta solución?

¿Fue útil esta página
0 / 5 - 0 calificaciones