ASGI против WSGI в Django: когда переход даёт выигрыш
Переход на ASGI не ускоряет обычные CRUD-запросы и иногда замедляет их. Разбираем, на каких нагрузках async в Django реально помогает, а где вы платите за него временем ответа и стабильностью.

Команда переводит Django-проект с Gunicorn на Uvicorn, ждёт роста throughput, а в проде получает те же цифры или медленнее на 5–10%. История типичная: ASGI подключили под нагрузку, которая от него ничего не выигрывает. Django поддерживает ASGI начиная с версии 3.0 (2019), async-вьюхи — с 3.1, async ORM — с 4.1. Но наличие поддержки не значит, что переход окупится. Ниже — где ASGI даёт измеримый выигрыш, а где превращается в лишний слой сложности.
Что физически меняется между WSGI и ASGI
WSGI — синхронный интерфейс: один воркер обрабатывает один запрос от начала до конца и на время блокирующих операций (запрос к БД, вызов внешнего API) просто ждёт. Параллелизм достигается количеством процессов и потоков. ASGI — асинхронный: один event loop может держать сотни одновременных соединений, переключаясь между ними, пока каждое ждёт I/O.
Ключевое слово — I/O-bound. Выигрыш появляется там, где воркер большую часть времени ждёт ответа снаружи, а не считает что-то на CPU. Если ваша вьюха делает один быстрый запрос к локальной БД и рендерит шаблон — она не I/O-bound в том смысле, который важен для event loop, и async вам ничего не даст.
ASGI не делает код быстрее. Он позволяет одному процессу не простаивать во время ожидания I/O. Если вы не ждёте — экономить нечего.
I/O-bound — задача, время выполнения которой определяется ожиданием ввода-вывода (сеть, диск, БД), а не вычислениями. Противоположность — CPU-bound, где узкое место в процессоре.
Где ASGI даёт выигрыш
Долгие внешние вызовы внутри одного запроса
Вьюха обращается к трём внешним API последовательно, каждый отвечает 200–400 мс. На WSGI воркер заблокирован всё это время. На ASGI с async-клиентом (например, httpx.AsyncClient) эти вызовы можно запустить конкурентно через asyncio.gather, и суммарное время ожидания схлопывается до самого долгого из трёх.
import asyncio
import httpx
from django.http import JsonResponse
# Django 4.2+, async-вьюха
async def aggregate(request):
async with httpx.AsyncClient(timeout=5.0) as client:
# три запроса летят одновременно, а не по очереди
prices, stock, reviews = await asyncio.gather(
client.get("https://api.example.com/prices"),
client.get("https://api.example.com/stock"),
client.get("https://api.example.com/reviews"),
)
return JsonResponse({
"prices": prices.json(),
"stock": stock.json(),
"reviews": reviews.json(),
})
Здесь выигрыш реальный и его видно в замерах: три последовательных вызова по 300 мс — это 900 мс, конкурентно — около 300 мс плюс накладные.
Долгоживущие соединения: WebSocket, SSE, long polling
Это то, ради чего ASGI задумывался. WSGI-воркер, держащий открытый WebSocket, занят целиком и не обслуживает никого другого. Тысяча одновременных чат-соединений на WSGI требует тысячи потоков — нежизнеспособно. На ASGI один процесс держит их в одном event loop. Django Channels работает только поверх ASGI по этой причине.
Высокая конкурентность при медленных ответах
Если у вас много одновременных клиентов и каждый запрос ждёт медленный upstream, ASGI позволяет одному процессу удерживать сотни соединений вместо десятков. Меньше процессов — меньше памяти на интерпретаторы Python.
Где ASGI не помогает или мешает
- Обычный CRUD с быстрой БД. Запрос к PostgreSQL на локальной сети отвечает за единицы миллисекунд. Event loop не успевает извлечь пользу из такого короткого ожидания, а оверхед на переключение контекста и async-обёртки съедает то, что могло бы сэкономиться.
- CPU-bound задачи. Генерация PDF, обработка изображений, тяжёлые вычисления в шаблоне. Event loop однопоточный: пока вы считаете, все остальные соединения ждут. Async здесь вредит — переносите такое в Celery или отдельный процесс.
- Синхронный код в async-вьюхе. Если внутри async-вьюхи вы вызываете синхронный ORM или блокирующую библиотеку, Django оборачивает вызов в
sync_to_asyncи уводит его в отдельный поток. Вы получаете async-фасад над синхронным кодом — плюс накладные, минус реальный выигрыш.
Сравнение: что выбрать под конкретную нагрузку
| Профиль нагрузки | WSGI (Gunicorn sync) | ASGI (Uvicorn/Gunicorn+Uvicorn worker) |
|---|---|---|
| CRUD, быстрая локальная БД | Оптимально, проще в отладке | Нет выигрыша, чуть медленнее |
| Много медленных внешних API в запросе | Воркер простаивает | Выигрыш при конкурентных вызовах |
| WebSocket, SSE, long polling | Не подходит | Единственный вариант |
| CPU-bound (рендер, вычисления) | Масштабируется процессами | Блокирует event loop, хуже |
| Смешанная нагрузка | Предсказуемо | Требует аккуратности с sync/async |
Async ORM: чего ждать и чего не ждать
С Django 4.1 у ORM появились async-методы: aget, acreate, afilter через async for и так далее. Важно понимать, что под капотом это по-прежнему обёртка над синхронным драйвером БД — настоящего асинхронного пути к PostgreSQL в стандартном Django на момент 5.x нет, запрос уходит в пул потоков через sync_to_async. То есть async ORM снимает необходимость писать sync_to_async руками и делает код чище, но не превращает обращение к БД в по-настоящему неблокирующее на уровне сокета.
Практический вывод: если единственное «медленное» в вашей вьюхе — это БД, async ORM не даст того выигрыша, что даёт async HTTP-клиент к внешнему сервису. Проверяйте актуальное состояние поддержки нативного async-драйвера в документации по async в Django для вашей версии — эта область активно меняется.
Как измерить, стоит ли переходить
Не переходите на веру. Замерьте на реалистичной нагрузке.
- Снимите профиль запросов: сколько времени вьюха проводит в ожидании внешнего I/O против CPU и БД. Django Debug Toolbar покажет время SQL, а для внешних вызовов добавьте тайминги руками.
- Прогоните нагрузочный тест текущего WSGI-стека: зафиксируйте RPS, p95 и p99 latency при целевой конкурентности. Инструменты —
wrk,k6,locust. - Соберите тот же эндпоинт на ASGI с async-клиентом для внешних вызовов. Прогоните тот же сценарий на том же железе и объёме данных.
- Сравните p95/p99, а не средние. Async чаще влияет на хвост распределения при высокой конкурентности, чем на медиану.
Если p95 при вашей конкурентности не улучшился хотя бы заметно — переход не оправдан, оставайтесь на WSGI и сэкономьте на сложности эксплуатации.
Что ломается при переходе
- Middleware. Синхронный middleware в ASGI-стеке заставляет Django переключать контекст между async и sync на каждый запрос. Проверьте, что ваши middleware поддерживают async, иначе вы теряете часть выигрыша.
- Сторонние библиотеки. Многие драйверы кеша, клиенты платёжных систем и SDK синхронны. Их вызов в async-вьюхе блокирует event loop, если не обёрнут явно.
- Отладка. Трейсбеки в async-коде длиннее и запутаннее, воспроизведение гонок сложнее. Заложите на это время.
- Утечка блокирующих вызовов. Одна забытая синхронная операция (чтение файла,
time.sleep, синхронный HTTP) в event loop подвешивает все соединения процесса. На WSGI такая ошибка задела бы один воркер.
Что делать дальше
Начните с профилирования, а не с переписывания. Найдите конкретные эндпоинты, где время уходит на ожидание внешнего I/O, — только их и есть смысл переводить на async, оставив остальное на WSGI или синхронными вьюхами под ASGI. Django позволяет смешивать sync и async-вьюхи в одном проекте, так что миграция может быть точечной. Если появляется задача с WebSocket или SSE — тут вопрос выбора не стоит, сразу Channels поверх ASGI. В проде следите за p99 latency и за метрикой занятости event loop: рост первого при стабильном втором обычно означает блокирующий вызов, просочившийся в цикл.
Тест и проверочная: Быстрые ответы с объяснением, почему остальные варианты неверны
Спросить за 10 ₽Источники: Asynchronous support — Django documentation, How to deploy with ASGI — Django documentation
Частые вопросы
У меня Django 3.2, а не 4.x — можно ли уже использовать async?
Я перешёл на ASGI, но latency не улучшилась или стала хуже — почему?
Нужен ли ASGI, если я использую только Django REST Framework?
Можно ли держать часть проекта на WSGI, а часть на ASGI?
Async-вьюха с ORM — это уже настоящий неблокирующий доступ к базе?
Какой сервер выбрать для ASGI в Django?
Материал носит информационный характер и подготовлен редакцией «Агентуры». Он не является офертой, рекламой или индивидуальной консультацией. Упомянутые продукты, компании и торговые знаки принадлежат их правообладателям. Перед принятием решений, влекущих юридические или финансовые последствия, обратитесь к профильному специалисту.