Память Node.js растёт, а после перезапуска сервис снова работает нормально? Это ещё не доказывает утечку: сначала нужно сравнить память одного процесса при одинаковой нагрузке, отделить JavaScript-объекты от буферов и проверить, что остаётся после завершения работы. Для руководителя проекта полезный результат такой проверки — воспроизводимый сценарий и понятная задача разработчику, а не обещание «добавить памяти и забыть».
Первые действия без вмешательства в рабочий сервис
- Сохраните имеющиеся графики памяти, нагрузки и перезапусков за один период. Отметьте время релиза, импорта или другой операции, после которой заметили рост.
- Уточните, что показывает график: отдельный процесс Node.js, контейнер или весь сервер. Сравнивайте один экземпляр и отмечайте его перезапуски: смена процесса может выглядеть как освобождение памяти.
- Сопоставьте рост с числом одновременных запросов, размером обрабатываемых файлов и длиной очереди. Не запускайте дополнительную нагрузку на рабочем сайте ради проверки.
Если процесс уже завершается и пропадают ответы API, сначала нужен согласованный порядок восстановления доступности. Перезапуск может временно вернуть работу, но сам по себе не объясняет причину. Для заявки, оплаты или фоновой обработки отдельно проверьте, что прерванная операция не потерялась и не выполнилась повторно.
Что сравнивать: heap, RSS и буферы
В Node.js метод process.memoryUsage() возвращает несколько показателей. В официальной документации Process они разделены так:
heapUsed— занятая память кучи V8, где размещаются JavaScript-объекты;heapTotal— память, выделенная V8 под кучу; её рост сам по себе не показывает утечку;rss— резидентная память всего процесса, включая код, JavaScript и нативные компоненты;external— память C++-объектов, связанных с JavaScript;arrayBuffers— память буферов, включая Node.jsBuffer; она уже входит вexternal, поэтому складывать эти два значения нельзя.
При использовании worker_threads RSS относится ко всему процессу, а остальные перечисленные значения — к текущему потоку. Один график главного потока может скрыть рост памяти рабочего потока.
Сборщик мусора освобождает ненужные объекты не после каждого запроса. Поэтому подъёмы и спады на графике допустимы; важнее, растёт ли нижний уровень после сопоставимых циклов работы. Высокий RSS при стабильной куче тоже возможен: документация Node.js отдельно описывает фрагментацию распределителя памяти glibc в Linux. Это гипотеза для проверки, а не диагноз по форме графика.
Четыре объяснения роста и способы их различить
Прогрев или ограниченный кеш. После запуска сервис загружает данные, затем потребление выходит на устойчивый уровень. Проверьте, перестаёт ли расти число записей кеша и есть ли у него ограничение размера или срока хранения. Одного короткого ровного участка недостаточно: нужен характерный для сервиса цикл.
Накопление незавершённой работы. Запросы или задания поступают быстрее, чем завершаются. Сопоставьте память с длиной очереди и числом активных операций. Если на тестовом контуре после остановки поступления новых заданий очередь сокращается и память освобождается, сначала разбирайте скорость обработки и предел параллельности.
Удержание больше не нужных объектов. После одинаковых повторений сценария и сборки мусора остаётся всё больше объектов. Разработчик проверяет, кто продолжает хранить ссылки: например, коллекция, кеш, обработчик события или незавершённая операция. Наличие таких конструкций в коде ещё не доказывает ошибку: нужно показать конкретное удержание после окончания полезной работы.
Рост вне JavaScript-кучи. При ровном heapUsed увеличиваются буферы или только RSS. Проверьте связь с загрузкой файлов, обработкой данных и нативными модулями. Снимок JavaScript-кучи не объясняет всю память процесса; увеличение лимита кучи здесь может вообще не затронуть причину.
Как получить сопоставимые измерения
Согласуйте с разработчиком один сценарий на изолированном стенде с искусственными данными: например, повторную генерацию отчёта. Зафиксируйте версию приложения и Node.js, лимиты контейнера, размер входных данных, параллельность и исходное состояние кеша. Затем прогрейте сервис и повторяйте одинаковый объём работы, дожидаясь завершения заданий.
В журнал наблюдения достаточно включить время, этап сценария, идентификатор тестового процесса, показатели памяти, число активных операций и результат выполнения. Содержимое запросов, контакты пользователей и токены туда не нужны. Частоту сбора выбирает разработчик: слишком частые измерения сами создают нагрузку.
Учебный пример. После каждого построения отчёта в общей коллекции остаётся его результат. Если коллекция должна хранить только текущий отчёт, рост числа завершённых результатов — проверяемый повод искать лишнюю ссылку. Если по требованиям это архив в памяти, задача другая: определить допустимый объём, срок хранения и поведение при достижении границы.
Повторите опыт после исправления с теми же условиями. Разные версии, размер отчёта или число параллельных операций сделают сравнение неоднозначным. Выбор поддерживаемой ветки Node.js и план её обновления — отдельные задачи; обновление не заменяет проверки причины роста памяти.
Когда нужен снимок кучи и чем он опасен
Снимок кучи, или heap snapshot, помогает найти сохраняющиеся объекты и ссылки, которые мешают их освободить. Официальное руководство Node.js предлагает сравнивать снимки после прогрева и повторения подозрительного сценария, затем изучать прирост и удерживающие ссылки.
Снятие снимка останавливает работу основного потока и требует дополнительной памяти. Оно способно привести к аварийному завершению процесса. Не начинайте с него на единственном рабочем экземпляре, от которого зависят заявки.
Предпочтителен тестовый контур. Если воспроизвести проблему можно только в рабочем окружении, специалист заранее определяет допустимость остановки выбранного экземпляра, запас ресурсов и порядок восстановления. Отладочный интерфейс нельзя открывать всему интернету.
Снимок может содержать данные приложения. Обращайтесь с ним как с чувствительным файлом: ограничьте доступ, согласуйте место хранения и срок удаления. Не прикладывайте его к обычной заявке и не загружайте в публичный анализатор. Для первоначального обсуждения достаточно обезличенных графиков и описания сценария.
Как принять исправление и определить границу самостоятельной работы
До доработки согласуйте условия проверки: сценарий, объём и длительность нагрузки, ограничения ресурсов и допустимые ошибки. Порог памяти выбирают по требованиям конкретного сервиса; универсального «нормального числа мегабайт» нет.
Исправление подтверждают вместе несколько результатов:
- при повторении исходного сценария после прогрева память остаётся в согласованных пределах;
- завершённая работа не оставляет необъяснимо растущую очередь или коллекцию;
- нет новых аварийных перезапусков, а время ответа и результаты операций удовлетворяют требованиям;
- проверены обычная нагрузка, характерный пик и поведение после его окончания.
Для подозрения на утечку важно проверить интервал не короче того, на котором раньше воспроизводился рост при сопоставимых условиях. Ровный график за ограниченное время снижает неопределённость, но не гарантирует отсутствие всех утечек. Увеличение --max-old-space-size меняет предел старшего поколения кучи V8; оно не удаляет удерживающие ссылки и не является доказательством исправления.
Если своя команда умеет воспроизвести проблему, безопасно работать со снимками и проверить результат, этих критериев достаточно для постановки внутренней задачи. Когда причина неясна, нет тестового контура или рост уже влияет на заказы, уместна диагностика действующего Node.js-сервиса в OpenStart. Работа начинается с разбора сценария, архитектуры и рисков, после чего можно согласовать первый этап; для первичного обращения достаточно описания симптома и условий его появления, без доступов и дампов памяти.