Асинхронные представления в Django: когда они реально помогают
async def во view не делает Django быстрее автоматически. Разбираем, где асинхронность даёт выигрыш, где вредит и почему один синхронный ORM-запрос убивает всю выгоду.

Вы переписали view с def на async def, ждали прироста, а под нагрузкой сервис ведёт себя так же или хуже. Это типичный результат: асинхронные представления в Django ускоряют не всё подряд, а конкретный класс задач — ожидание внешнего I/O. Если внутри view остаётся синхронный запрос к базе через обычный ORM, весь смысл теряется. Разберём, на каких задачах async-view действительно снимает нагрузку, на каких только добавляет накладных расходов, и что ломается на границе синхронного и асинхронного кода.
Материал для Django 5.x (актуально и для 4.2 LTS в части описанного поведения). Читатель — тот, кто уже пишет views и упёрся в вопрос: переводить их в async или нет.
Что даёт async-view, а что нет
Асинхронное представление — это корутина, которую ASGI-сервер (Uvicorn, Daphne, Hypercorn) исполняет в одном событийном цикле вместе с сотнями других. Пока один запрос ждёт ответа от внешнего API или медленной сети, поток не блокируется — цикл обслуживает другие запросы. В этом вся выгода.
Выигрыш появляется только тогда, когда view проводит заметное время в ожидании внешнего I/O и это ожидание можно сделать неблокирующим. Классический случай: view дёргает два-три сторонних HTTP-сервиса и агрегирует ответы.
Асинхронность экономит не процессорное время, а время ожидания. Если ваш view упирается в CPU или в синхронную базу, async не поможет — он поможет только там, где поток простаивал бы вхолостую.
Где async-view не даёт ничего или вредит:
- CPU-bound задачи — сериализация большого JSON, обработка изображений, расчёты. Событийный цикл однопоточный: тяжёлая синхронная операция в корутине блокирует весь цикл, и все параллельные запросы встают.
- Простой CRUD на ORM — если view делает один-два запроса к базе и сразу отдаёт ответ, накладные расходы на переключение контекста съедают призрачную выгоду.
- Смешанный код с синхронным ORM — вызов обычного
Model.objects.get()внутриasync defDjango оборачивает вsync_to_asyncи уводит в отдельный поток. Это работает, но никакого параллелизма по базе вы не получаете.
Где выигрыш реальный: несколько внешних вызовов
Представьте view, который собирает страницу из ответов трёх внешних API — курс валют, погода, лента новостей. Синхронно это три последовательных ожидания: 200 + 200 + 200 мс = 600 мс на запрос, и поток заблокирован всё это время.
Асинхронно те же три запроса запускаются параллельно, и общее время близко к самому медленному из них. Плюс поток не простаивает — пока ждём ответы, цикл обслуживает другие запросы.
import asyncio
import httpx
from django.http import JsonResponse
# Django 5.x, ASGI-сервер (Uvicorn/Daphne)
async def dashboard(request):
async with httpx.AsyncClient(timeout=5.0) as client:
# три внешних вызова стартуют одновременно
rates_resp, weather_resp, news_resp = await asyncio.gather(
client.get("https://api.example.com/rates"),
client.get("https://api.example.com/weather"),
client.get("https://api.example.com/news"),
)
return JsonResponse({
"rates": rates_resp.json(),
"weather": weather_resp.json(),
"news": news_resp.json(),
})
Здесь важны две вещи. Первое — HTTP-клиент должен быть асинхронным (httpx.AsyncClient), синхронный requests внутри async def заблокирует цикл и убьёт весь эффект. Второе — asyncio.gather запускает вызовы параллельно; если бы вы писали три отдельных await подряд, они бы выполнялись последовательно и выигрыша по времени не было бы, только по неблокирующему ожиданию.
ORM: главная ловушка
Начиная с Django 4.1 у QuerySet и менеджеров есть асинхронные варианты методов с префиксом a: aget(), acreate(), afirst(), acount(), асинхронная итерация через async for. Это не делает саму базу асинхронной — под капотом Django по-прежнему уводит запрос в пул потоков через sync_to_async. Но эти методы дают корректный async-интерфейс и не роняют цикл на ровном месте.
Типичная ошибка — оставить синхронный ORM-вызов в асинхронном view:
# НЕПРАВИЛЬНО: синхронный вызов ORM в async-view
async def profile(request, user_id):
user = User.objects.get(pk=user_id) # SynchronousOnlyOperation
return JsonResponse({"name": user.username})
Django выбросит SynchronousOnlyOperation, потому что синхронный доступ к базе из асинхронного контекста запрещён без явной обёртки. Правильно — использовать асинхронный метод:
from django.http import JsonResponse, Http404
from asgiref.sync import sync_to_async
# Django 5.x
async def profile(request, user_id):
try:
user = await User.objects.aget(pk=user_id)
except User.DoesNotExist:
raise Http404
return JsonResponse({"name": user.username})
Но даже с aget() запрос всё равно исполняется в потоке из пула. Если ваш view — это один такой запрос и всё, вы платите за переключение контекста и не получаете ничего взамен. Асинхронность окупается, когда рядом с базой есть настоящий неблокирующий I/O, который можно совместить с ожиданием.
Смешивание sync и async
Границу между мирами держат две обёртки из пакета asgiref (устанавливается вместе с Django):
- sync_to_async — оборачивает синхронную функцию, чтобы вызвать её из корутины. Именно этим Django оборачивает синхронный middleware, ORM и вашу синхронную бизнес-логику.
- async_to_sync — наоборот, запускает корутину из синхронного кода. Нужен, когда из обычной функции вы дёргаете асинхронную библиотеку.
Тяжёлую синхронную функцию (например, обработку данных сторонней библиотекой) в async-view оборачивайте явно, иначе она заблокирует цикл:
from asgiref.sync import sync_to_async
def heavy_report(raw_data):
# синхронная CPU-логика или синхронная библиотека
...
async def report_view(request):
data = await fetch_data() # неблокирующий I/O
# уводим тяжёлую синхронную работу в поток
result = await sync_to_async(heavy_report, thread_sensitive=True)(data)
return JsonResponse(result)
Параметр thread_sensitive=True (значение по умолчанию) заставляет исполнять функцию в одном общем потоке — это важно для кода, который завязан на локальное состояние потока, включая сам ORM. Ставить False стоит только если вы уверены, что функция потокобезопасна и не трогает разделяемое состояние; иначе получите труднодиагностируемые гонки.
Когда переводить view в async, а когда нет
| Что делает view | Async-view | Почему |
|---|---|---|
| 2+ внешних HTTP-вызова, которые можно распараллелить | Да | Ожидание совмещается, поток не простаивает |
| Долгий запрос к внешнему API + отдача ответа | Да | Цикл обслуживает другие запросы во время ожидания |
| WebSocket / SSE / стриминг | Да | Долгоживущие соединения, чистый I/O |
| Один-два ORM-запроса и рендер | Нет | Накладные расходы больше выгоды |
| CPU-нагрузка: расчёты, обработка изображений | Нет | Блокирует событийный цикл, встают все запросы |
| Синхронный сторонний SDK без async-версии | Осторожно | Только через sync_to_async, иначе блок цикла |
Практическое правило: смешивать sync- и async-views в одном проекте можно, Django это поддерживает. Переводите в async точечно те view, где реально есть параллельный или долгий внешний I/O, а остальные оставляйте синхронными. Тотальный перевод «на всякий случай» чаще ухудшает картину.
Что ломается на практике
- SynchronousOnlyOperation — где-то в async-view остался синхронный доступ к ORM или к сессии. Ищите вызовы без префикса
aи обёрткиsync_to_async. - Тихая деградация под нагрузкой —
requests, синхронный клиент кеша или тяжёлый CPU-код внутри корутины блокируют весь цикл. Симптом: под параллельной нагрузкой растёт время ответа у запросов, которые сами по себе быстрые. - WSGI вместо ASGI — async-view под gunicorn с WSGI-воркером работает, но Django исполняет корутину через
async_to_sync, и никакого параллелизма нет. Чтобы async работал по-настоящему, нужен ASGI-сервер. - Middleware — синхронный middleware в цепочке заставляет Django адаптировать поток на каждом запросе. Если критично, приводите middleware к тому же режиму, что и view.
Что делать дальше
Перед переводом снимите профиль: где view проводит время — в ожидании сети или в процессоре. Если в ожидании внешних сервисов — кандидат на async. Разверните проект на ASGI-сервере (Uvicorn под управлением gunicorn-воркера или напрямую) и замерьте p95 времени ответа под параллельной нагрузкой до и после перевода, на одинаковом железе и объёме данных. Проверьте, что все ORM-вызовы в переведённых view используют a-методы, а синхронные библиотеки обёрнуты в sync_to_async. Про настройку ASGI-деплоя и различия воркеров смотрите официальную документацию Django по разделу async.
Тест и проверочная: Быстрые ответы с объяснением, почему остальные варианты неверны
Спросить за 10 ₽Источники: Django documentation — Asynchronous support, Django documentation — Asynchronous QuerySet methods
Частые вопросы
Async-view медленнее синхронного на простом CRUD — это нормально?
У меня Django 4.2, а не 5.x — примеры сработают?
Можно оставить requests внутри async-view, если запрос быстрый?
Я перевёл view в async, а прироста нет — что проверить?
Обязательно ли переводить весь проект на async?
Как безопасно вызвать синхронную бизнес-логику из async-view?
Материал носит информационный характер и подготовлен редакцией «Агентуры». Он не является офертой, рекламой или индивидуальной консультацией. Упомянутые продукты, компании и торговые знаки принадлежат их правообладателям. Перед принятием решений, влекущих юридические или финансовые последствия, обратитесь к профильному специалисту.