Node js увеличить память
Today I ran my script for filesystem indexing to refresh RAID files index and after 4h it crashed with following error:
Server is equipped with 16gb RAM and 24gb SSD swap. I highly doubt my script exceeded 36gb of memory. At least it shouldn't
Script creates index of files stored as Array of Objects with files metadata (modification dates, permissions, etc, no big data)
I've already experiend weird node issues in the past with this script what forced me eg. split index into multiple files as node was glitching when working on such big files as String. Is there any way to improve nodejs memory management with huge datasets?
Can anyone confirm if this issue can occur due to less CPU. In my case I have 32 GB of RAM and specified about 11G for node options, but have only 2 CPU. Still getting OOM.
Доказательства
Изменение ограничения памяти для всей среды Node.js
Чтобы изменить лимит памяти для всей среды, нужно установить значение переменной NODE_OPTIONS в конфигурационном файле (его расширение .bashrc, bash_profile или .zshrc и т. п.).
▍ Использование удалённого отладчика Chrome для создания снепшотов кучи
Если вы работаете с Node 6.3. или с более поздней его версией, для создания снепшотов кучи можно использовать удалённый отладчик Chrome. Для того, чтобы это сделать, сначала запустите Node командой такого вида: node --inspect server.j s. Затем перейдите по адресу chrome://inspect . Теперь вы сможете удалённо отлаживать процессы Node. Чтобы сэкономить время, можете установить этот плагин Chrome, который автоматически откроет вкладку отладчика при запуске Node с флагом --inspect . После этого просто делайте снепшоты тогда, когда сочтёте это необходимым.
Средства удалённой отладки Chrome и создание снепшотов кучи
▍ Чрезмерное использование памяти
В ситуации чрезмерного использования памяти программа занимает гораздо больше памяти, чем ей нужно для решения возложенной на неё задачи. Например, такое может возникнуть тогда, когда ссылки на большие объекты хранят дольше, чем нужно для правильной работы программы, что предотвращает уничтожение этих объектов сборщиком мусора. Подобное случается и тогда, когда в памяти держат большие объекты, которые попросту не нужны программе (это вызывает одну из двух основных проблем, которые мы рассмотрим ниже).
Задача: копирование огромного файла
Если кого-нибудь попросят создать в среде Node.js программу для копирования файлов, то он, вероятнее всего, тут же напишет примерно то, что показано ниже. Назовём файл, содержащий этот код, basic_copy.js .
Эта программа создаёт обработчики для чтения и записи файла с заданным именем и пытается записать данные файла после их прочтения. Для маленьких файлов такой подход оказывается вполне рабочим.
Предположим, что нашему приложению надо скопировать огромный файл (будем считать «огромными» файлы, размер которых превышает 4 Гб) в ходе процесса резервного копирования данных. У меня, например, есть видеофайл размером 7.4 Гб, который я, с помощью вышеописанной программы, попробую скопировать из моей текущей директории в директорию Documents . Вот команда для запуска копирования:
Как видно, операция чтения файла не удалась из-за того, что Node.js позволяет считывать в буфер лишь 2 Гб данных. Как преодолеть это ограничение? Выполняя операции, интенсивно использующие подсистему ввода-вывода (копирование файлов, их обработка, сжатие), нужно учитывать возможности систем и ограничения, связанные с памятью.
▍Буферы
Буфер можно создать, инициализировав объект Buffer .
Если у нас уже есть некие данные, вроде массива или чего-то подобного, буфер можно создать на основе этих данных.
У буферов есть методы, которые позволяют «заглядывать» в них и узнавать о том, какие данные там находятся — это методы toString() и toJSON() .
Мы, в процессе оптимизации кода, не будем создавать буферы самостоятельно. Node.js создаёт эти структуры данных автоматически, при работе с потоками или сетевыми сокетами.
Как избежать недостатка памяти в Node.js
Вот три альтернативных решения, которые позволят уменьшить потребление памяти.
Отладка
Итак, благодаря настройкам Node и организации мониторинга сервера мы выиграли время, которое можно было потратить на то, чтобы дойти до первопричины неполадки. На первый взгляд может показаться, что «проблема с памятью сервера» — это нечто ужасное, а для избавления от этой «проблемы» потребуются фантастические инструменты и умения. Однако, на самом деле, всё не так уж и страшно. Есть вполне доступные инструменты для исследования приложений, существует множество материалов, в которых можно найти подсказки. Мы, для исследования памяти Node-сервера, будем пользоваться инструментами разработчика Chrome.
Типы проблем с памятью
Выявление проблем с памятью
Признаки утечки памяти, кроме того, включают в себя уменьшение производительности программы с течением времени. Если сервер периодически выполняет один и тот же процесс, который изначально быстр, а перед отказом постепенно становится медленнее, это, весьма вероятно, говорит об утечке памяти.
Признаки чрезмерного использования памяти обычно выражаются в низкой производительности программ. Однако, чрезмерное использование памяти без утечки со временем не приводит к падению производительности.
Потоки и буферы в Node.js
Для того чтобы обойти вышеописанную проблему, нам нужен механизм, с помощью которого можно разбивать большие массивы данных на небольшие фрагменты. Также нам понадобятся структуры данных, позволяющие хранить эти фрагменты и работать с ними. Буфер — это структура данных, которая позволяет хранить двоичные данные. Далее, нам нужно иметь возможность читать фрагменты данных с диска и записывать их на диск. Эту возможность могут дать нам потоки. Поговорим о буферах и потоках.
Рекомендации о настройке размера Node.js-кучи для тех случаев, когда можно управлять этим параметром, но не ограничениями памяти уровня контейнера
- Запустите минимальное Node.js-приложение в контейнере и измерьте статический размер RSS (в моём случае, для Node.js 10.x, это примерно 20 Мб).
- Куча Node.js содержит не только область old_space, но и другие (такие, как new_space, code_space, и так далее). Поэтому, если учитывать стандартную конфигурацию платформы, стоит рассчитывать на то, что программе понадобится ещё около 20 Мб памяти. Если стандартные настройки менялись — эти изменения также нужно учитывать.
- Теперь нужно вычесть полученное значение (предположим, это будет 40 Мб) из объёма памяти, доступной в контейнере. То, что осталось, представляет собой значение, которое, не опасаясь завершения программы от нехватки памяти, можно указать в качестве значения ключа --max-old-space-size .
▍Пример №1. Приложение, которое выделяет память под буфер
В следующем примере, buffer_example.js , показана программа, которая выделяет память под буфер:
Для того чтобы объём памяти, выделяемой программой, превысил бы лимит, заданный при запуске контейнера, сначала запустим контейнер следующей командой:
После этого запустим программу:
Как видно, система не завершила выполнение программы, хотя при этом выделенная программой память и превышает лимит контейнера. Произошло это из-за того, что программа не работает со всей выделенной памятью. Показатель RSS очень мал, он не превышает лимит памяти контейнера.
▍Пример №2. Приложение, заполняющее буфер данными
В следующем примере, buffer_example_fill.js , память не просто выделяется, а ещё и заполняется данными:
После этого запустим приложение:
Как видно, даже теперь приложение не завершается! Почему? Дело в том, что когда объём активной памяти достигает лимита, заданного при запуске контейнера, и при этом в файле подкачки есть место, некоторые из старых страниц памяти процесса перемещаются в файл подкачки. Освобождённая память оказывается доступной тому же самому процессу. По умолчанию Docker выделяет под файл подкачки пространство, равное лимиту памяти, заданному с помощью флага --memory . Учитывая это можно сказать, что у процесса есть 2 Гб памяти — 1 Гб в активной памяти, и 1 Гб — в файле подкачки. То есть, благодаря тому, что приложение может пользоваться своей же памятью, содержимое которой временно перемещается в файл подкачки, размер показателя RSS находится в пределах лимита контейнера. В результате приложение продолжает работать.
Решение 2. Копирование файлов с использованием потоков и с автоматической настройкой скорости чтения и записи данных
Назовём эту программу streams_copy_efficient.js . Вот её код:
Основное отличие этой программы от предыдущей заключается в том, что код для копирования фрагментов данных заменён на следующую строку:
В основе всего того, что тут происходит, лежит метод pipe() . Он контролирует скорости чтения и записи, что приводит к тому, что память теперь не оказывается перегруженной.
Мы копируем тот же самый огромный файл. Теперь посмотрим на то, как выглядит работа с памятью и с диском.
Благодаря использованию pipe() скорости чтения и записи настраиваются автоматически
Теперь мы видим, что процесс node потребляет всего 61.9 Мб памяти. Если же взглянуть на данные по использованию диска, то можно увидеть следующее:
Благодаря механизму back pressure скорости чтения и записи теперь всегда равны друг другу. Кроме того, новая программа выполняется на 13 секунд быстрее старой.
Благодаря использованию метода pipe() нам удалось уменьшить время выполнения программы и снизить потребление памяти на 98.68%.
В данном случае 61.9 Мб — это размер буфера, создаваемый потоком чтения данных. Мы вполне можем задать этот размер самостоятельно, воспользовавшись методом read() потока для чтения данных:
Здесь мы копировали файл в локальной файловой системе, однако тот же подход можно использовать и для оптимизации многих других задач ввода-вывода данных. Например — это работа с потоками данных, источником которых является Kafka, а приёмником — база данных. По такой же схеме можно организовать чтение данных с диска, их сжатие, что называется, «на лету», и запись обратно на диск уже в сжатом виде. На самом деле, можно найти и множество других вариантов применения описанной здесь технологии.
▍ Использование heapdump для создания снепшотов кучи
Мы, для создания снимков кучи, пользовались heapdump. Этот npm-пакет оказался весьма полезным. Его можно импортировать в код и обращаться к нему в тех местах программы, где нужно делать снепшоты. Например, мы делали снепшот каждый раз, когда сервер получал запрос, который мог вызвать процесс, интенсивно использующий память. Тут же мы формировали имя файла, содержащее текущее время. Таким образом мы могли воспроизводить проблему, отправляя на сервер всё новые и новые запросы. Вот как это выглядит в коде:
Перезапуск процессов
Допустим, ваша программа работает на компьютере с ограниченным объёмом памяти, например Raspberry Pi.
Мы будем использовать cluster и библиотеки node v8.
Cluster даёт возможность воспользоваться преимуществами многоядерных систем и запускать кластер из процессов Node.js.
V8 предоставляет API для конкретной версии V8, используемой в Node.js.
Давайте разделим программу на две сущности: master и worker.
Master будет перезапускать worker`ов в случае, если они перестанут работать из-за переполнения кучи. Worker`ы будут отвечать за основную логику (в нашем случае запускать тяжёлую функцию heavyHeapConsumer).
total_heap_size — размер кучи, который можно увеличить.
heap_size_limit — максимально возможный размер кучи.
В коде worker`а устанавливается total_heap_size равный 85% от heap_size_limit. Затем worker каждую секунду проверяет не превышен ли лимит. Если лимит превышен, то процесс worker убивает себя.
Лимит (85%) и интервал проверки (1 секунда) нужно выбирать для каждого конкретного случая. Здесь функция heavyHeapConsumer увеличивает кучу каждые 100мс. Если в вашем варианте увеличение будет происходить каждые 10мс, то следует уменьшить лимит и увеличить интервал проверки.
При запуске Node.js-приложений в контейнерах Docker традиционные настройки памяти не всегда работают так, как ожидается. Материал, перевод которого мы сегодня публикуем, посвящён поиску ответа на вопрос о том, почему это так. Здесь же будут приведены практические рекомендации по управлению памятью, доступной Node.js-приложениям, работающим в контейнерах.
«Heap out of memory» во время nmp install
Если во время установки пакетов с помощью npn или yarn у вас появляется эта ошибка, вы можете увеличить лимит памяти на время установки.
Реальное поведение приложения, запущенного с ключом --max-old-space-size
Приложению, сразу после запуска, не выделяется вся память, лимит которой указан с помощью --max-old-space-size . Размер JavaScript-кучи зависит от нужд приложения. О том, какой размер памяти использует приложение, можно судить на основании значения поля heapUsed из объекта, возвращаемого методом process.memoryUsage() . Фактически, речь идёт о памяти, выделенной в куче под объекты.
В результате мы приходим к выводу о том, что приложение будет принудительно завершено в том случае, если размер кучи окажется больше лимита, заданного ключом --memory при запуске контейнера.
Но в реальности этого тоже может не случиться.
При профилировании ресурсоёмких Node.js-приложений, которые запущены в контейнерах с заданным лимитом памяти, можно наблюдать следующие паттерны:
- OOM Killer срабатывает гораздо позже того момента, когда значения heapTotal и heapUsed оказываются значительно превышающими ограничения на объём памяти.
- OOM Killer никак не реагирует на превышение ограничений.
Ожидаемое поведение приложения, запущенного с ключом --max-old-space-size
По умолчанию максимальный размер кучи в Node.js (вплоть до версии 11.x) составляет 700 Мб на 32-битных платформах, и 1400 Мб на 64-битных. О настройке этих значений можно почитать здесь.
В теории, если установить с помощью ключа --max-old-space-size лимит памяти, превышающий лимит памяти контейнера, можно ожидать, что приложение будет завершено защитным механизмом ядра Linux OOM Killer.
В реальности этого может и не случиться.
Общие рекомендации
Когда Node.js-приложения запускают с ключом --max-old-space-size , значение которого превышает лимит памяти, заданный при запуске контейнера, может показаться, что Node.js «не обращает внимания» на лимит контейнера. Но, как видно из предыдущих примеров, явной причиной подобного поведения является тот факт, что приложение просто не использует весь объём кучи, заданный с помощью флага --max-old-space-size .
Помните о том, что приложение не всегда будет вести себя одинаково в том случае, если оно использует больше памяти, чем доступно в контейнере. Почему? Дело в том, что на активную память процесса (RSS) влияет множество внешних факторов, на которые не может воздействовать само приложение. Они зависят от нагруженности системы и от особенностей окружения. Например — это особенности самого приложения, уровень параллелизма в системе, особенности работы планировщика операционной системы, особенности работы сборщика мусора, и так далее. Кроме того, эти факторы, от запуска к запуску приложения, могут меняться.
Загрузка снепшотов и определение типа проблемы с памятью
Следующий шаг заключается в загрузке снепшотов на закладке Memory (память) инструментов разработчика Chrome. Если вы использовали для создания снепшотов кучи удалённый отладчик Chrome, то они уже будут загружены. Если вы использовали heapdump, то вам понадобится загрузить их самостоятельно. Обязательно загружайте их в правильном порядке, а именно — в том, в котором они были сделаны.
Самое главное, на что надо обращать внимание на данном этапе работы, заключатся в том, чтобы понять — с чем именно вы столкнулись — с утечкой или с чрезмерным использованием памяти. Если перед вами утечка памяти, то вы, вероятно, уже получили достаточно данных для того, чтобы начать исследовать кучу в поисках источника проблемы. Однако, если перед вами — чрезмерное использование памяти, вам нужно попробовать некоторые другие методы анализа для того, чтобы получить содержательные данные.
Наша первая проблема с памятью выглядела, на закладке Memory инструментов разработчика Chrome, так, как показано ниже. Несложно заметить, что куча постоянно растёт. Это говорит об утечке памяти.
Куча увеличивается со временем — очевидная утечка памяти
Наша вторая проблема с памятью, которая возникла через пару месяцев после исправления утечки, в итоге, на тех же испытаниях, выглядела так, как показано на рисунке ниже.
Куча со временем не растёт — это не утечка памяти
Размер кучи со временем не меняется. Всё дело в том, что при чрезмерном использовании памяти её размер превышает некие ожидаемые показатели не всегда, а лишь при выполнении определённых операций. При этом снепшоты делаются в какие-то моменты, которые никак не привязаны к ситуациям с чрезмерным использованием памяти. Если в момент создания снепшота не происходило выполнения неправильно написанной ресурсоёмкой функции, тогда куча не будет содержать никакой ценной информации о памяти, используемой этой функцией.
Для выявления подобных проблем мы рекомендуем два способа, которые помогли нам обнаружить виновников проблемы — функцию и переменную. Это — запись профиля выделения памяти и создание снепшотов на сервере, находящемся под серьёзной нагрузкой.
Если вы используете версию Node 6.3 или более позднюю, вы можете записать профиль выделения памяти через удалённый отладчик Chrome, запустив Node с уже упоминавшимся ключом --inspect . Это даст сведения о том, как отдельные функции используют память с течением времени.
Запись профиля выделения памяти
Ещё один вариант заключается в отправке множества одновременных запросов к вашему серверу и в создании множества снепшотов во время обработки этих запросов (предполагается, что сервер работает асинхронно, как результат, некоторые снепшоты могут оказаться гораздо больше других, что укажет на проблему). Мы бомбардировали сервер запросами и делали снепшоты. Некоторые из них оказались очень большими. Исследованием этих снепшотов можно заняться для выявления источника проблемы.
Итоги
Две вышеописанные проблемы с памятью заставили нас притормозить развитие нашего проекта, которое до этого шло очень быстро, и проанализировать производительность сервера. Теперь мы понимаем особенности производительности сервера на гораздо более глубоком уровне, чем раньше, и мы знаем, сколько времени нужно для нормального выполнения отдельных функций, и сколько памяти они используют. У нас появилось гораздо лучшее понимание того, какие ресурсы нам нужны при дальнейшем масштабировании проекта. И, что самое важное, мы перестали бояться проблем с памятью и перестали ожидать их появления в будущем.
Самый быстрый способ — увеличить количество памяти в Node.js. Начиная с версии v8 вы можете устанавливать ограничение в мегабайтах с помощью флага --max-old-space-size :
Вы можете установить любое ограничение, но не используйте всю доступную память, иначе может произойти крэш системы.
Аналогичного эффекта можно добиться с помощью другого флага:
▍Причины чрезмерного потребления памяти
Обратите внимание на скорости чтения данных с диска и записи данных на диск с предыдущей иллюстрации (колонки Disk Read и Disk Write ). А именно, тут можно видеть следующие показатели:
Такая разница в скоростях чтения из записи данных означает, что источник данных выдаёт их гораздо быстрее, чем приёмник может их принять и обработать. Компьютеру приходится хранить в памяти прочитанные фрагменты данных до момента их записи на диск. Как результат, мы и видим такие показатели использования памяти.
На моём компьютере эта программа выполнялась 3 минуты 16 секунд. Вот сведения о ходе её выполнения:
▍Пример №3. Приложение, заполняющее буфер данными, выполняющееся в контейнере, в котором файл подкачки не используется
Вот код, с которым мы будем здесь экспериментировать (это — тот же файл buffer_example_fill.js ):
На этот раз запустим контейнер, явным образом настроив особенности работы с файлом подкачки:
Временное решение проблемы
Параметр $SIZE задаётся в мегабайтах и, теоретически, может быть любым числом, которое имеет смысл на конкретном компьютере. В нашем случае был использован параметр 8000, который, с учётом особенностей работы сервера, позволил выиграть достаточно времени на исследования. Кроме того, мы увеличили динамическую память. Мы пользуемся Heroku, там это делается просто.
Также мы воспользовались сервисом Twilio, настроили его так, чтобы нас оповещали каждый раз, когда на сервер приходит запрос, требующий особенно много памяти. Это позволило нам наблюдать за запросом и перезапускать сервер после его завершения. Такое решение неидеально, но для того, чтобы наши пользователи не сталкивались с отказами, мы были готовы на всё, даже на круглосуточные дежурства без выходных.
Избегайте утечек памяти
В этой статье объясняется, как работает управление памятью в JavaScript, и как избежать большинства возможных утечек.
Её содержание сводится к тому, что большинство утечек, которые можно отследить, вызваны неудалёнными ссылками на объекты, которые больше не нужны. Это может случиться, когда вы забыли удалить interval, timer или чрезмерно используете глобальные переменные.
Обработка данных по частям
Иногда нужно обработать большой набор данных. Например, вы пишите программу, которая принимает данные из CSV файла, очищает их и добавляет в БД (это называется ETL: извлечение, трансформация, загрузка).
Если в такой ситуации программе начинает не хватать памяти, попробуйте разделить данные на несколько частей.
Подробнее о том, как сделать это в MongoDB в этом ответе на StackOverflow.
Что означает эта ошибка?
По умолчанию в Node.js установлен лимит памяти, который не позволяет программе занять слишком много памяти и уронить всю систему. Лимит отличается на разных версиях Node.js и архитектурах (32бита или 64бита).
How to verify and give right size?
This is basically stay in our engine v8 . below code helps you to understand the Heap Size of your local node v8 engine.
Steps to fix this issue (In Windows) -
- Open command prompt and type %appdata% press enter
- Navigate to %appdata% > npm folder
- Open or Edit ng.cmd in your favorite editor
- Add --max_old_space_size=8192 to the IF and ELSE block
Your node.cmd file looks like this after the change:
I just want to add that in some systems, even increasing the node memory limit with --max-old-space-size , it's not enough and there is an OS error like this:
In this case, probably is because you reached the max mmap per process.
You can check the max_map_count by running
and increas it by running
and fix it to not be reset after a reboot by adding this line
in /etc/sysctl.conf file.
Check here for more info.
A good method to analyse the error is by run the process with strace
The strace tip works, though note that if you're using Docker with the Node Alpine base image, you will have to install strace yourself.
Also note that strace produces a ton of output lines, which will obstruct your ability to see the regular log lines (unless you have some sort of filtering of the output). Any way to cut down on this noise to only show events relevant to std::bad_alloc errors?
Recently, in one of my project ran into same problem. Tried couple of things which anyone can try as a debugging to identify the root cause:
As everyone suggested , increase the memory limit in node by adding this command:
Here size-value i have defined for my application was 1536 (as my kubernetes pod memory was 2 GB limit , request 1.5 GB)
So always define the size-value based on your frontend infrastructure/architecture limit (little lesser than limit)
One strict callout here in the above command, use --max-old-space-size after node command not after the filename server/index.js .
If you have ngnix config file then check following things:
worker_connections: 16384 (for heavy frontend applications) [nginx default is 512 connections per worker , which is too low for modern applications]
use: epoll (efficient method) [nginx supports a variety of connection processing methods]
Remove all logging/tracking tools like APM , Kafka , UTM tracking, Prerender (SEO) etc middlewares or turn off.
Now code level debugging: In your main server file , remove unwanted console.log which is just printing a message.
Now check for every server route i.e app.get() , app.post() . below scenarios:
- data => if(data) res.send(data) // do you really need to wait for data or that api returns something in response which i have to wait for?? , If not then modify like this:
else part: if there is no error coming then simply return res.send(<>) , NO console.log here .
error part: some people define as error or err which creates confusion and mistakes. like this:
remove winston , elastic-epm-node other unused libraries using npx depcheck command.
In the axios service file , check the methods and logging properly or not like :
Save yourself from using stringify , parse etc on accessive large dataset. (which i can see in your above shown logs too.
Недавно в компании Reside Real Estate столкнулись с проблемами: в самые ответственные моменты начал падать Node.js-сервер. Подозрение пало на память. Сотрудники компании прибегли к временным мерам, что позволило избавить от неудобств пользователей, и занялись поисками источника проблем. В результате им удалось найти и устранить неполадки.
В этом материале они рассказывают о том, как искать и устранять ошибки, связанные с использованием памяти. А именно, речь пойдёт об утечках памяти, и о ситуациях, когда программы используют гораздо больше памяти, чем им на самом деле нужно. Этот рассказ поможет тем, кто столкнётся с чем-то похожим, сразу понять причину странного поведения сервера и быстро вернуть его в строй.
Итоги
Одной из целей написания этой статьи была демонстрация того, как легко можно писать плохие программы на Node.js, даже несмотря на то, что эта платформа предоставляет в распоряжение разработчика замечательные API. Уделив некоторое внимание этим API, можно улучшить качество серверных программных проектов.
▍ Утечка памяти
В информатике утечка памяти — это разновидность неконтролируемого использования ресурсов, которая возникает, когда программа неправильно управляет выделением памяти, в результате чего память, которая больше не нужна, не освобождается.
В низкоуровневых языках вроде C утечки памяти часто возникают в ситуации, когда память выделяют, например так: buffer = malloc(num_items*sizeof(double)); , но не освобождают после того, как память больше не нужна: free(buffer); .
В языках с автоматическим управлением освобождением памяти утечки возникают, когда к сущностям, которые больше не нужны, можно получить доступ из исполняющейся программы, или из некоего корневого объекта. В случае с JavaScript, любой объект, к которому можно обратиться из программы, не уничтожается сборщиком мусора, соответственно, место, которое он занимает в куче, не освобождается. Если размер кучи вырастет слишком сильно, возникнет ситуация нехватки памяти.
30 Answers 30
If I remember correctly, there is a strict standard limit for the memory usage in V8 of around 1.7 GB, if you do not increase it manually.
In one of our products we followed this solution in our deploy script:
I am developing with angular 4 and getting same issue, what should be yourFile.js file for angular app ?
If you want to increase the memory usage of the node globally - not only single script, you can export environment variable, like this:
export NODE_OPTIONS=--max_old_space_size=4096
Then you do not need to play with files when running builds like npm run build .
I'm getting the same error even after setting NODE_OPTIONS to 4096 or more. When I run the command npm run build , I see some processes running with commands like usr/bin/node --max_old_space_size=2048 . What could be the reason?
@AyeshWeerasinghe probably libraries or scripts that you are using or running have a hardcoded max_old_space_size parameter which overrides the exported env variable.
Just in case anyone runs into this in an environment where they cannot set node properties directly (in my case a build tool):
You can set the node options using an environment variable if you cannot pass them on the command line.
Can you please explain what you mean, when you say " . set the node options using an environment variable.. "?
@Keselme An environment variable is a variable that has been set on the server that all processes can read the data from. Open an SSH terminal to your server and type: MY_VAR=hello then type: echo $MY_VAR. You will see that it prints "hello" in the terminal. You've just set an environment variable and read it back.
Here are some flag values to add some additional info on how to allow more memory when you start up your node server.
1GB - 8GB
can one keep increasing it in powers of 2? should one set it larger than system memory? if not what's a good system memory to max-old-space-size ratio?
@HarryMoreno You can actually put in any number value you like. Doesn't have to be in power of 2. Not sure about the ratio though. It's only a max limit, it wont be using all the memory. I would just set it as high as you need then scale back if needed.
@HarryMoreno A good system memory to max-old-space-size ratio depends entirely on what else is running on your machine. You can increase it in powers of two - or you can use any number. You can set it larger than system memory - but you will hit swap issues.
I just faced same problem with my EC2 instance t2.micro which has 1 GB memory.
I resolved the problem by creating swap file using this url and set following environment variable.
Finally the problem has gone.
I hope that would be helpful for future.
Thanks. I'm now able to "yarn build" Strapi on my $5/mo Linode nanode instance after I created a 2GB swap file and added an "ENV NODE_OPTIONS=--max_old_space_size=1024" to my Dockerfile. Not sure the swap step was needed in my case but it can't hurt.
exactly, with small machines you may need to make it smaller rather than bigger, e.g. export NODE_OPTIONS=--max_old_space_size=1024
i was struggling with this even after setting --max-old-space-size.
Then i realised need to put options --max-old-space-size before the karma script.
also best to specify both syntaxes --max-old-space-size and --max_old_space_size my script for karma :
I had to cut it to --max-old-space-size=8192 --optimize-for-size --max_old_space_size=8192 --optimize_for_size and it worked
I encountered this issue when trying to debug with VSCode, so just wanted to add this is how you can add the argument to your debug setup.
You can add it to the runtimeArgs property of your config in launch.json .
See example below.
I had a similar issue while doing AOT angular build. Following commands helped me.
4k is not enough. developer was keep on 4k as static. good solution from developer. Also when I explore the npm page, I couldnt saw the info about change the limit value. Actually, There is a solution but didnt worked.
After running this i type npm run dev and it stopped there. it is not showing any progress not giving any error after this line > webpack-dev-server --config ./webpack.dev.config.js . It was playing statue so, project cannot be run.
I've faced this same problem recently and came across to this thread but my problem was with React App. Below changes in the node start command solved my issues.
Обзор рекомендаций
Предположим, Node.js-приложение выполняется в контейнере с установленным лимитом памяти. Если речь идёт о Docker, то для установки этого лимита могла быть использована опция --memory . Нечто подобное возможно и при работе с системами оркестрации контейнеров. В таком случае рекомендуется, при запуске Node.js-приложения, использовать опцию --max-old-space-size . Это позволяет сообщить платформе о том, какой объём памяти ей доступен, а так же учесть то, что этот объём должен быть меньше лимита, заданного на уровне контейнера.
Когда Node.js-приложение выполняется внутри контейнера, задавайте ёмкость доступной ему памяти в соответствии с пиковым значением использования активной памяти приложением. Это делается в том случае, если ограничения памяти контейнера можно настраивать.
Теперь поговорим о проблеме использования памяти в контейнерах подробнее.
Объяснение особенностей поведения Node.js-приложений в контейнерах
Контейнер наблюдает за одним важным показателем приложений, которые в нём выполняются. Это — RSS (resident set size). Этот показатель представляет некую часть виртуальной памяти приложения.
Более того, он представляет собой фрагмент памяти, которая выделена приложению.
Но и это ещё не всё. RSS — это часть активной памяти, выделенной приложению.
Активной может быть не вся память, выделенная приложению. Дело в том, что «выделенная память» не обязательно физически выделяется до тех пор, пока процесс не начнёт ей по-настоящему пользоваться. Кроме того, в ответ на запросы на выделение памяти от других процессов, операционная система может сбросить в файл подкачки неактивные фрагменты памяти приложения и передать освободившееся пространство другим процессам. А когда приложению снова понадобятся эти фрагменты памяти, они будут взяты из файла подкачки и возвращены в физическую память.
Показатель RSS указывает на объём активной и доступной приложению памяти в его адресном пространстве. Именно он влияет на принятие решения о принудительном завершении работы приложения.
▍ Снепшот кучи
«Утечка памяти» — это проблема, которая выражается в постоянно растущем размере кучи. В результате куча оказывается слишком большой для продолжения нормальной работы сервера. Поэтому в самом начале исследования нужно сделать несколько снепшотов (снимков состояния) кучи, с некоторым интервалом, и погрузиться в исследование этих снепшотов с использованием инструментов разработчика Chrome для того, чтобы понять, почему куча так велика и почему она растёт. Обратите внимание на то, что следует делать несколько снепшотов, через некоторое время, в результате можно будет изучить объекты, которые будут переходить из одного снепшота в другой. Эти объекты, вполне возможно, являются виновниками утечки памяти. Существует множество способов создать снепшот кучи.
Профилирование
Профилирование помогает обнаружить утечки памяти. На фронтенде это можно сделать в Chrome в Инструментах разработчика во вкладке Memory.
В Node.js начиная с версии 6.3.0 также можно использовать Chrome для отладки использования памяти.
Во-первых, запустите приложение в режиме проверки:
Затем откройте страницу в Chrome, введите адрес chrome://inspect и нажмите на кнопку Open dedicated DevTools for Node.
После этого откроется окно, в котором вы сможете подключиться к вашему Node.js приложению.
Решение 1. Копирование файлов с использованием потоков
Рассмотрим решение проблемы копирования огромного файла, о которой мы говорили выше. Это решение может быть основано на двух потоках и будет выглядеть следующим образом:
- Мы ожидаем появления очередного фрагмента данных в потоке для чтения.
- Записываем полученные данные в поток для записи.
- Отслеживаем ход операции копирования.
Мы ожидаем, что пользователь, запуская эту программу, предоставит ей два имени файла. Первое — это файл-источник, второе — имя его будущей копии. Мы создаём два потока — поток для чтения и поток для записи, перенося фрагменты данных из первого во второй. Тут имеются и некоторые вспомогательные механизмы. Они используются для наблюдения за процессом копирования и для вывода соответствующих сведений в консоль.
Мы пользуемся здесь механизмом событий, в частности, речь идёт о подписке на следующие события:
- data — вызывается при чтении фрагмента данных.
- end — вызывается при окончании чтения данных из потока для чтения.
- error — вызывается в случае возникновения ошибки в процессе чтения данных.
Однако тут есть одна проблема. Её можно выявить, если взглянуть на данные по использованию системных ресурсов различными процессами.
Данные об использовании системных ресурсов
Обратите внимание на то, что процесс node , выполнив копирование 88% файла, занимает 4.6 Гб памяти. Это очень много, такое обращение с памятью способно помешать работе других программ.
Ограничения памяти на разных версиях Node.js
Эти значения не объявлены официально, но с помощью небольшой программы можно получить такие значения для 64 битной архитектуры.
4GB памяти в куче будет достаточно для большинства случаев
Чтобы проверить лимит памяти вашей системы, создайте файл index.js и добавьте в него следующий код:
Example
Why size is 16000 in max-old-space-size?
Basically, it varies depends on the allocated memory to that thread and your node settings.
Лимит памяти Docker
По умолчанию контейнеры не имеют ограничений по ресурсам и могут использовать столько памяти, сколько позволяет им операционная система. У команды docker run имеются опции командной строки, позволяющие задавать лимиты, касающиеся использования памяти или ресурсов процессора.
Команда запуска контейнера может выглядеть так:
Обратите внимание на следующее:
- x — это лимит объёма памяти, доступной контейнеру, выраженный в единицах измерения y .
- y может принимать значение b (байты), k (килобайты), m (мегабайты), g (гигабайты).
Здесь лимит памяти установлен в 1000000 байт.
Для проверки лимита памяти, установленного на уровне контейнера, можно, в контейнере, выполнить следующую команду:
Поговорим о поведении системы при указании с помощью ключа --max-old-space-size лимита памяти Node.js-приложения. При этом данный лимит памяти будет соответствовать лимиту, установленному на уровне контейнера.
То, что в имени ключа называется «old-space», представляет собой один из фрагментов кучи, управляемой V8 (то место, где размещаются «старые» JavaScript-объекты). Этот ключ, если не вдаваться в детали, которых мы коснёмся ниже, контролирует максимальный размер кучи. Подробности о ключах командной строки Node.js можно почитать здесь.
В общем случае, когда приложение пытается использовать больше памяти, чем доступно в контейнере, его работа завершается.
В следующем примере (файл приложения называется test-fatal-error.js ) в массив list , с интервалом в 10 миллисекунд, помещают объекты MyRecord . Это приводит к бесконтрольному росту кучи, имитируя утечку памяти.
Обратите внимание на то, что все примеры программ, которые мы будем тут рассматривать, помещены в образ Docker, который можно загрузить с Docker Hub:
Вы можете воспользоваться этим образом для самостоятельных экспериментов.
Кроме того, можно упаковать приложение в контейнер Docker, собрать образ и запустить его с указанием лимита памяти:
Здесь ravali1906/dockermemory — это имя образа.
Теперь можно запустить приложение, указав лимит памяти для него, превышающий лимит контейнера:
Здесь ключ --max_old_space_size представляет собой лимит памяти, указываемый в мегабайтах. Метод process.memoryUsage() даёт сведения об использовании памяти. Значения показателей выражены в байтах.
Работа приложения в некий момент времени принудительно завершается. Происходит это тогда, когда объём использованной им памяти переходит некую границу. Что это за граница? О каких ограничениях на объём памяти можно говорить?
Рекомендации по настройке лимитов памяти контейнеров для тех случаев, когда этот параметр контролировать можно, а параметры Node.js-приложения — нет
- Запускайте приложение в режимах, позволяющих выяснить пиковые значения потребляемой им памяти.
- Проанализируйте показатель RSS. В частности, тут может, наряду с методом process.memoryUsage() , пригодиться и команда Linux top .
- При условии, что в контейнере, в котором планируется запускать приложение, ничего кроме него выполняться не будет, полученное значение можно использовать в качестве лимита памяти контейнера. Для того чтобы подстраховаться, рекомендуется увеличить его хотя бы на 10%.
Syntax
Итоги
В Node.js 12.x некоторые из рассмотренных здесь проблем решаются путём адаптивной настройки размера кучи, выполняемой в соответствии с объёмом доступной оперативной памяти. Этот механизм работает и при запуске Node.js-приложений в контейнерах. Но настройки могут и отличаться от настроек, используемых по умолчанию. Это, например, происходит в тех случаях, когда при запуске приложения использовался ключ --max_old_space_size . Для таких случаев всё вышесказанное остаётся актуальным. Это говорит о том, что тот, кто запускает Node.js-приложения в контейнерах, должен внимательно и ответственно относиться к настройкам памяти. Кроме того, знание стандартных ограничений на использование памяти, довольно консервативных, позволяет улучшить работу приложений благодаря обдуманному изменению этих ограничений.
Программы, в ходе работы, пользуются оперативной памятью компьютеров. На JavaScript, в среде Node.js, можно писать серверные проекты самых разных масштабов. Организация работы с памятью — это всегда непростая и ответственная задача. При этом, если в таких языках, как C и C++, программисты довольно плотно занимаются управлением памятью, в JS имеются автоматические механизмы, которые, как может показаться, полностью снимают с программиста ответственность за эффективную работу с памятью. Однако на самом деле это не так. Плохо написанный код для Node.js может помешать нормальной работе всего сервера, на котором он выполняется.
В материале, перевод которого мы сегодня публикуем, речь пойдёт об эффективной работе с памятью в среде Node.js. В частности, здесь будут рассмотрены такие концепции, как потоки, буферы и метод потоков pipe() . В экспериментах будет использован Node.js v8.12.0. Репозиторий с кодом примеров можно найти здесь.
Анализ снепшотов
Теперь у нас есть данные, которые вполне могут помочь найти виновников проблем с памятью. В частности, рассмотрим анализ ситуации, в которой размеры последовательно сделанных снепшотов растут. Вот один из снепшотов, который загружен на вкладке Memory инструментов разработчика Chrome.
Исследование утечки памяти — все функции указывают на наш сервис электронной почты
Показатель Retained Size — это размер памяти, освобождённой после того, как объект удалён вместе со своими зависимыми объектами, которые недостижимы из корневого объекта.
Анализ можно начать с сортировки списка по убыванию по параметру Retained Size, после чего приступить к исследованию больших объектов. В нашем случае имена функций указали нам на ту часть кода, которая вызывала проблему.
Так как мы были уверены в том, что перед нами утечка памяти, мы знали, что исследование стоит начать с поиска переменных с неподходящей областью видимости. Мы открыли файл index.js почтовой службы и тут же обнаружили переменную уровня модуля в верхней части файла.
Мы со всем этим разобрались, внесли необходимые изменения, протестировали проект ещё несколько раз и исправили в итоге утечку памяти.
Вторую проблему отлаживать было сложнее, но тут сработал тот же подход. Ниже показан профиль выделения памяти, который мы записали с использованием инструментов разработчика Chrome и ключа Node --inspect .
Поиск виновников чрезмерного использования памяти
Так же как при анализе данных в ходе поиска утечки памяти, многие имена функций и объектов с первого взгляда узнать не удаётся, так как находятся они на более низком уровне, чем код, который пишут для Node.js. В подобной ситуации следует, встретив незнакомое имя, записать его.
Профиль выделения памяти привёл нас к одной из функций, recordFromSnapshot , она стала хорошей отправной точкой. Наше исследование снепшота кучи, которое не особенно отличалось от исследования, выполняемого при поиске утечки памяти, позволило обнаружить очень большой объект target . Это была переменная, объявленная внутри функции recordFromSnapshot . Эта переменная осталась от старой версии приложения, она была больше не нужна. Избавившись от неё, мы исправили ситуацию с чрезмерным использованием памяти и ускорили выполнение процесса, которое раньше занимало 40 секунд, до примерно 10 секунд. При этом процессу не требовалась дополнительная память.
▍Потоки
Потоки, если обратиться к языку научной фантастики, можно сравнить с порталами в другие миры. Существует четыре типа потоков:
- Поток для чтения (из него можно читать данные).
- Поток для записи (в него можно отправлять данные).
- Дуплексный поток (он открыт и для чтения из него данных, и для отправки данных в него).
- Трансформирующий поток (особый дуплексный поток, позволяющий обрабатывать данные, например — сжимать их или проверять их корректность).
Другими словами, нам, для решения задачи копирования большого файла, нужен какой-то механизм, позволяющий не перегружать систему.
Потоки и буферы (по материалам документации Node.js)
На предыдущей схеме показаны два типа потоков — потоки для чтения (Readable Streams) и потоки для записи (Writable Streams). Метод pipe() — это очень простой механизм, позволяющий прикреплять потоки для чтения к потокам для записи. Если вам пока вышеприведённая схема не особенно понятна — ничего страшного. После разбора нижеследующих примеров вы легко с ней разберётесь. В частности, сейчас мы рассмотрим примеры обработки данных с использованием метода pipe() .
Читайте также: