Sentry-javascript: Утечки памяти @ sentry / node на узле 10.13.0

Созданный на 22 нояб. 2018  ·  50Комментарии  ·  Источник: getsentry/sentry-javascript

Пакет + Версия

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

Версия:

4.3.4

Описание

Я пробовал интегрировать часового в проект ah next.js. Я попробовал использовать этот шаблон https://github.com/sheerun/next.js/tree/with-sentry-fix/examples/with-sentry и обнаружил, что похоже на утечку памяти в часовом. Если вы посмотрите этот проект и добавите 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));
  });
}

и засыпая сервер запросами, я запросил следующие ресурсы:

    "/",
    "/_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"

Благодаря этому использование памяти для меня бесконечно растет. Как только я удалю запрос и errorHandler из server.js, утечка памяти прекратится. Кажется, это связано с этими двумя

In Progress Bug

Самый полезный комментарий

@michalkvasnicak Я исследовал это, и это не вызвано напрямую Sentry.

Наш транспорт @sentry/node зависит от https-proxy-agent , который зависит от agent-base которого требуется файл patch-core.js _и_ вот что создает утечку: shrug:

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

Вы можете легко проверить это, скопировав его содержимое в свой тест и запустив его со статистикой кучи:

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

Возможно, нам придется его разветвить или написать обходной путь.

Все 50 Комментарий

@abraxxas, вы можете помочь мне воспроизвести эту проблему? Как именно вы запускаете эту утечку памяти?
Я безуспешно пробовал ваше описание, оно просто генерирует статистические события, никогда не утекает.

@kamilogorek Спасибо за ваш ответ. Я сделал следующее. Я проверил этот пример https://github.com/sheerun/next.js/tree/with-sentry-fix/examples/with-sentry и добавил memwatch в 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));
  });
}

затем я запустил пример с помощью узла 10.x (с 8.xi не обнаружил проблем с памятью) и запросил следующие ресурсы с помощью нашего набора тестов 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"

(обратите внимание, что хеши могут измениться для вас)

но вы сможете достичь тех же результатов с помощью этого очень простого подхода https://www.simonholywell.com/post/2015/06/parallel-benchmark-many-urls-with-apachebench/

после нескольких тысяч запросов наше использование памяти составило почти 1 ГБ и никогда не уменьшалось даже в режиме ожидания. Как только я удалю запрос и errorHandler из server.js, утечка памяти прекратится. Похоже, это связано с этими 2. Может быть, у вас либо было слишком мало запросов, либо вы использовали узел 8.x?

@abraxxas подтвердил. Узел 10 + ~ 300req для каждого ресурса выполняет свою работу. Будем исследовать дальше. Спасибо!

@kamilogorek интересно , но если вы посмотрите на heapsize в своем
воспроизведение вы увидите, что он остается около 20 МБ без обработчиков
но быстро увеличивается вместе с ними.

Я думаю, что предупреждение об утечке памяти без обработчиков происходит только потому, что
из-за запросов происходит некоторое постоянное увеличение памяти.

По-прежнему существует огромная разница в использовании памяти между версиями с
часовой и без.

В четверг, 29 ноября 2018 г., 12:45 Камил Огорек < [email protected] написал:

@abraxxas https://github.com/abraxxas Я успешно воспроизвел это,
однако, похоже, что сервер по-прежнему утекает объекты запроса самостоятельно,
даже без обработчиков Sentry.

https://streamable.com/bad9j

Темпы роста немного больше, так как мы подключаем домен и собственную область видимости.
возражать против запроса, но он будет использоваться вместе с запросом сборщиком мусора.

-
Вы получаете это, потому что вас упомянули.
Ответьте на это письмо напрямую, просмотрите его на GitHub
https://github.com/getsentry/sentry-javascript/issues/1762#issuecomment-442804709 ,
или отключить поток
https://github.com/notifications/unsubscribe-auth/AIbrNlgPjPd5Jra1aahR-Dthf7XvbCexks5uz8jjgaJpZM4YvOA2
.

@abraxxas игнорирует мой предыдущий комментарий (тот, который я удалил), он полностью на нашей стороне :)

Похоже, проблема в самом ядре Node. См. Этот комментарий https://github.com/getsentry/sentry-javascript/issues/1762#issuecomment -444126990

@kamilogorek Вы уже успели разобраться в этом? Это вызывает у нас огромную утечку памяти. Посмотрев на кучу, эта строка выглядит так, будто это может быть корнем проблемы:

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

Инспектор показал тысячи записей в списке eventProcessors
image

У меня нет никакого контекста относительно того, как все устроено, но мы заметили, что запросы имеют неправильную область видимости и выдают неправильные метаданные (см. # 1773), поэтому кажется, что все управляется в глобальном состоянии и не очищается, когда запрос заканчивается

@abraxxas @tpbowden есть проблема с утечкой модуля домена в самом ядре Node. Мы будем следить за ним и постараемся найти временное решение, прежде чем оно будет исправлено в ядре. Связанная проблема: https://github.com/nodejs/node/issues/23862

@kamilogorek есть ли у вас какие-нибудь идеи временного решения или временного решения этой проблемы? Прогресс по проблеме узла выглядит довольно медленным

В настоящее время мы используем PM2 для перезапуска процессов Node.js, когда объем памяти достигает определенного порога. https://pm2.io/doc/en/runtime/features/memory-limit/#max -memory-threshold-auto-reload

Использование лаборатории для модульного тестирования. Утечки все еще присутствуют. Я знаю, что устранение утечек может быть болезненным. Есть ли расчетное время прибытия для исправления?

1 тест завершен
Продолжительность теста: 1832 мс
Были обнаружены следующие утечки: __ extends, __assign, __rest, __decorate, __param, __metadata, __awaiter, __generator, __exportStar, __values, __read, __spread, __await, __asyncGenerator, __asyncDemplateelegjectValue, __asyncDemplateelegjectal, __asyncDemplateelegjectal, __asyncDemplateelegjectal, __

npm ERR! код ELIFECYCLE
npm ERR! ошибка 1
npm ERR! [email protected] тест: lab build/test
npm ERR! Статус выхода 1
npm ERR!
npm ERR! Ошибка при выполнении тестового сценария[email protected].
npm ERR! Вероятно, это не проблема npm. Вероятно, выше есть дополнительный вывод журнала.

npm ERR! Полный журнал этого запуска можно найти в:
npm ERR! /Users/sunknudsen/.npm/_logs/2019-02-13T14_41_28_595Z-debug.log

@sunknudsen Проблема фактически исправлена ​​в NodeJS, см. https://github.com/nodejs/node/issues/23862 и https://github.com/nodejs/node/pull/25993. Наверное, нужно дождаться релиза.

@garthenweb Влияет ли это на @sentry/node из-за того, как был разработан пакет? Один из проектов, который я разрабатываю на hapi (и полагается на множество других зависимостей), не дает утечек (по крайней мере, они не обнаруживаются лабораторией ).

@sunknudsen Более-менее. Насколько я понял, это комбинация (устаревшего) пакета домена и обещаний. См. Https://github.com/getsentry/sentry-javascript/blob/master/packages/node/src/handlers.ts#L233

В моем случае я просто удалил межплатформенное ПО с моего (экспресс) сервера, чтобы это исправить.

Это проблема не в узле, а в Sentry, поскольку отключение обработчиков Sentry устраняет проблему;

image

@MartijnHols В настоящее время мы работаем над основным выпуском, который должен значительно уменьшить объем памяти, занимаемый нашим SDK. Если вы любите приключения, вы можете попробовать https://github.com/getsentry/sentry-javascript/pull/1919

@HazAT Спасибо, я установил его в производство вчера вечером (в 23:10 на графике) и повторно включил обработчики с результатами ниже. Обычно наблюдается небольшой всплеск использования ЦП около 23: 00-24: 00 (как вы можете видеть в предыдущий день), но этот показатель казался выше. Стандартное использование ЦП также намного шире, чем без обработчиков. Не уверен, связано ли это с изменениями в новой версии или с включенными обработчиками. Попробую снова отключить обработчики через несколько часов. В час выявляется около 2,5 ошибок.

image

@MartijnHols Спасибо, что попробовали!

Следует иметь в виду две вещи: исправление утечки памяти для доменов в узле, недавно появившихся в узле в 11.10 .
Кроме того, нам пришлось отменить публикацию 5.0.0-beta1 потому что он был ошибочно помечен как latest , 5.0.0-rc.1 теперь является последней версией next .
Пожалуйста, попробуйте 5.0.0-rc.1 Мы внесли небольшое изменение в постановку событий в очередь, что должно значительно улучшить загрузку / память.

Обновление до узла 11.12, похоже, стабилизировало использование памяти и процессора. Похоже, что сейчас нет заметной разницы в использовании ресурсов при сравнении с отключенными обработчиками, может быть, даже немного лучше. Кажется, он также отлично отлавливает ошибки со всей необходимой мне информацией (у него может быть больше консольных "хлебных крошек", что приятно). Не уверен, что еще я могу проверить для 5.0.0. Я дам вам знать, если у меня возникнут какие-либо проблемы, но, вероятно, нет.

LGTM. Спасибо!

Я был бы счастлив попробовать и это. @HazAT знаете ли вы, что исправление в 11.10 уже перенесено в активную версию LTS 10.x ?

@adriaanmeuris Я читал, что кто-то спросил, будет ли он перенесен на 10.x , но не уверен, что они это сделают.
ссылка: https://github.com/nodejs/node/pull/25993#issuecomment -463957701

Я решил проблему создания клиента и области вручную в промежуточном программном обеспечении Express

Я создал файл services/sentry который экспортирует функцию

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

и в промежуточном программном обеспечении я сохраняю сторожевой клиент / область в объекте запроса, подобном этому

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

При этом кажется, что утечка памяти исправлена

image

@couds Эта реализация действительно хороша, есть одна вещь, которую вы должны учесть.

Для каждого запроса вы получите нового клиента, и мы не поймем никаких глобальных ошибок / автоматических хлебных крошек (или чего-то еще, что делают интеграции по умолчанию).

@HazAT Спасибо, я знаю это, но это небольшая цена. чтобы решить эту огромную утечку памяти, но чтобы минимизировать эту потерю, я сделал несколько вещей.

  1. Я стараюсь обрабатывать все исключения, которые предпочитаю не полагаться на события unCoughException / обещания.
  2. Для хлебных крошек я добавляю их вручную, пока у меня есть доступ к объекту req ..
  3. Загрузите исходные карты в часовую с помощью @sentry/webpack-plugin
  4. Добавляйте теги / Extra по каждому запросу, которые помогут идентифицировать проблему

Еще одна вещь, я забыл вставить еще один тег, который я добавляю, это транзакция (для имитации обработчика по умолчанию)

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

Я надеюсь, что это кому-то поможет, Sentry - действительно отличный инструмент, и я сделал все, что мог, чтобы не удалить его из своего стека.

@HazAT @kamilogorek мы все еще наблюдаем огромный рост памяти на [email protected] и [email protected] - вы уверены, что этот патч nodejs исправил это?

@tpbowden Не могли бы вы предоставить репродукцию ИЛИ хотя бы сказать, как выглядит ваш код?
Кроме того, используете ли вы какие-либо другие плагины, связанные с Sentry?

@HazAT Я пытался воспроизвести, но это вызвано довольно сложным сервером с большим количеством промежуточного программного обеспечения (рендеринг на стороне сервера React). С установленным промежуточным программным обеспечением Sentry мы достигаем огромного роста памяти (при большой нагрузке он может увеличиваться на ~ 10 МБ / с). Когда мы удаляем промежуточное ПО Sentry.Handlers.requestHandler() с нашего сервера, память постоянно составляет ~ 200 МБ.

Мы не используем никаких плагинов, только @sentry/node . Можете ли вы придумать что-нибудь, что я мог бы попытаться воспроизвести?

@HazAT Похоже, это связано с объединением Sentry с использованием webpack на сервере - это происходит только тогда, когда приложение было создано с помощью Webpack. Добавление @sentry/node к externals устранило нашу проблему с памятью. Есть идеи, почему это происходит?

Утечка все еще воспроизводима на Node 11.14.0 с использованием @ sentry / node 5.1.0

Для нас утечка вызвана взаимодействием между @ sentry / node и i18next-express-middleware. Наше экспресс-приложение похоже на https://github.com/i18next/react-i18next/blob/master/example/razzle-ssr/src/server.js.

Если мы поместим .use(Sentry.Handlers.requestHandler()); выше .use(i18nextMiddleware.handle(i18n)) мы получим утечку памяти. Если поставить под ним часовую, то утечки не будет.

У нас такая же проблема. Я пробовал это с node 10.15.3 и 11.14.0 . Вот минимальный репозиторий для воспроизведения проблемы https://github.com/michalkvasnicak/sentry-memory-leak-reproduction. Просто запустите yarn test или yarn test:watch которые сообщат об использовании кучи. Он всегда увеличивается.

yarn test
image

yarn test:watch

image

image

@michalkvasnicak Спасибо, что

Вы на самом деле ничего не тестируете в отношении SDK

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

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

Вам нужен пакет, но это все.
Я не уверен, так как у меня еще нет опыта работы с jest тестами на утечки, но какие утечки могут возникнуть, если просто потребовать пакет?
Не уверен, возможно, мне что-то не хватает, но я ожидал, что объем памяти увеличится при загрузке нового пакета.

Что касается утечек, мы действительно создаем некоторые глобальные переменные, но они нам нужны для отслеживания состояния.

@HazAT да, это странно, но этого достаточно для утечки тестов, jest выйдет из строя при обнаружении утечки. У нас есть та же проблема в нашей базе кода, где просто комментирование импорта @sentry/node решает проблему с утечкой.

@michalkvasnicak Я исследовал это, и это не вызвано напрямую Sentry.

Наш транспорт @sentry/node зависит от https-proxy-agent , который зависит от agent-base которого требуется файл patch-core.js _и_ вот что создает утечку: shrug:

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

Вы можете легко проверить это, скопировав его содержимое в свой тест и запустив его со статистикой кучи:

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

Возможно, нам придется его разветвить или написать обходной путь.

какой обходной путь для этого?

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

исправили некоторые утечки памяти, может кто нибудь протестировать?

У меня такая же проблема на узле 11.10, но, похоже, она исправлена ​​на узле 12.3.1

Открыт PR по проблеме agent-base : https://github.com/TooTallNate/node-agent-base/pull/25

Мы можем либо разветвить это и переопределить зависимость в разрешениях Yarn, либо найти способ объединить их.

Может быть немного оффтопом, но также может вызвать некоторые утечки, что в Handlers.ts нет очистки домена, только domain.create?
Средняя статья или SO предполагает, что должны быть removeListeners и domain.exit. Но однозначного ответа на это не нашел.

ОБНОВЛЕНИЕ: теперь я вижу, что обработчики используют domain.run, который внутренне вызывает domain.exit. По-прежнему удаление слушателей / излучателей может иметь значение, но, честно говоря, понятия не имею.

Я обновился до Node 12.4.0, но все еще наблюдаю плохое поведение, которое, кажется, связано с Sentry.

Вот несколько работ врача из клиники узлов, выполненные с опцией --autocannon за 90 секунд.

Это кажется действительно ненадежным только тогда, когда есть обработчики запросов. Если вы посмотрите на дно желобов GC в прогоне без обработчиков, они все примерно на одном уровне (65-70 МБ), где запуск с обработчиками, кажется, увеличивается примерно на 5 МБ с каждым циклом GC.

@ tstirrat15 это исправление еще не выпущено, поэтому, вероятно, поэтому у вас все еще есть эта проблема. Можете ли вы попробовать от последнего мастера, если это вариант?

@ tstirrat15 5.4.2 с исправлением выпущено, попробуйте :)

Вот еще один запуск с v5.4.2. Он все еще кажется немного протекающим ...

GC всегда работает правильно и восстанавливает память до базового уровня. Увеличение использования памяти вызвано хлебными крошками, собранными из событий и очереди событий, но оно остановится на 100 хлебных крошках и больше не будет увеличиваться. Было бы неплохо, если бы мы могли видеть дамп ~ 15-30 минут и видеть, останавливается ли пиковая память в какой-то момент.

Хм ... звучит хорошо. Я доведу этот PR до производства и посмотрю, изменится ли поведение. Спасибо!

Похоже, это связано с объединением Sentry с использованием webpack на сервере - происходит только тогда, когда приложение было создано с помощью Webpack. Добавление @ sentry / node во внешнее устранило нашу проблему с памятью. Есть идеи, почему это происходит?

@tpbowden Вы правы, у меня такая же проблема. Я использовал SDK v5.15.0 и node v12.3.1, оба должны включать все необходимые исправления, упомянутые здесь.

Я связываю все зависимости в своем комплекте сервера с webpack. Таким образом я могу отправить образ докера без node_modules, но что-то испортило сторожевой SDK, и при такой упаковке происходит утечка памяти.

Это может быть ошибка в процессе оптимизации. Мое безумное предположение - это, наверное, короче. Некоторая оптимизация, вероятно, нарушает использование модуля домена, и закрытие обратного вызова, переданного в scope.addEventProcessor , больше не является сборщиком мусора, поэтому каждый сделанный запрос приводит к утечке большого количества памяти.

Я также использую razzle.js, который немного отстает от версий webpack / terser, возможно, он уже исправлен.

Это больше не похоже на ошибку на стороне часового. Я продолжу исследовать это и при необходимости открою проблему и буду держать эту ветку в курсе.

Это больше не похоже на ошибку на стороне часового. Я продолжу исследовать это и при необходимости открою проблему и буду держать эту ветку в курсе.

Держите нас в курсе, спасибо!

@kamilogorek Не могли бы вы указать мне, где в коде _eventProcessors в экземпляре Scope? Я не могу его найти. Кажется, что все запросы добавляют обратный вызов обработчика событий к этому массиву, и они никогда не удаляются. Если бы я знал, как их следует удалять, это могло бы помочь мне лучше понять ошибку.

Screen Shot 2020-03-23 at 15 49 03

Или, может быть, вся область видимости должна быть уникальной и собирать мусор для каждого запроса? Кажется, что каждый запрос получает один и тот же экземпляр области 🤔

Или, может быть, вся область видимости должна быть уникальной и собирать мусор для каждого запроса?

Это правильно. Однако scope следует клонировать для каждого нового экземпляра domain 🤔

См. Этот стек вызовов:

https://github.com/getsentry/sentry-javascript/blob/fd26d9fa273002502706b03fc1a9a46864cd8440/packages/node/src/handlers.ts#L319 -L328

https://github.com/getsentry/sentry-javascript/blob/fd26d9fa273002502706b03fc1a9a46864cd8440/packages/hub/src/hub.ts#L442 -L457

https://github.com/getsentry/sentry-javascript/blob/fd26d9fa273002502706b03fc1a9a46864cd8440/packages/hub/src/hub.ts#L463 -L488

https://github.com/getsentry/sentry-javascript/blob/fd26d9fa273002502706b03fc1a9a46864cd8440/packages/hub/src/hub.ts#L479

Ха! Я думаю, что я нашел что-то.

Мы используем dynamicRequire:
https://github.com/getsentry/sentry-javascript/blob/fd26d9fa273002502706b03fc1a9a46864cd8440/packages/hub/src/hub.ts#L465-L468

Но когда я вхожу в код dynamicRequire:
https://github.com/getsentry/sentry-javascript/blob/fd26d9fa273002502706b03fc1a9a46864cd8440/packages/utils/src/misc.ts#L28-L31

require не определено в mod 🤯

Так он попадает в catch блок getHubFromActiveDomain функции и вместо этого использовать getHubFromCarrier() !

Поскольку в моей настройке _everyting_ связан с webpack, вероятно, есть некоторые предположения, сделанные для объекта mod который нарушается webpack. У вас есть идеи, как это исправить? 🤔

Резюме

Мы используем dynamicRequire:
Screen Shot 2020-03-24 at 12 05 04

mod.require не определен:
Screen Shot 2020-03-24 at 12 20 01

Как выглядит объект мода:
Screen Shot 2020-03-24 at 12 20 38

В итоге мы используем getHubFromCarrier:
Screen Shot 2020-03-24 at 12 21 22

Я вручную пропатчил модуль Hub прямо в папке node_modules. Я удалил строку с помощью dynamicRequire и просто добавил import domain from 'domain'; в начало файла и ... теперь он работает отлично! Больше никаких утечек! 🎉

Может быть, хак dynamicRequire был нужен раньше, но больше не нужен в более новых версиях webpack? 🤔

Еще пробовал заменить:

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

с участием:

const domain = require('domain');

и он тоже отлично работает. Я не знаю, какое из этих двух решений вы бы предпочли.

Вы хотите, чтобы я начал пиар с этим исправлением?

Была ли эта страница полезной?
0 / 5 - 0 рейтинги