Как нагрузочным тестом отличить утечку памяти от штатного роста кэша?
Нужно наблюдать не только объём занятой памяти, но и её поведение после снижения нагрузки и очистки кэша. При утечке недоступная для повторного использования память продолжает расти между одинаковыми циклами нагрузки, а после возврата к исходной нагрузке и сборки мусора не возвращается к устойчивому базовому уровню. Рост штатного кэша обычно ограничивается политикой хранения и стабилизируется либо уменьшается после вытеснения или очистки объектов.
Долгоживущие серверные процессы стали одной из причин, по которым простого контроля средней загрузки CPU недостаточно. Сервис может отвечать быстро в коротком тесте, но постепенно исчерпать память из-за неосвобождённых объектов или неограниченного кэша.
Нагрузочное тестирование применяют не только для измерения пропускной способности, но и для обнаружения таких деградаций во времени. Ключевая идея — проверять устойчивость состояния системы после повторяющихся циклов нагрузки, а не оценивать память по одной точке.
Рост потребления памяти сам по себе не доказывает утечку. Кэш, буферы, пулы объектов и внутренние структуры сборщика мусора могут законно занимать память, пока система не достигнет рабочего равновесия.
Ошибочный вывод приводит к разным последствиям. Если принять кэш за утечку, можно без необходимости уменьшить его размер и ухудшить задержки из-за падения коэффициента попаданий. Если принять утечку за кэш, сервис может закончить работу из-за нехватки памяти после нескольких часов или дней эксплуатации.
Нужно построить длительный тест с повторяемыми этапами: прогрев, стабильная нагрузка, снижение нагрузки или пауза, затем повтор той же последовательности. На каждом этапе следует собирать объём памяти процесса, размер управляемой кучи, внешнюю память, частоту и длительность сборок мусора, количество активных объектов, ошибки нехватки памяти и задержки.
У штатного кэша обычно есть наблюдаемая граница: его размер стабилизируется, начинает вытеснять старые записи или уменьшается после очистки. Повтор одинакового сценария после очистки должен приводить примерно к тому же профилю памяти. Точная форма кривой зависит от политики кэша и поведения среды выполнения, поэтому одного факта возврата памяти операционной системе недостаточно.
При утечке объём памяти, недоступной для освобождения, растёт после каждого одинакового цикла. После снижения нагрузки и принудительно доступной штатной очистки базовый уровень остаётся выше предыдущего, а разница накапливается. Важно отличать логическую утечку от задержанного освобождения: память может временно удерживаться сборщиком мусора, пулом или аллокатором и не сразу возвращаться операционной системе.
Надёжный эксперимент включает контрольную группу. Нужно повторить тест с отключённым или ограниченным кэшем, если это возможно без изменения самого сценария, и сравнить наклон роста памяти, количество элементов кэша и объём памяти после очистки. Дополнительно полезно снять профили распределения объектов или сравнить снимки кучи между моментами после одинаковых этапов нагрузки.
Следует контролировать внешние факторы: одинаковый набор данных, маршруты запросов, размер ответов, фоновые задачи, версию среды выполнения и длительность этапов. Короткий тест может не показать медленную утечку, а принудительная сборка мусора в рабочем процессе может изменить его поведение, поэтому такой приём используют как диагностический эксперимент, а не как универсальное исправление.
После шести часов теста сервис начал получать ошибки нехватки памяти. Команда увидела, что память процесса растёт, и сначала рассматривала два варианта: уменьшить кэш или увеличить лимит памяти.
Уменьшение кэша было быстрым, но ухудшило коэффициент попаданий и увеличило задержку базы данных. Увеличение лимита временно отодвинуло сбой, но не устранило причину и повысило стоимость экземпляров.
Был выбран цикл из одинакового набора операций с периодами стабильной нагрузки и последующего снижения нагрузки. Размер кэша стабилизировался, но число объектов, связанных с контекстами запросов, продолжало расти; после сборки мусора базовый объём памяти не возвращался к прежнему уровню. Профилирование подтвердило удержание этих объектов, после устранения причины память стабилизировалась между циклами, а ошибки нехватки памяти исчезли.
**1. Достаточно ли увидеть, что память процесса не возвращается операционной системе?
Нет. Аллокатор или среда выполнения могут удерживать освобождённые страницы для повторного использования, поэтому RSS способен оставаться высоким без утечки. Нужно анализировать доступную для повторного использования память, состояние кучи и удерживаемые объекты, а также проверять, растёт ли базовый уровень после одинаковых циклов.
Да. Неограниченный или долго живущий кэш вызывает монотонный рост памяти, хотя объекты формально используются по назначению. Отличительный признак — связь роста с числом записей и наличие политики вытеснения или очистки; после достижения лимита рост должен прекратиться либо перейти в режим замещения. Если политика отсутствует, это может быть дефектом управления кэшем, даже если это не классическая утечка.
График показывает симптом, но не владельца памяти. Один и тот же рост может быть связан с кэшем, очередью сообщений, буферами, фрагментацией, нативной памятью или задержанной сборкой мусора. Для локализации нужны корреляция с числом запросов и объектов, событиями сборщика мусора, размером очередей, содержимым кэша и сравнением снимков памяти между одинаковыми контрольными точками.