Вопрос: почему asyncio тормозит и зависает под нагрузкой - Академия Selectel

Вопрос: почему asyncio тормозит и зависает под нагрузкой

Линия поддержки
Линия поддержки Ответы на вопросы пользователей
3 апреля 2026

Показали, как понять, когда asyncio просто тормозит, а когда реально зависает, и как аккуратно добавить timeout и cancellation, чтобы не уронить продакшен.

Изображение записи

Комментарий пользователя

Здравствуйте! В FastAPI‑сервисе под нагрузкой начали видеть странные паузы по 2–5 секунд, хотя CPU почти не нагружен. В логах нет ошибок, иногда всплывает asyncio.exceptions.CancelledError, а инструменты мониторинга пишут, что Future висит в await. Как понять, что именно тормозит, и можно ли это как‑то «ограничить»?

Илья Долматов Пользователь

Ответ специалиста

Добрый день, Илья! Так бывает почти у всех, кто впервые выходит в прод с async/await на реальной нагрузке. Вроде бы CPU‑нагрузки нет, сервис отвечает, но иногда появляются паузы, asyncio‑таски как будто «зависают».

Скорее всего, у вас одна из двух проблем:

  • блокировка цикла (Event Loop) — где-то внутри async def затесалась тяжелая математика или долгая синхронная операция. Она просто «вешает» поток, и asyncio не может переключиться на другие задачи;
  • «забытые» таски — внешнее API или база данных отвечают медленно, а у вас нет жесткого таймаута. Таски копятся, забивают очередь, а потом отваливаются по CancelledError, когда клиент уже устал ждать и закрыл соединение.
  • Земцов Антон

    Земцов Антон

    Младший бэкенд-разработчик Python

При этом вы не видите RuntimeError или BlockingIOError, потому что asyncio просто не успевает пробежаться по всем таскам, и часть запросов как‑то «залипает» в очереди.

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


      import asyncio
import httpx
async def call_external():

    # Медленный внешний сервис
    async with httpx.AsyncClient(timeout=5.0) as client:
        r = await client.get("https://slow-api.example.com/long_task")
        r.raise_for_status()
        return r.json()
async def safe_call():

    # Обернем таск в wait_for
    try:
        result = await asyncio.wait_for(call_external(), timeout=3.0)
        return result
    except asyncio.TimeoutError:

        # Тут мы видим, что ресурс реально тормозит
        print("Timeout for external call!")
        return {}

Теперь, когда asyncio падает TimeoutError, вы сразу понимаете, где именно виснет задача.

Еще один сценарий — фоновый create_task без await:


      async def background_task():
    await asyncio.sleep(10)

    # Делаем что-то долго, без await выше
async def handle_request():
    asyncio.create_task(background_task())  # Создали таск и забыли

    # Никакого await, поэтому не ожидаем завершения
    return {"ok": True}

В проде такие таски накапливаются, линия asyncio как будто «думает», а asyncio.CancelledError появляется, когда asyncio пытается завершиться, но не может дождаться всех тасков. 

Лучше:


      async def handle_request():
    task = asyncio.create_task(background_task())

    # Явно ждем, либо ограничиваем время
    try:
        await asyncio.wait_for(task, timeout=3.0)
    except asyncio.TimeoutError:
        print("Background task took too long, skipping")

В продакшене проще всего обернуть критичные звенья:

  • asyncio.wait_for вокруг вызовов внешних API;
  • asyncio.timeout вокруг логики, зависящей от БД;
  • asyncio.gather с return_exceptions=True для списка параллельных запросов, чтобы не завязнуть на одном медленном.

Так вы добавляете явный предел на ожидание, и если asyncio действительно «тормозит», вы уже знаете, где именно.

Надеюсь, это поможет вашему сервису не залипать. Заглядывайте в Академию Selectel, у нас тут лампово и полезно.