GC работает, контейнер падает

public

Автор: Нина Пакшина · Московский клуб программистов


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

Доклад объясняет, почему автоматическое управление памятью не отменяет ограничений среды. Обычная настройка GC связана с относительным ростом кучи, а контейнер ограничен абсолютным объёмом памяти. Между этими двумя условиями может оказаться достаточно места для аварийного завершения.

Основной инструмент разбора — не только схема работы сборщика, но и небольшие программы с трассировкой. На них видно, как настройки меняют частоту сборок и потребление ресурсов, а затем — почему мягкий лимит памяти полезен, но не всесилен.

Кому смотреть: Go-разработчикам, чьи сервисы работают в контейнерах и периодически превышают память при всплесках нагрузки или пересечении фоновых задач.

Из этого можно взять в работу: сопоставить бюджет памяти контейнера с настройками Go runtime и проверить не только обычную нагрузку, но и одновременное выполнение наиболее затратных операций.


GOGC задаёт компромисс между ростом кучи и затратами процессора на сборку. В показанной программе при значении 100 получилось 16 сборок, при 10 — 38, при 1000 — одна. Это результаты конкретного эксперимента: они показывают направление изменений, а не обещают универсальное ускорение от увеличения параметра.

Относительная настройка не задаёт абсолютного бюджета. Если после сборки остаётся большая живая куча, дальнейший допустимый рост может оказаться несовместим с ограничением контейнера. При неравномерной нагрузке размер этой живой кучи тоже меняется — красивой периодической картины из учебного примера уже нет.

Для учёта бюджета runtime Нина показывает GOMEMLIMIT. В демонстрации контейнер ограничен 10 МБ: исходный запуск завершается аварийно, а после задания мягкого лимита в 8 МБ программа заканчивает работу. Эти значения нужны для воспроизведения случая, а не как рецепт настройки сервиса.

Почему лимит мягкий? Сборщик не может освободить объекты, которые приложение продолжает удерживать. Если пытаться любой ценой соблюдать слишком тесный бюджет, сборки начнут повторяться почти непрерывно, мешая полезной работе. Поэтому runtime ограничивает затраты CPU на GC и допускает превышение установленного значения.

По словам автора, после введения мягкого лимита повторных падений в её случае не наблюдалось. Но это не гарантия защиты от любого OOM: бюджет Go runtime не равен всему потреблению памяти процесса, а удержание ненужных данных настройкой не исправить. Доклад 2023 года полезен именно этим различием между управлением сборкой и управлением объёмом работы приложения.