В микросервисной архитектуре и телеком-ботах на Python (таких как боты управления на python-telegram-bot или демоны синхронизации) процессы работают непрерывно месяцами. На виртуальных серверах с 1–2 GB RAM постепенная утечка даже 2–3 мегабайт в день рано или поздно приводит к срабатыванию механизма ядра Linux OOM Killer, который без предупреждения завершает процесс.

Типичные источники утечек в asyncio

В отличие от классического синхронного кода, в асинхронных приложениях утечки чаще всего связаны не с открытыми файловыми дескрипторами, а с жизненным циклом корутин и ссылочными связями:

Инструментарий диагностики: tracemalloc

Встроенный модуль tracemalloc позволяет отслеживать точные строки кода, аллоцирующие память в рантайме, практически без снижения производительности сервиса:

import asyncio
import tracemalloc
import logging

logger = logging.getLogger("mem-sentinel")

async def memory_monitor_daemon(interval_sec: int = 3600):
    tracemalloc.start(25) # сохраняем до 25 кадров стека вызовов
    snapshot_prev = None

    while True:
        await asyncio.sleep(interval_sec)
        snapshot_curr = tracemalloc.take_snapshot()

        if snapshot_prev:
            top_stats = snapshot_curr.compare_to(snapshot_prev, 'lineno')
            logger.info("--- ТОП 5 ПРИРОСТА ПАМЯТИ ПО СТРОКАМ ---")
            for stat in top_stats[:5]:
                logger.info(str(stat))

        snapshot_prev = snapshot_curr

Ограничение ресурсов через Systemd

Чтобы процесс не мог занять всю память операционной системы и уронить критические службы (Nginx, Sing-Box), в системный юнит .service всегда добавляются лимиты cgroups:

# /etc/systemd/system/bot.service

[Service]
MemoryMax=150M
MemoryHigh=120M
Restart=always
RestartSec=5

При достижении отметки MemoryHigh ядро начинает активно вытеснять неиспользуемые страницы процесса в swap, а если утечка прогрессирует до MemoryMax, сервис перезапускается за доли секунды без остановки всей ОС.