Потребление памяти при сборке Ember-cli может привести к сбою (OOM) на минимальном размере экземпляра

Во время обновления наибольшая нагрузка на память (ОЗУ + swap) приходится на процесс ‘ember’. Мне кажется, что с каждым обновлением он становится всё больше, и уже приближается к тому моменту, когда не сможет запускаться на компьютерах с минимально рекомендуемой конфигурацией.

Стоит разобраться в этом до того, как возникнет сбой. (Надеюсь, что по соображениям стоимости ответом не станет увеличение минимально рекомендуемых требований. Увеличение swap-пространства помогло бы, если позволяет место на диске. В принципе, можно временно перейти на более дорогой экземпляр с большим объёмом ОЗУ.)

Я запускаю два форума небольшого размера на небольших экземплярах — оба, насколько я знаю, соответствуют минимальным рекомендуемым требованиям. В обоих случаях ОЗУ + swap = 3 ГБ. В одном случае это экземпляр Digital Ocean с 1 ГБ ОЗУ и 2 ГБ swap, в другом — экземпляр Hetzner с 2 ГБ ОЗУ и 1 ГБ swap.

Вот три снимка процесса ember на машине DO, полученные с помощью команды ps auxc:

USER       PID %CPU %MEM      VSZ    RSS TTY   STAT START   TIME COMMAND
1000     10342 87.7 65.1 32930460 657936 ?     Rl   16:57   2:23 ember

USER       PID %CPU %MEM      VSZ    RSS TTY   STAT START   TIME COMMAND
1000     10342 84.9 60.7 43572204 612668 ?     Rl   16:57   2:57 ember

USER       PID %CPU %MEM      VSZ    RSS TTY   STAT START   TIME COMMAND
1000     10342 81.2 55.2 43405220 557128 ?     Rl   16:57   3:40 ember

Очевидно, что размер процесса в 43 ГБ не полностью присутствует в виртуальной памяти, так как у нас доступно только 3 ГБ. Использование 65% размера ОЗУ для RSS впечатляет, но само по себе не является проблемой. Количество свободной памяти и свободного swap показывает, что машина находится на грани состояния нехватки памяти (OOM), что, скорее всего, приведёт к завершению работы какого-либо процесса и некорректному завершению обновления.

Вот вывод команды free в виде моментального снимка:

# free
              total        used        free      shared  buff/cache   available
Mem:        1009140      863552       72768        6224       72820       34868
Swap:       2097144     1160628      936516

Чтобы попытаться зафиксировать ситуацию в момент, максимально близкий к сбою, я использовал команду vmstat 5:

# vmstat 5 5
procs -----------memory----------    ---swap-- -----io----  -system-- ------cpu-----
 r  b   swpd    free   buff  cache    si    so    bi    bo   in    cs us sy id wa st
 3  0 1392140  61200  11632  76432    41    32   117    93    0     1  2  1 97  0  0
 1  1 1467220  63416    324  67284  8786 20499 13178 20567 2539  8924 77 13  0 10  0
 0  2 1593340  57916   1096  53832 24262 46868 29986 46889 5377 18534 44 22  0 34  0
 4  0 1155632 120680   2772  86280 39111 35424 54768 37824 6987 25174 38 27  0 35  0
 3  0 1102988  74096   2852  85276 11261   246 12610   271 1879  6365 86  6  0  8  0

Вы заметите множество переключений контекста (cs), большую активность диска (bi, bo) и высокую активность swap (si, so), но самое важное — это использование swap до 1,6 ГБ при свободной памяти, упавшей до 60 МБ, и использовании буферов всего 54 МБ. Это означает, что из доступных 3 ГБ виртуальной памяти занято около 2,6 ГБ. Это 87% от ёмкости. (Ситуация может быть даже хуже, так как мы делаем выборку только каждые 5 секунд.)

Обратите внимание, что ситуация вызывала беспокойство (при использовании около 2 ГБ, то есть не так близко к критическому состоянию, как сегодня) ещё в августе, когда я обновлялся:

# vmstat 5 5
procs -----------memory----------    ---swap-- -----io----  -system-- ------cpu-----
 r  b    swpd   free   buff  cache    si    so    bi    bo   in    cs us sy id wa st
 3  0  700404  62740   1956  48748    35    29   108    92    3     8  2  1 96  0  1
 1  0  741000  65996   1880  44360  3708 11190  3982 11191  643  1437 92  4  0  3  1
 1  0  834836  70452   1480  53856   528 18969  4274 18974  532  1575 93  6  0  1  0
 4  1 1010144  82192   4644  44400 30065 38803 35455 39946 4432 19267 28 26  0 39  7
 1  0  644116 307764   1644  55348 24406 21154 27724 21945 2551  8672 52 22  0 21  6

Привет, @Ed_S! Какую версию Discourse вы использовали для этих тестов? Мы регулярно обновляем ember-cli и его дополнения, поэтому я просто хочу убедиться, что мы говорим об одном и том же.

Также, сколько ядер процессора имеют ваши виртуальные машины? Одно? (вы можете проверить это, выполнив команду lscpu в консоли)

Чтобы мы все работали с одними и теми же данными, пожалуйста, попробуйте выполнить:

/var/discourse/launcher enter app
cd /var/www/discourse/app/assets/javascripts/discourse
apt-get update && apt-get install time
NODE_OPTIONS='--max-old-space-size=2048' /usr/bin/time -v yarn ember build -prod

На моем тестовом Droplet (1 CPU, 1 ГБ ОЗУ, 2 ГБ swap) я вижу следующее:

Command being timed: "yarn ember build -prod"
	User time (seconds): 369.74
	System time (seconds): 22.62
	Percent of CPU this job got: 81%
	Elapsed (wall clock) time (h:mm:ss or m:ss): 8:02.73
	Average shared text size (kbytes): 0
	Average unshared data size (kbytes): 0
	Average stack size (kbytes): 0
	Average total size (kbytes): 0
	Maximum resident set size (kbytes): 774912
	Average resident set size (kbytes): 0
	Major (requiring I/O) page faults: 253770
	Minor (reclaiming a frame) page faults: 1158920
	Voluntary context switches: 519269
	Involuntary context switches: 383328
	Swaps: 0
	File system inputs: 7521784
	File system outputs: 316304
	Socket messages sent: 0
	Socket messages received: 0
	Signals delivered: 0
	Page size (bytes): 4096
	Exit status: 0

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

Спасибо, @david — ценю, что Ember — это отдельная сущность.

Я только что выполнил эти команды.

# /var/discourse/launcher enter app
Обнаружена архитектура x86_64.

ПРЕДУПРЕЖДЕНИЕ: Мы собираемся начать загрузку базового образа Discourse
Этот процесс может занять от нескольких минут до часа в зависимости от скорости вашей сети

Пожалуйста, будьте терпеливы

2.0.20220720-0049: Pulling from discourse/base
Digest: sha256:7ff397003c78b64c9131726756014710e2e67568fbc88daad846d2b368a02364
Status: Downloaded newer image for discourse/base:2.0.20220720-0049
docker.io/discourse/base:2.0.20220720-0049

Это установка в продакшене, и по состоянию на вчерашний день она была актуальной. В настоящее время система сообщает:

Installed 2.9.0.beta12 (8f5936871c)

Это экземпляр с одним процессором, как и у вас: 1 ГБ оперативной памяти и 2 ГБ подкачки.

Результат команды time:

Done in 303.21s.
	Command being timed: "yarn ember build -prod"
	User time (seconds): 222.71
	System time (seconds): 17.17
	Percent of CPU this job got: 78%
	Elapsed (wall clock) time (h:mm:ss or m:ss): 5:04.15
	Average shared text size (kbytes): 0
	Average unshared data size (kbytes): 0
	Average stack size (kbytes): 0
	Average total size (kbytes): 0
	Maximum resident set size (kbytes): 702292
	Average resident set size (kbytes): 0
	Major (requiring I/O) page faults: 348190
	Minor (reclaiming a frame) page faults: 1152689
	Voluntary context switches: 617736
	Involuntary context switches: 774189
	Swaps: 0
	File system inputs: 5001936
	File system outputs: 318280
	Socket messages sent: 0
	Socket messages received: 0
	Signals delivered: 0
	Page size (bytes): 4096
	Exit status: 0

Непосредственно перед этим я обновил хост и перезагрузил его, поэтому всё в контейнере было перезапущено заново.

Худший показатель использования памяти, зафиксированный утилитой vmstat, запущенной в другом окне:

# vmstat 1
procs  -----------memory----------    ---swap--  -----io----   -system-- ------cpu-----
 r  b    swpd   free   buff  cache    si     so    bi     bo    in    cs us sy id wa st
 2  0  704000 136044  24136 158144  1517   3503  8256   4377   886  3564 43  8 43  6  0
...
 5  0 1451436  71604   1248  50196 55016 110236 73204 121060 13152 45971 29 60  0 10  1

Похоже, мы явно увеличили допустимый кучу Node с 500 МБ до 2 ГБ — возможно, это слишком большой шаг, и 1,5 ГБ было бы лучше:

Стоит отметить, что Ember — не единственное, что работает на машине, и мы упираемся в глобальный лимит оперативной памяти + своп. Поэтому история работы машины и потребности всех остальных запущенных процессов играют роль. Мой перезапуск, возможно, помог здесь достичь более низкого пикового уровня по сравнению с вчерашним днем.

Указанный выше запрос на слияние упоминался в
Не удалось обновить экземпляр Discourse до 15 февраля 2022 года,
где также отмечается, что у кого-то возникла нехватка памяти, которая была решена перезагрузкой.

Неудобно, что команда time не сообщает пиковое использование памяти. Возможно, на машине с хотя бы 3 ГБ оперативной памяти и без свопа счетчик RSS покажет пиковое использование Ember. Или, возможно, мы могли бы использовать другую тактику — несколько вариантов описаны здесь, а также есть некоторые идеи здесь.

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

Причина, по которой мы добавили этот флаг, заключалась в том, что собственный OOM-киллер Node убивал сборку — 500 МБ было недостаточно. Готов попробовать настроить его на 1,5 ГБ — я только что проверил это на своём дроплете, и всё работает нормально. На самом деле, похоже, что даже 1,0 ГБ достаточно.

Я попытался отслеживать использование памяти с разными значениями max_heap:

(while(true); do (free -m -t | grep Total | awk '{print $3}') && sleep 0.5; done) | tee 1000mb.csv

Это показывает использование памяти во время сборки:

Разница во времени сборки была незначительной, но лимиты 1 ГБ и 1,5 ГБ явно приводят к меньшему общему использованию. Как и ожидалось, вывод команды time показывает значительно меньше «крупных ошибок страниц» при более низком лимите для Node.

Любопытно, что разница между 1,5 ГБ и 1 ГБ так мала… :face_with_monocle:

В любом случае, я согласен, что снижение лимита — хорошая идея. Чтобы убедиться, что это не повлияет на производительность сборки на машинах с более высокими характеристиками, я думаю, мы должны переопределять лимит только тогда, когда знаем, что он слишком низкий. В противном случае мы можем позволить Node использовать значение по умолчанию.

Вот PR — мы постараемся как можно скорее его объединить. Спасибо, что подняли этот вопрос @Ed_S!