RE:NODE

Руководства11 мин чтения

Утечки памяти игрового сервера: найти и исправить

Как отличить настоящую утечку памяти от роста мира и заполненного heap, найти виновный плагин или мод на серверах Java, Lua, Unity и Source и сдерживать её до исправления.

0 прочтений

У игрового сервера с утечкой памяти нижняя граница потребления растёт день за днём и не опускается, пока процесс не перезапустится. Это единственный надёжный признак. Память, которая растёт несколько часов, а потом выходит на плато, - это кэш или heap, заполняющийся до настроенного размера, а память, которая растёт вместе с исследованным миром, - это рост, а не утечка. Убедившись, что утечка настоящая, ищите её измерениями внутри среды выполнения - сводка heap в Java, collectgarbage("count") в Lua, монитор ресурсов в FiveM, дамп handle в SourceMod - и отключением половин плагинов, пока рост не исчезнет. Пока утечка не исправлена, рестарт по расписанию и запас памяти не дадут ей превратиться в вылет в пиковое время.

Утечка, рост или кэш: три похожих графика#

График памяти в панели показывает одну и ту же растущую линию для трёх очень разных ситуаций. Различить их - это весь первый шаг.

КартинаЧто вы видитеЧто этоЧто делать
Заполнение и платоРастёт несколько часов после рестарта, потом ровноHeap или кэш достигает своего размераНичего; подбирайте тариф под плато
Ступенчатый ростРастёт, когда игроки исследуют или строят, иначе ровноМир становится большеОжидаемо; ограничения мира или больше памяти
Растущее дноМинимум каждый день выше, независимо от активностиУтечкаНайти; перезапускать, пока не найдёте

Проверка такая: читайте график за дни, а не минуты, и смотрите на провалы - тихие часы, когда никого нет онлайн. Настоящая утечка продолжает расти даже ночью или как минимум никогда не отдаёт назад то, что забрала. Рост следует за активностью игроков: ночь без игроков - ровная ночь. Заполненный heap растёт один раз и останавливается. Другие формы на том же графике разобраны в статье о том, как читать график нагрузки сервера.

Две системы делают первую картину похожей на утечку для тех, кто с ней ещё не сталкивался.

  • Java не отдаёт память обратно. Сервер Minecraft или Project Zomboid, запущенный с -Xmx6G, рано или поздно будет использовать почти 6 ГБ плюс немного накладных расходов и останется на этом уровне. Сборщик мусора освобождает память внутри heap, но редко возвращает её операционной системе. Это нормально, и именно поэтому руководство по RAM для Minecraft предостерегает от того, чтобы отдавать Java всё.
  • Серверы на Unity и Unreal наращивают heap ступенями и переиспользуют его. Память сервера Valheim или Palworld после оживлённого вечера выше, чем после тихого, и может не опуститься обратно, при этом ничего не утекает.

Почему утечка превращается в вылет#

Утечка - медленная проблема с внезапным концом. Каждый день дно выше, пик выше, и в какой-то момент пик оживлённого вечера встречается с лимитом памяти тарифа.

На контейнерном хостинге возможны два финала. Исходный Pterodactyl может позволить серверу уйти в swap - он останется жив, но станет мучительно медленным. В RE:NODE ядро останавливает контейнер на лимите, и он перезапускается начисто, а не уходит в swap, - восстанавливается быстро, но с точки зрения игры это остановка без сохранения, так что всё с момента последнего автосохранения теряется. Это замечает и наблюдатель за вылетами: каждые две минуты он проверяет серверы, у которых аптайм пошёл назад, игнорирует рестарты, которые вы запросили сами, и после трёх незапрошенных рестартов за час публикует предупреждение на странице сервера и автоматически открывает тикет. Сервер с утечкой обычно до такой частоты не доходит, потому что утечке нужны дни, чтобы снова заполнить память, а вот сервер с утечкой, который циклически падает у потолка, дойдёт. Подробности - в статье о том, почему ваш игровой сервер постоянно перезапускается, а сторона ядра - в статье о swap в Linux и OOM killer.

Рестартдно сбрасываетсяДни аптаймадно растётОживлённый вечерпик у лимитаОстановка OOMбез сохраненияПотерянный прогресспосле автосейва
Как медленная утечка губит вечер

Как правильно измерять#

Прежде чем кого-то обвинять, соберите цифры.

  1. Перезапустите сервер и запишите память после того, как он закончит загрузку и устаканится, - через пять-десять минут.
  2. Записывайте дно ежедневно в течение трёх-пяти дней: самое низкое значение в тихие часы.
  3. Отмечайте, что менялось в эти дни: игроки, новые постройки, новые плагины, события.
  4. Посчитайте наклон. 150 МБ в день на тарифе с 6 ГБ дают вам месяц; 800 МБ в день - неделю.

Если у вас есть доступ к командной строке на машине, резидентная память процесса лежит в /proc:

bash
$ grep VmRSS /proc/$(pgrep -f valheim_server)/statusVmRSS:   3145728 kB

Одна оговорка насчёт цифр контейнера. Учёт памяти контейнера может включать файловый кэш - страницы файлов игры, которые ядро держит в памяти, потому что их недавно читали. Этот кэш освобождаемый и утечкой не является, хотя может попадать в сырые цифры cgroup. Лучшая мера - собственная резидентная память процесса или отчёт самой среды выполнения.

Поиск утечки на серверах Java#

Minecraft и Project Zomboid работают на JVM, а у Java лучшие инструменты среди всех игровых сред выполнения.

Вопрос в Java не в том, «много ли использует процесс» - он будет использовать свой heap, - а в том, «растут ли живые данные после сборки мусора». На Paper на этот вопрос отвечает spark прямо из игры:

code
/spark heapsummary/spark gc

heapsummary перечисляет классы, занимающие больше всего памяти, по количеству экземпляров и размеру. Снимите одну сводку вскоре после рестарта и другую через день. Класс, число экземпляров которого выросло с тысяч до миллионов, с именем пакета плагина, - ваш подозреваемый. /spark gc показывает поведение сборки мусора; heap, почти заполненный сразу после каждой сборки, означает, что вырос набор живых данных. Как читать вывод, объясняется в руководстве по профайлеру spark.

Вне игры ту же работу делают собственные инструменты JDK:

bash
$ jcmd $(pgrep -f paper.jar) GC.class_histogram | head -25$ jstat -gcutil $(pgrep -f paper.jar) 10s

Гистограмма содержит ту же информацию, что и heapsummary. jstat -gcutil каждые десять секунд печатает, насколько заполнено каждое поколение heap; смотрите на столбец старого поколения сразу после полных сборок. Если за часы он ползёт вверх, живые данные растут. Полный дамп heap (jcmd <pid> GC.heap_dump /tmp/heap.hprof) можно открыть в Eclipse MAT, чтобы увидеть, что удерживает объекты, но он приостанавливает сервер и пишет файл размером с heap - снимайте его в тихое время.

Обычные виновники в Java - плагины, которые держат ссылки на игроков, миры или чанки после их выгрузки: словари по игрокам, которые не очищаются при выходе, слушатель, сохраняющий каждое событие, кэш без ограничения размера. Плагины прогрузки чанков и некоторые рендереры карты мира держат в памяти чанки, которые сервер иначе выгрузил бы. В гистограмме это видно как рост объектов миров или чанков.

Поиск утечки в Lua: Garry's Mod, FiveM, Don't Starve Together#

Lua сама сообщает размер своего heap. В Garry's Mod, из консоли сервера:

code
lua_run print(collectgarbage("count"))

Число - это килобайты, занятые Lua-состоянием сервера. Запишите его после рестарта, через несколько часов и через сутки. Если оно стабильно растёт, какой-то аддон хранит таблицы, которые никогда не освобождает, - обычно это таблицы по игрокам с ключом в виде сущности игрока, таймеры, создаваемые при каждом спавне и никогда не удаляемые, или хуки, добавляемые снова и снова. Удаление аддонов половинами с наблюдением за этим числом несколько часов сужает круг гораздо быстрее, чем наблюдение за общей памятью процесса.

FiveM показывает память по ресурсам. В консоли F8 клиента resmon 1 открывает монитор ресурсов со временем CPU и памятью каждого работающего ресурса с точки зрения этого клиента, а собственная команда сервера profiler записывает серверную стоимость ресурсов. Ресурс, у которого столбец памяти стабильно растёт, пока остальные стоят на месте, и есть утечка. Как это читать, рассказано в статье о производительности сервера FiveM и resmon.

Don't Starve Together и другие игры с модами на Lua следуют тому же принципу: память Lua принадлежит модам, так что утечка - почти всегда мод, который хранит состояние по игрокам, по сущностям или по дням и никогда его не очищает.

Поиск утечки на серверах Unity, Unreal и Source#

Игры на Unity

Valheim, Rust, 7 Days to Die и Unturned работают на Unity, обычно со средой выполнения Mono для модов. Их управляемый heap растёт и редко уменьшается, так что высокое, но ровное значение - это нормально. Утечки здесь в основном от модов: плагин BepInEx в Valheim, плагин Oxide или Carbon в Rust, мод на Harmony в 7 Days to Die. Rust даёт кое-какие инструменты в консоли - gc.collect принудительно запускает сборку, а фреймворки плагинов показывают время хуков по каждому плагину, что указывает на проблемные плагины, - но учёт памяти по плагинам ограничен. Надёжный метод - деление пополам.

В некоторых играх на Unity есть и рост, вызванный миром, который похож на утечку: количество сущностей в Rust растёт в течение вайпа по мере того, как игроки строят, а память 7 Days to Die растёт с числом загруженных регионов. Сравните с количеством сущностей или регионов, прежде чем винить мод.

Игры на Unreal Engine

Palworld, ARK и другие серверы на Unreal управляют нативной памятью. Утечки здесь обычно в самой игре, а не в модах, и по-настоящему их исправляют только патчи разработчика. Известный пример - ранние релизы Palworld, где память росла с аптаймом до рестарта. Если ваш сервер на Unreal показывает растущее дно без установленных модов, проверьте патчноуты разработчика и сообщения сообщества, прежде чем тратить время на деление пополам, и сдерживайте утечку рестартами. Именно этой игре посвящена статья о памяти сервера Palworld.

Source и SourceMod

SourceMod отслеживает handle - ссылки, которые плагины держат на таймеры, файлы, запросы к базе данных и структуры данных. Плагин, который открывает handle и никогда их не закрывает, течёт, и SourceMod замечает это по порогу, записывая в свой лог ошибок MEMORY LEAK DETECTED IN PLUGIN с именем файла плагина, после чего выгружает плагин. Количество handle можно посмотреть и напрямую:

code
sm_dump_handles handles.txt

Дамп перечисляет handle по плагинам-владельцам. Снимите один после рестарта и другой через день; плагин, у которого счётчик продолжает расти, и есть утечка. Управление плагинами разобрано в статье о плагинах SourceMod для Team Fortress 2.

Деление пополам, когда инструменты никуда не указывают#

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

  1. Сделайте backup сервера, конфигов и данных плагинов.
  2. Отключите половину плагинов или модов, которые можно убрать, не ломая сохранения. Контентные моды, от которых зависит мир, оставьте; их проверяйте последними и на копии.
  3. Дайте поработать целые сутки и запишите дно в тот же тихий час.
  4. Если наклон исчез, утечка в той половине, которую вы убрали; верните её и уберите другую. Если остался - она в той половине, что ещё работает.
  5. Повторяйте, пока не останется один плагин. Двадцать плагинов - это около пяти раундов, рабочая неделя, потому что каждая проверка требует суток.

Это медленно, так что по возможности делайте это на копии. Как держать такую копию, рассказано в статье о тестовом сервере рядом с рабочим, а тот же метод деления пополам применительно к вылетам описан в статье что делать, когда обновление мода всё сломало. Найдя виновника, проверьте, нет ли обновления, сообщите автору, приложив цифры до и после, или замените плагин.

Как сдерживать утечку, пока её не исправили#

Большинство утечек исправляете не вы; их рано или поздно исправляет автор плагина или разработчик игры. А пока:

  • Ставьте рестарт по расписанию до того, как дно дойдёт до опасной зоны. Если дно растёт на 500 МБ в день, а у вас 2 ГБ запаса над обычным пиком, достаточно рестарта раз в два дня - ежевечерний не нужен. Как выбирать периодичность по графику, а не по привычке, объясняет статья о полезных расписаниях перезапуска.
  • Сохраняйтесь перед каждым рестартом. Поставьте команду сохранения игры за минуту до рестарта в том же расписании. Рестарт, перед которым мир сохранён, никому ничего не стоит.
  • Сократите интервал автосохранения, пока утечка существует, чтобы незапланированная остановка у потолка стоила минут. Настройки для каждой игры - в статье об интервалах автосохранения игровых серверов.
  • Держите запас. У сервера, обычный пик которого в нескольких сотнях мегабайт от лимита, нет запаса на утечку. Переход на тариф выше в RE:NODE меняет лимит на уже существующем сервере, без пересоздания.

В RE:NODE вкладка Schedules выполняет упорядоченные задачи с задержками - консольную команду, затем действие с питанием - по cron-выражению, так что «сохранить, подождать минуту, перезапустить» каждую вторую ночь в 05:00 - это одно расписание.

FAQ#

Нормально ли, что RAM моего сервера постоянно растёт?

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

Почему мой сервер Minecraft использует всю RAM, которую я ему выделил?

Потому что Java заполняет heap до -Xmx и редко возвращает память операционной системе. Это не утечка. Прежде чем беспокоиться, проверьте через heapsummary в spark или jstat, растут ли живые данные heap после сборки мусора.

Исправит ли ежедневный рестарт утечку памяти?

Он её сдерживает, но не исправляет. Утечка сбрасывается вместе с процессом и начинается заново. Используйте рестарт, чтобы держать сервер подальше от лимита, пока вы ищете и устраняете причину.

Поможет ли больше RAM от утечки памяти?

Это выигрывает время, пропорциональное дополнительной памяти, делённой на суточный рост. В краткосрочной перспективе это может быть правильным ответом, но утечка всё равно рано или поздно дойдёт до нового лимита, если сервер раньше не перезапустится.

Как найти, какой плагин течёт?

Сначала используйте собственные инструменты среды выполнения - spark или jcmd для Java, collectgarbage("count") для Lua, resmon для FiveM, sm_dump_handles для SourceMod. Если они не указывают на один плагин, отключайте плагины половинами и каждый раз измеряйте дно памяти в течение суток.


Комментарии

Полностью анонимно: без аккаунта, без почты, без cookie. Мы храним имя, которое вы ввели, текст и время - больше ничего. Количество ссылок ограничено, разметка не отображается.

0/2000