TGViewer
Антон Непша.js Антон Непша.js @nepshajs · 2.91K subscribers
Post #195 4.66K
Как я искал утечку памяти в приложении на Python

На этапе нагрузочного тестирования обнаружилась утечка памяти в контейнере приложения, которое я разрабатываю.

До этого мне приходилось сталкиваться с утечками в JS в браузере, я даже доклад про это рассказывал, но в этот раз утечка была в агенте на Python, поэтому браузерные девтулзы там не помогли бы — пришлось разбираться в этом вопросе заново.

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

Memray
Отчёт pytest-memray не самый подробный — это просто текст.
Поэтому я решил перейти на обычный memray, который работает независимо от pytest и способен выдавать самые разные форматы отчетов. Я использовал Flamegraph-диаграмму и линейный график.

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

Причина утечки
Работа с результатами отчета привела меня к файлу с таким кодом:

from prometeus_client import Histogram

metrics = Histogram(
name="my_service",
labelsnames=("sender", "status", "time"),
)

def send_metrics(
sender: StrEnum,
status: StrEnum,
time: float
):
metrics.labels(
sender=sender,
status=status,
time=time
).observe(time)

Респект, если вы уже догадались, в чём тут причина)) Я гадать не стал, признаюсь, сразу пошел в гигачат. Это не реклама, кстати, просто из корпоративной сети мне доступен только он. Но я и не жалуюсь, т.к. гигач сразу указал на причину утечки — лейбл time.

Почему именно time, а не, например, status?
У лейбла status набор возможных принимаемых значений ограничен енумом — либо "success", либо "error" (это для примера). Соответственно, сколько бы я ни гонял свой скрипт, хоть 5 раз, хоть 500, в метриках сохранится только два варианта значений этого лейбла — "success" или "error".

У лейбла sender значений чуть больше, но тоже вполне ограниченное количество.

А вот лейбл time в моём случае означал время обработки запроса. С типом float. То есть, запрос мог обработаться, например, за 0.5 секунд со статусом "success". А мог за 0.2 со статусом "error". Или за 0.10002 секунд, 0.037261846 секунд, 0.137, 0.9989 и ещё за огромное количество вариаций. Умножьте это количество вариаций на комбинации с остальными лейблами, которых тоже на самом деле было не три, а чуть больше, и вы поймёте, почему я в воскресенье вечером про утечки памяти пишу))
  • 🔥 30
  • 😁 8
  • ❤‍🔥 3
  • 💘 2
  • 🐳 1
More from @nepshajs
  1. Sep 30, 2026👨‍💻 Фронтенд с AI: что можно делегировать агентам 5 октября, стартует конференция Podlod…
  2. Jul 30, 2026Моё хобби — менять названия полей в тулах и смотреть, как это влияет на качество заполнени…
  3. Jul 20, 2026Расширил свой недавний доклад про работу с GigaChat API и расскажу его 25 июля на Технохаб…
  4. Jul 17, 2026Никогда ещё не уходил с митапа под таким сильным впечатлением, как вчера после JS x AI con…
  5. Jul 15, 2026Агентские фреймворки больше не нужны Мне стали часто попадаться видео автора Jake Van Clie…
  6. Jul 10, 2026JS x AI митап в Сбер.Среде Я тут недавно осознал, что я уже два с половиной года работаю с…
Threads Profile ViewerView any public Threads profile without an account.Open ThreadLook →Writing with AI? Make it sound human.Metric37 rewrites AI drafts so they read naturally. Free AI detector, 1,500 words free.Try Metric37 →