исправление утечек памяти в python-сервисах: диагностика и сбор дампов
Python-сервисы под нагрузкой могут silently расходовывать память до hitting cgroup limit и OOM-kill. Без систематического сбора дампов и интроспекции root-cause hunt превращается в перебор гипотез. Ниже — проверенный набор команд и скриптов для диагностики, сбора хит-дампов и устранения типичных утечек.
1. Команды диагностики потребления памяти
Базовый уровень — psutil. Устанавливается одной строкой и работает без перезапуска процесса.
Текущее потребление текущего процесса:
Расширенная структура одной командой:
Краткая таблица ключевых атрибутов psutil.Process.memory_full_info():
| Атрибут | Описание |
|---|---|
python | Память, выделенная внутри интерпретатора Python |
rss | Resident Set Size — физическая память в RAM |
vms | Virtual Memory Size — virtual address space |
shared | Общая память (shared libraries, mmap) |
text, lib, data | Сегменты кода, библиотек, данных ELF |
Для отслеживания динамики роста за короткий интервал можно использовать psutil в цикле или обертку watch -n 1 psutil ..., но на production чаще включают tracemalloc, встроенный в CPython.
tracemalloc добавляет небольшой оверхед (~1–2 %). Включайте его только на стенде или при явных подозрениях на утечку.2. Инструменты для отслеживания роста и сбора дампов
Когда RSS начинает расти незаметно, нужны более глубокие инс펙ции. Набор зависит от доступности отладчика и разрешения на ptrace.
gdb + gcore — классический способ выгрузить полный дамп процесса без его остановки (при условии включенного coredump).
Результат gcore — бинарный файл, который можно анализировать локально:
objgraph — утилита для быстрого ответа на вопрос «кто держит объект».
objgraph работает только с объектами Python. Для нативного C‑расширения или ctypes придется использовать gdb или valgrind.tracemalloc + heapdump — встроенный механизм Python 3.4+ может сохранить снимок кучи в файл.
Дамп можно открыть в визуализаторе (например, python -m pdb или сторонние GUI), но для глубокого анализа все же удобнее gdb.
cap_sys_ptrace, сбор gcore может требовать привилегий или использования kubectl exec с доступом к хосту.3. Типичные антипаттерны и чего избегать
| Антипattern | Последствие | Рекомендация |
|---|---|---|
Игнорирование gc.collect() как «волшебной кнопки» | Ложное чувство безопасности, утечка продолжается | Используйте gc.collect() для очистки временных ссылок, но не как лечение логической утечки |
Опора только на __del__ для освобождения ресурсов | Нерегулярное освобождение, circular references | Предпочитайте contextlib.contextmanager, try/finally или weakref |
Отсутствие лимитов памяти (cgroup/ulimit) | Острый OOM-kill без дампа, потери контекста | Всегда ставьте memory.limit в манифестах и проверяйте ulimit -v |
| Кэши без TTL или роста без пределов | Мощленный рост RSS | Используйте functools.lru_cache(maxsize=N) или внешние хранилища с экспирацией |
| Накопление объектов в глобальных списках/модулях | «Смерть» памяти при длительном работе | Периодически проверяйте длину списков, выносите в периодические задачи очистку |
del obj в __del__ при наличии circular references. Python’s GC eventually collects them, но порядок не гарантирован, что приводит к пикам памяти между коллекциями.4. Сценарий реагирования на OOM-инцидент
- Подтверждение — проверьте событие в кластере:
kubectl get events -n <ns> | grep OOMилиdmesg | grep out of memory. Убедитесь, что процесс terminated с кодом 137. - Быстрый взгляд на RSS — если сервис ещё жив, выполните одну команду:
- Сбор даmpа — выберите метод в зависимости от доступности инструментов:
- Если есть
gdbи права:gdb -p <PID> -batch -ex gcore -ex quit /tmp/oom_dump.core - Если нужно быстро:
python3 -c "import tracemalloc; tracemalloc.start(); ..."иtracemalloc.take_snapshot().statistics('traceback')
- Если есть
- Анализ — используйте
objgraph.most_common_types()илиgdb‑командыinfo files,btдля вызова стека. Ищите unexpected количествоdict,list, или объектов вашего доменного класса. - Фиксация — устраните причину: добавьте
maxsizeк кэшу, устраните циклические импорты, заменитеglobal‑списки на с ограниченным циклом, настройтеweakrefдля наблюдателей. - Профилактика — добавьте алерт на
rss > 80% от лимитав Prometheus/Grafana и документируйте команду сбора дампа в runbook.
Завершение диагностики на этом этапе позволяет либо восстановить сервис, либо собрать достаточно данных для тикета в трекере с конкретными типами объектов и стеками вызовов. Here’s a thinking process:
- Analyze the Request:
- Topic: Fixing memory leaks in Python services: diagnostics and dump collection
- Role: Practicing Lead DevOps
- Format: IT notes for a Hugo blog (Mana theme)
- Style: Short paragraphs, concrete commands, flag tables, code with fences, no fluff, no marketing, no emojis, no author signature at end
- Allowed: GitHub admonitions (
> [!NOTE],> [!TIP],> [!WARNING]) - Prohibited: Made-up flags, URLs, versions; uncertain facts → cautious tone; no YAML/TOML front matter; no wrapping in ```; start with a lid (2-4 sentences); sections with
##; practical commands, tables if needed; 800-1600 words; end on last substantive section; don’t repeat rules, don’t write “User wants”, "