Sentry-javascript: @ Sentinela / nó vazamentos de memória no nó 10.13.0

Criado em 22 nov. 2018  ·  50Comentários  ·  Fonte: getsentry/sentry-javascript

Pacote + Versão

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

Versão:

4.3.4

Descrição

Tentei integrar o sentry no projeto ah next.js. Eu tentei usando este modelo https://github.com/sheerun/next.js/tree/with-sentry-fix/examples/with-sentry e descobri o que parece ser um vazamento de memória no sentinela. Se você verificar este projeto e adicionar 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));
  });
}

e bombardeio o servidor com solicitações, solicitei os seguintes 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"

Com isso, o uso da memória cresce infinitamente para mim. Assim que removo a solicitação e o errorHandler do server.js, o vazamento de memória para. Então, parece estar conectado a esses 2

In Progress Bug

Comentários muito úteis

@michalkvasnicak Eu investiguei isso e não é causado diretamente pelo Sentinela.

Nosso transporte @sentry/node depende de https-proxy-agent , que depende de agent-base que requer seu arquivo patch-core.js _and_ isso é o que cria um vazamento: encolher de ombros:

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

Você pode verificar isso facilmente copiando o conteúdo em seu teste e executando-o com estatísticas de heap:

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);
});

Provavelmente teremos que bifurcar ou escrever uma solução alternativa.

Todos 50 comentários

@abraxxas você pode me ajudar a reproduzir esse problema? Como exatamente você está provocando esse vazamento de memória?
Tentei sua descrição sem sorte, ela apenas produz eventos de estatísticas, nunca vaza nenhum.

@kamilogorek Obrigado pela sua resposta. O que eu fiz foi o seguinte. Eu verifiquei este exemplo https://github.com/sheerun/next.js/tree/with-sentry-fix/examples/with-sentry e adicionei memwatch ao 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));
  });
}

em seguida, executei o exemplo usando o nó 10.x (com 8.xi não observamos problemas de memória) e solicitei os seguintes recursos usando nosso kit de teste 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"

(observe que os hashes podem mudar para você)

mas você deve ser capaz de obter os mesmos resultados com esta abordagem muito simples https://www.simonholywell.com/post/2015/06/parallel-benchmark-many-urls-with-apachebench/

após alguns milhares de solicitações, nosso uso de memória estava em quase 1 GB e nunca foi reduzido, mesmo quando ocioso. Assim que removo a solicitação e o errorHandler do server.js, o vazamento de memória para. Portanto, parece estar conectado a esses 2. Talvez você tenha recebido poucas solicitações ou usou o nó 8.x?

@abraxxas confirmado. O nó 10 + ~ 300req para cada recurso faz o trabalho. Vou investigar mais. Obrigado!

@kamilogorek intersting, mas se você olhar o tamanho do seu
reprodução você verá que fica em torno de 20mb sem os manipuladores
mas aumenta rapidamente com eles.

Acho que o aviso de vazamento de memória sem os manipuladores acontece apenas porque
devido às solicitações, há algum aumento constante de memória.

Ainda há uma grande diferença no uso de memória entre a versão com
sentinela e sem.

Na quinta-feira, 29 de novembro de 2018, 12h45, Kamil Ogórek < [email protected] escreveu:

@abraxxas https://github.com/abraxxas eu reproduzi com sucesso,
no entanto, parece que o servidor ainda vaza objetos de solicitação por conta própria,
mesmo sem manipuladores de Sentinelas.

https://streamable.com/bad9j

A taxa de crescimento é um pouco maior, pois atribuímos domínio e nosso próprio escopo
contestar a solicitação, mas seria utilizado junto com a solicitação do GC.

-
Você está recebendo isso porque foi mencionado.
Responda a este e-mail diretamente, visualize-o no GitHub
https://github.com/getsentry/sentry-javascript/issues/1762#issuecomment-442804709 ,
ou silenciar o tópico
https://github.com/notifications/unsubscribe-auth/AIbrNlgPjPd5Jra1aahR-Dthf7XvbCexks5uz8jjgaJpZM4YvOA2
.

@abraxxas ignore meu comentário anterior (o que removi), está totalmente do nosso lado :)

Parece ser o problema no próprio núcleo do Node. Consulte este comentário https://github.com/getsentry/sentry-javascript/issues/1762#issuecomment -444126990

@kamilogorek Você já teve a chance de dar uma olhada nisso? Isso está causando um grande vazamento de memória para nós. Depois de olhar para o heap, essa linha parece que pode ser a raiz do problema:

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

O inspetor mostrou milhares de entradas na lista eventProcessors
image

Não tenho nenhum contexto sobre como as coisas são arquitetadas, mas notamos que as solicitações não têm escopo correto e estão fornecendo os metadados errados (consulte # 1773), então parece que tudo está sendo gerenciado em estado global e não limpo quando um pedido termina

@abraxxas @tpbowden há um problema com o vazamento do módulo de domínio no próprio núcleo do Node. Continuaremos monitorando e tentaremos encontrar uma solução temporária antes que seja consertada no núcleo. Problema relacionado: https://github.com/nodejs/node/issues/23862

@kamilogorek , você tem alguma ideia para uma solução alternativa ou solução temporária para isso? O progresso no problema do nó parece muito lento

Atualmente, estamos usando o PM2 para reiniciar os processos Node.js quando a memória atinge um certo limite. https://pm2.io/doc/en/runtime/features/memory-limit/#max -memory-threshold-auto-reload

Usando o laboratório para testes de unidade. Os vazamentos ainda estão presentes. Eu sei que os vazamentos podem ser dolorosos para depurar. Existe um HEC para corrigir?

1 teste concluído
Duração do teste: 1832 ms
Os seguintes vazamentos foram detectados: __ extends, __assign, __rest, __decorate, __param, __metadata, __awaiter, __generator, __exportStar, __values, __read, __spread, __await, __asyncGenerator, __asyncDelegator_implate_implate_asyncVjectalegator, __asyncDelegator_implaca_impleto_assincronizador, __impleto_vetor_async,

npm ERR! código ELIFECYCLE
npm ERR! errno 1
npm ERR! [email protected] teste: lab build/test
npm ERR! Status de saída 1
npm ERR!
npm ERR! Falha no script de teste [email protected] .
npm ERR! Isso provavelmente não é um problema com o npm. Provavelmente, há uma saída de registro adicional acima.

npm ERR! Um registro completo desta execução pode ser encontrado em:
npm ERR! /Users/sunknudsen/.npm/_logs/2019-02-13T14_41_28_595Z-debug.log

@sunknudsen O problema foi corrigido no NodeJS, consulte https://github.com/nodejs/node/issues/23862 e https://github.com/nodejs/node/pull/25993. Provavelmente precisamos esperar por um lançamento.

@garthenweb Isso afeta @sentry/node por causa de como o pacote foi desenvolvido? Um dos projetos que estou desenvolvendo no hapi (e depende de muitas outras dependências) não produz vazamentos (pelo menos eles não são detectados pelo laboratório ).

@sunknudsen Mais ou menos. É a combinação do pacote de domínio (obsoleto) e promessas, tanto quanto eu entendi. Consulte https://github.com/getsentry/sentry-javascript/blob/master/packages/node/src/handlers.ts#L233

No meu caso, acabei de remover os middlewares de sentinela do meu servidor (expresso) para consertá-lo.

Este não é um problema no nó, mas no Sentry, pois desabilitar os manipuladores do Sentry corrige o problema;

image

@MartijnHols No momento, estamos trabalhando em uma versão importante que deve reduzir significativamente o https://github.com/getsentry/sentry-javascript/pull/1919

@HazAT Obrigado, eu instalei em produção ontem à noite (às 23:10 no gráfico) e

image

@MartijnHols Obrigado por experimentar!

Duas coisas para se ter em mente: a correção do vazamento de memória para domínios no nó atingiu o nó apenas recentemente em 11.10 .
Além disso, tivemos que cancelar a publicação de 5.0.0-beta1 porque foi erroneamente marcado como latest , 5.0.0-rc.1 agora é a versão next recente.
Por favor, tente 5.0.0-rc.1 fizemos uma pequena mudança na forma como os eventos são enfileirados que deve melhorar muito a carga / memória.

A atualização para o nó 11.12 parece ter estabilizado o uso de memória e CPU. Parece que não há nenhuma diferença perceptível agora no uso de recursos quando comparado a ter os manipuladores desativados, talvez até um pouco melhor. Ele também detecta erros com todas as informações de que preciso (pode ter mais "migalhas de pão" do console, o que é bom). Não tenho certeza do que mais posso verificar para 5.0.0. Avisarei você se tiver algum problema, mas provavelmente não.

LGTM. Obrigado!

Eu ficaria feliz em tentar isso também. @HazAT você sabe se a correção em 11.10 já foi retransmitida para a versão LTS ativa 10.x ?

@adriaanmeuris Eu li que alguém perguntou se o backport será feito para 10.x , mas não tenho certeza se eles farão.
ref: https://github.com/nodejs/node/pull/25993#issuecomment -463957701

Resolvi o problema criando o cliente e o escopo manualmente em um middleware expresso

Eu criei um arquivo services/sentry que exporta uma função

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 });
};

e em um middleware eu salvo o cliente / escopo sentry no objeto de solicitação 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();
});

Com isso parece que o vazamento de memória é fixo

image

@couds Esta implementação é muito boa, mas há uma coisa que você deve considerar.

Para cada solicitação, você receberá um novo cliente e não detectamos nenhum erro global / breadcrumbs automáticos (ou qualquer outra coisa que as integrações padrão façam).

@HazAT Obrigado, eu sei disso, mas é um pequeno preço a pagar. para resolver aquele enorme vazamento de memória, mas para minimizar a perda, fiz algumas coisas.

  1. Tento lidar com todas as exceções que prefiro não confiar nos eventos unCoughException / promessa.
  2. Para breadcrumbs, eu os adiciono manualmente, desde que eu tenha acesso ao objeto req.
  3. Faça upload dos mapas de origem para o sentinela com @sentry/webpack-plugin
  4. Adicione tags / extras em cada solicitação que ajudará a identificar o problema

Mais uma coisa, esqueci de colar mais uma tag que estou adicionando que é a transação (para simular o manipulador padrão)

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

Espero que isso ajude alguém, Sentry é uma ferramenta realmente ótima e fiz o que pude para evitar removê-lo da minha pilha.

@HazAT @kamilogorek ainda estamos vendo o enorme crescimento da memória em [email protected] e [email protected] - você tem certeza de que este patch do nodejs corrigiu isso?

@tpbowden Você pode fornecer uma reprodução OU pelo menos dizer como seu código se parece?
Além disso, você está usando algum outro plug-in relacionado ao Sentry?

@HazAT Eu tentei reproduzir, mas está sendo causado por um servidor bastante complexo com muito middleware (renderização do lado do servidor React). Com o middleware Sentry instalado, estamos atingindo um grande crescimento de memória (sob carga pesada, pode aumentar em cerca de 10 MB / segundo). Quando removemos Sentry.Handlers.requestHandler() middleware de nosso servidor, a memória é constante em ~ 200 MB

Não estamos usando plug-ins, apenas @sentry/node . Você pode pensar em algo que eu poderia tentar ajudar a reproduzir isso?

@HazAT Parece que está relacionado ao empacotamento do Sentry usando webpack no servidor - só acontece quando o aplicativo foi construído por Webpack. Adicionar @sentry/node a externals corrigiu nosso problema de memória. Alguma ideia de por que isso acontece?

O vazamento ainda é reproduzível no Nó 11.14.0 usando @ sentry / node 5.1.0

Para nós, o vazamento é causado pela interação entre @ sentry / node e i18next-express-middleware. Nosso aplicativo expresso é semelhante a https://github.com/i18next/react-i18next/blob/master/example/razzle-ssr/src/server.js.

Se colocarmos .use(Sentry.Handlers.requestHandler()); acima de .use(i18nextMiddleware.handle(i18n)) , teremos um vazamento de memória. Se colocarmos sentinela abaixo dela, não haverá vazamento.

Nós temos o mesmo problema. Eu tentei com node 10.15.3 e 11.14.0 . Aqui está um repositório mínimo para reproduzir o problema https://github.com/michalkvasnicak/sentry-memory-leak-reproduction. Apenas execute yarn test ou yarn test:watch que relatará o uso de heap. Isso sempre aumenta.

yarn test
image

yarn test:watch

image

image

@michalkvasnicak Obrigado por

Você não está realmente testando nada em relação ao SDK

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

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

Você precisa do pacote, mas é isso.
Não tenho certeza, pois ainda não tenho experiência com jest executando testes de vazamento, mas o que pode vazar apenas exigindo o pacote?
Não tenho certeza, talvez eu esteja faltando alguma coisa, mas eu esperaria que a memória aumentasse ao carregar um novo pacote.

Em relação ao que pode vazar, criamos algumas variáveis ​​globais, mas precisamos delas para rastrear o estado.

@HazAT sim é estranho mas basta fazer testes de vazamento, jest irá falhar com vazamento detectado. Temos o mesmo problema em nossa base de código onde apenas comentar a importação de @sentry/node resolveu o problema de vazamento.

@michalkvasnicak Eu investiguei isso e não é causado diretamente pelo Sentinela.

Nosso transporte @sentry/node depende de https-proxy-agent , que depende de agent-base que requer seu arquivo patch-core.js _and_ isso é o que cria um vazamento: encolher de ombros:

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

Você pode verificar isso facilmente copiando o conteúdo em seu teste e executando-o com estatísticas de heap:

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);
});

Provavelmente teremos que bifurcar ou escrever uma solução alternativa.

alguma solução alternativa para este?

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

consertou alguns vazamentos de memória, alguém pode testá-lo?

Eu tenho o mesmo problema no nó 11.10, mas parece estar corrigido no nó 12.3.1

Há um PR aberto para o problema de agent-base : https://github.com/TooTallNate/node-agent-base/pull/25

Podemos bifurcar isso e substituir a dependência nas resoluções do Yarn ou encontrar uma maneira de mesclá-lo.

Pode ser um pouco offtopic, mas também pode causar alguns vazamentos de que não há limpeza de domínio em Handlers.ts, apenas domain.create?
Artigo médio ou SO sugere que deve haver removeListeners e domain.exit. Mas não encontrou uma resposta definitiva para isso.

ATUALIZAÇÃO: agora vejo que os manipuladores estão usando domain.run, que chama internamente domain.exit. Ainda remover os ouvintes / emissores pode fazer diferença, mas honestamente não tenho ideia.

Eu atualizei para o Node 12.4.0 e ainda estou vendo um mau comportamento que parece estar ligado ao Sentry.

Aqui estão algumas execuções do médico da clínica do nó, feitas com a opção --autocannon por 90 segundos.

Ele só parece realmente vazado quando os manipuladores de solicitação estão funcionando. Se você olhar para o fundo das calhas do GC na execução sem os manipuladores, eles estão quase no mesmo nível (65-70 MB), onde a execução com os manipuladores parece estar subindo cerca de 5 MB a cada ciclo do GC.

@ tstirrat15 esta correção ainda não foi lançada, então é provavelmente por isso que você ainda está tendo esse problema. Você pode tentar com o master mais recente, se for uma opção?

@ tstirrat15 5.4.2 com a correção incluída foi lançada, experimente :)

Aqui está outra execução com a v5.4.2 instalada. Ainda parece um pouco gotejante ...

O GC está sempre funcionando corretamente e restaura a memória à linha de base. O aumento do uso de memória é causado por breadcrumbs coletados dos eventos e da fila de eventos, mas parará em 100 breadcrumbs e não aumentará mais. Seria bom se pudéssemos ver um despejo de aproximadamente 15-30 minutos e ver se o pico de memória para em algum ponto.

Hmm ... parece bom. Vou levar este PR para a produção e ver se o comportamento muda. Obrigado!

Parece que está relacionado ao empacotamento do Sentry usando o webpack no servidor - só acontece quando o aplicativo foi construído pelo Webpack. Adicionar @ sentinela / nó a externos corrigiu nosso problema de memória. Alguma ideia de por que isso acontece?

@tpbowden Você está certo sobre isso, eu tenho o mesmo problema. Eu estava executando o SDK v5.15.0 e o node v12.3.1, ambos deveriam incluir todas as correções necessárias mencionadas aqui.

Estou agrupando todas as dependências em meu pacote de servidor com webpack. Desta forma, posso enviar uma imagem docker sem node_modules, mas algo está bagunçando o SDK do sentry e vazando memória quando agrupado dessa forma.

Pode ser um bug causado por algum processo de otimização. Meu palpite é que provavelmente é mais conciso. Provavelmente, alguma otimização está atrapalhando o uso do módulo de domínio, e o fechamento do retorno de chamada passado para scope.addEventProcessor não é mais coletado como lixo, portanto, cada solicitação feita está perdendo uma boa quantidade de memória.

Também estou usando razzle.js, que está um pouco atrasado nas versões webpack / terser, talvez já esteja corrigido.

Isso não parece mais ser um bug do lado dos sentinelas. Vou continuar a investigar isso e abrir um problema quando apropriado e manter este tópico atualizado.

Isso não parece mais ser um bug do lado dos sentinelas. Vou continuar a investigar isso e abrir um problema quando apropriado e manter este tópico atualizado.

Mantenha-nos informados, obrigado!

@kamilogorek Você poderia me apontar onde no código o retorno de chamada dos processadores de evento adicionado ao array _eventProcessors dentro da instância do Escopo é removido? Eu não consigo encontrar. Parece que todas as solicitações estão adicionando um retorno de chamada do processador de eventos a essa matriz e nunca são removidas. Se eu soubesse como eles devem ser removidos, poderia me ajudar a entender melhor o bug.

Screen Shot 2020-03-23 at 15 49 03

Ou talvez seja todo o escopo que deve ser exclusivo e coletado como lixo para cada solicitação? Parece que cada solicitação está obtendo a mesma instância de escopo 🤔

Ha! Eu acho que encontrei algo.

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

Mas quando entro no código dynamicRequire:
https://github.com/getsentry/sentry-javascript/blob/fd26d9fa273002502706b03fc1a9a46864cd8440/packages/utils/src/misc.ts#L28-L31

require está definido em mod 🤯

Assim que entra no catch bloco do getHubFromActiveDomain função e passar a usar getHubFromCarrier() !

Como na minha configuração _todas as coisas_ são agrupadas pelo webpack, provavelmente há algumas suposições feitas no objeto mod que foi quebrado pelo webpack. Você tem uma ideia de como isso pode ser consertado? 🤔

Recapitular

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

mod.require é indefinido:
Screen Shot 2020-03-24 at 12 20 01

Qual a aparência do objeto mod:
Screen Shot 2020-03-24 at 12 20 38

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

Corrigi manualmente o módulo Hub diretamente na pasta node_modules. Eu removi a linha usando dynamicRequire e apenas adicionei import domain from 'domain'; no topo do arquivo e ... agora funciona perfeitamente! Não há mais vazamentos! 🎉

Talvez o hack dynamicRequire fosse necessário antes, mas não é mais necessário com as versões mais recentes do webpack? 🤔

Eu também tentei substituir:

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

com:

const domain = require('domain');

e também funciona bem. Não sei qual dessas duas soluções você prefere.

Você gostaria que eu abrisse um PR com esta correção?

Esta página foi útil?
0 / 5 - 0 avaliações