Webrium
Подписаться

Память течёт: как найти и чем мерить

Порядок действий, который доводит до причины, и порядок, который нет

Потребление растёт и не выходит на полкурост без полки

Утечка памяти редко выглядит как утечка. Выглядит она так: сервис работает нормально, к концу дня отвечает медленнее, ночью перезапускается сам, и утром всё снова хорошо. Пока перезапуск случается, проблема не болит; когда нагрузка вырастает, перезапуск начинает случаться в рабочее время.

Ниже — порядок действий, который приводит к причине, и порядок, который к ней не приводит.

Тупик: смотреть на общее потребление процесса

Первая реакция, самая естественная, — открыть график памяти процесса и посмотреть, как он растёт. График растёт, это подтверждает, что проблема есть, и на этом полезная информация заканчивается.

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

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

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

Что мерить вместо этого

Работающая последовательность состоит из трёх шагов, и порядок в ней важен.

Шаг первый: отличить утечку от насыщения. Дайте сервису поработать под ровной нагрузкой заметно дольше обычного прогрева и смотрите не на само значение, а на форму кривой. Насыщение выходит на полку: рост замедляется и останавливается. Утечка сохраняет наклон: она может быть пологой, но не выравнивается. Если кривая вышла на полку выше, чем вам хотелось бы, — это не утечка, это настройка кэшей и пулов, и лечится она совсем другими действиями.

Отдельно проверьте, что растёт именно куча приложения, а не память процесса целиком. Расхождение между ними — обычное дело: куча стоит на месте, процесс растёт. Тогда искать надо не в объектах, а в том, что живёт вне кучи, — в буферах, в соединениях, в коде на других языках, вызываемом из вашего.

Есть и третья форма кривой, которую часто принимают за утечку: пила. Память растёт, резко падает, снова растёт — и так по кругу. Это нормальная работа сборщика мусора, и смотреть надо не на пики, а на нижние точки. Если нижние точки стоят на одной высоте, утечки нет, каким бы пугающим ни выглядел размах. Если нижние точки ползут вверх, утечка есть, даже когда пики выглядят одинаково.

Шаг второй: снять два снимка кучи и сравнить их. Это единственное действие, которое даёт ответ, а не гипотезу. Снимок делается после прогрева, второй — после того, как под нагрузкой прошло достаточно времени и рост стал очевиден. Сравнивать нужно не абсолютные размеры, а разницу: какие типы объектов прибавили в числе больше остальных.

Дальше почти всегда становится видно, что растёт какая-то одна коллекция. Вопрос сужается до конкретного вопроса: кто в неё кладёт и кто должен был удалять.

Шаг третий: найти путь удержания. Знать, что объектов стало больше, недостаточно — нужно понять, почему сборщик мусора их не убрал. Инструменты анализа кучи умеют показывать цепочку ссылок от корня до объекта, и именно она даёт ответ. Как правило, цепочка упирается в одну из нескольких типовых конструкций, и по ней сразу видно, какая именно.

Типовые причины, которые находятся чаще всего

Кэш без ограничения размера и без срока жизни. Растёт ровно столько, сколько разных ключей встретилось. На тестовом стенде ключей мало, на бою — много.

Коллекция, которая используется как накопитель между обработками: сюда добавляют результаты и очищают в конце — а конец не наступает при ошибке.

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

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

Незакрытые ресурсы: соединения, файлы, курсоры. Они держат буферы вне кучи — тот самый случай, когда куча ровная, а процесс растёт.

Замыкание, которое захватило больше, чем нужно, и живёт дольше, чем предполагалось. Здесь особенно легко ошибиться: захватывается не поле, а весь объект, которому это поле принадлежит.

Чем это мерить в бою

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

Минимум, который стоит иметь всегда: график размера кучи после сборки мусора, а не до неё. Значение до сборки скачет и ничего не показывает; значение после — это то, что реально удерживается, и именно оно на утечке ползёт вверх.

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

Третий — возможность снять снимок кучи с рабочего экземпляра. Её стоит подготовить заранее: проверить, что прав хватает, что места на диске достаточно, что снятие снимка не уронит сервис под нагрузкой. Готовить это в момент инцидента поздно.

Что делать, пока причина не найдена

Расследование занимает дни, а сервис должен работать сегодня. Здесь уместны меры, которые честно называются временными.

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

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

Чего делать не стоит — молча увеличивать лимит и снимать оповещение. Это лечит симптом ровно до следующего роста нагрузки, а расследование становится сложнее: сравнивать снимки на большем объёме дольше, и разница между ними менее выразительна.

Позиция редакции

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

Мы неправы в одном случае: когда утечка появилась вместе с недавним изменением и вы точно знаете, каким. Тогда чтение этого изменения короче любых измерений, и начинать надо с него. Но «примерно тогда же что-то выкатывали» — это не то же самое, что «точно знаете»: в этой формулировке чтение кода снова превращается в угадывание.

С чем это связано

Метка над заголовком говорит, зачем туда идти: продолжить тему, перейти к практике или увидеть возражение.

Независимые ожидания, запущенные вместе, занимают время самого медленногоожидания вместе
Предыстория

Асинхронность: что она ускоряет, а что нет

Что знать до этого: асинхронность не ускоряет вычисления, она позволяет не простаивать в ожидании, и это меняет ожидания от неё.

Из трёх причин медленной работы за шкалу выходит ожидание, а не вычислениеожидание
Практика

Python тормозит: что действительно ускоряет

Руками: под словом «медленно» прячутся три разные ситуации, и в статье сказано, как выяснить свою и что делать в каждой.

Четыре показателя-поля трейса: ID запроса, шаг вызова и код ошибки в норме, а тело запроса уходит за шкалу — его место в архиве, а не в трейсетело запроса
Углубление

Что писать в трассировку, а что нет

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

Письмо по вторникам

Один разбор недели и короткий список того, что изменилось. Без дайджестов на сорок ссылок.

Начните вводить — материалы появятся здесь.

выбрать · Enter открыть · Esc закрыть