Короткий ответ
Нужно снять снимки памяти через tracemalloc или объектный граф через objgraph в разные моменты времени и сравнить, какие типы объектов накапливаются быстрее всего, что укажет на место утечки — обычно это растущие кэши, незакрытые ресурсы или циклические ссылки, недоступные для сборщика мусора из-за __del__.
Как это работает подробнее
tracemalloc фиксирует место выделения памяти для каждого объекта и позволяет сравнить два снимка (snapshot.compare_to), показывая, какие строки кода ответственны за наибольший прирост; objgraph.show_growth() показывает, какие типы объектов стали чаще встречаться между вызовами.
Пример кода
example.py
import tracemalloc
tracemalloc.start()
snapshot1 = tracemalloc.take_snapshot()
run_worker_cycle() # обработка пачки задач
snapshot2 = tracemalloc.take_snapshot()
top_stats = snapshot2.compare_to(snapshot1, "lineno")
for stat in top_stats[:10]:
print(stat) # строки кода с наибольшим приростом выделенной памятиЧто использовать на практике
- Практика. Типичные источники роста — необновляемые кэши без ограничения размера (словарь, растущий без TTL/maxsize), подписчики событий, на которые никто не отписывается, соединения или файлы, не закрытые в блоке finally, а также объекты с циклическими ссылками и
__del__, которые не собираются обычным reference counting.
Подводные камни
- Риск. Сразу подозревают «утечку памяти в самом Python» вместо анализа собственного кода; путают рост RSS процесса (включающий фрагментацию аллокатора) с реальным ростом живых Python-объектов; не воспроизводят проблему в контролируемых условиях перед тем, как чинить «вслепую».