Перейти к основному контенту
Tech Path Finder
КурсыИнтервьюКод-ревьюБлог
Tech Path Finder

Персонализированный путеводитель в IT. Квизы, мок-интервью, код ревью и аналитика прогресса.

@potapov_me

Платформа

  • Курсы
  • Прогресс
  • Мок-интервью
  • Код ревью
  • Живое ревью с ИИ
  • Тренажёр переговоров
  • Закладки

Контент

  • Блог
  • Главная
  • Обратная связь

Компания

  • О проекте
  • Тарифы
  • Условия использования
  • Конфиденциальность
  • Согласие на обработку данных
  • Cookie
  • Реквизиты

Аккаунт

  • Войти
  • Зарегистрироваться
  • Профиль

© 2026 Tech Path Finder. Все права защищены.

·ИП Потапов К.С.·Политика конфиденциальности·
Сделано с ❤️ в России
  1. Асинхронный Django
async_django

Асинхронный Django

ASGI, async views/ORM, WebSockets и ограничения async-кода

Асинхронный Django

Статус: актуально для Django 5.2/6.0. Django 6.0 имеет async API не только для части ORM, но и для cache, authentication, sessions и signals. Полностью асинхронные транзакции ORM всё ещё не поддерживаются.

Async позволяет одному процессу обслуживать другие задачи, пока текущая ждёт сеть или другое неблокирующее I/O. Он полезен для fan-out к внешним API, медленных streaming responses и большого числа одновременно ожидающих соединений.

async def не делает CPU-работу параллельной и не ускоряет обычный CRUD автоматически. Если внутри view выполняется синхронный HTTP-клиент, тяжёлое вычисление или длинная синхронная функция, event loop остаётся заблокированным.

#WSGI, ASGI и стандартный runserver

Django умеет выполнить async view и под WSGI: для запроса создаётся отдельный event loop. Асинхронные библиотеки внутри view работают, но преимуществ полностью асинхронного request stack и эффективных долгих соединений нет.

Для этого нужен ASGI server:

python -m uvicorn config.asgi:application

Официальный вариант с process manager:

python -m gunicorn config.asgi:application \ -k uvicorn_worker.UvicornWorker

Он требует пакеты gunicorn, uvicorn и uvicorn-worker. Точное число процессов, timeouts и пределы соединений выбирают по нагрузочному тесту и инфраструктуре.

Исправлено: стандартный python manage.py runserver использует WSGI application из WSGI_APPLICATION. Наличие asgi.py и async view само по себе не переключает его на ASGI. Сторонние пакеты, например Channels, могут переопределить команду.

Файл ASGI создаёт startproject:

import os from django.core.asgi import get_asgi_application os.environ.setdefault("DJANGO_SETTINGS_MODULE", "config.settings") application = get_asgi_application()

runserver и любой ASGI/WSGI server разработки нельзя использовать в production без предназначенной для этого конфигурации.

#Когда async оправдан

Хороший кандидат одновременно удовлетворяет нескольким условиям:

  • запрос значимую часть времени ждёт внешнее I/O;
  • у используемой библиотеки есть настоящий async API;
  • запросов много одновременно;
  • весь middleware stack не возвращает каждый запрос в отдельный sync thread;
  • внешняя система выдержит добавленный параллелизм.

Один SQL-запрос и рендер шаблона часто не становятся быстрее от async. Обработка изображения, PDF, шифрование большого массива и ML inference блокируют event loop; их переносят в worker/process или специализированный сервис.

Async повышает конкурентность, а не создаёт бесплатную пропускную способность. База, connection pool и внешний API всё равно имеют пределы.

#Async view и параллельные запросы

В Python 3.12 TaskGroup удобно выражает группу связанных операций:

import asyncio import httpx from django.http import JsonResponse async def dashboard(request): timeout = httpx.Timeout(3.0) async with httpx.AsyncClient(timeout=timeout) as client: async with asyncio.TaskGroup() as group: weather_task = group.create_task( client.get("https://weather.example/api/current") ) rates_task = group.create_task( client.get("https://rates.example/api/latest") ) weather = weather_task.result() rates = rates_task.result() weather.raise_for_status() rates.raise_for_status() return JsonResponse( { "weather": weather.json(), "rates": rates.json(), } )

У production endpoint должна быть политика частичного отказа: вернуть весь запрос с ошибкой, показать устаревшее значение или пропустить необязательный блок. Для каждого upstream задают timeout, допустимое число повторов и circuit/bulkhead-пределы. Бесконтрольный fan-out из 100 запросов способен положить downstream быстрее синхронного кода; ограничивайте конкурентность Semaphore или пулом клиента.

Не используйте requests внутри async def. Синхронный сетевой вызов удержит thread event loop до завершения.

#Async ORM: что действительно поддерживается

Методы QuerySet, выполняющие SQL, обычно имеют вариант с префиксом a: aget(), acreate(), acount(), aexists(), afirst(), aupdate() и другие. QuerySet поддерживает async for.

async def published_titles(): queryset = ( Post.objects.filter(status=Post.Status.PUBLISHED) .select_related("author") .order_by("-created_at")[:100] ) result = [] async for post in queryset: result.append( { "title": post.title, "author": post.author.display_name, } ) return result

Методы, только изменяющие конструкцию QuerySet, например filter() и select_related(), не выполняют SQL и не требуют await. Ожидать нужно момент вычисления.

Ленивый доступ к незагруженной связи внутри async-контекста попытается выполнить синхронный запрос и вызовет SynchronousOnlyOperation. Подготовьте связи через select_related()/prefetch_related() или выполните весь связанный фрагмент в одной sync-функции.

Не запускайте много ORM-запросов через gather() только потому, что это возможно. Один оптимальный SQL с join/aggregation часто быстрее и бережнее к connection pool.

#Транзакционная операция остаётся sync-островом

В Django 5.2/6.0 транзакции ещё не работают как полностью async ORM-контекст. Соберите всю операцию в одну синхронную функцию и пересеките границу один раз:

from asgiref.sync import sync_to_async from django.db import transaction @sync_to_async def reserve_stock(*, product_id, quantity): with transaction.atomic(): product = Product.objects.select_for_update().get(pk=product_id) if product.stock < quantity: raise InsufficientStock product.stock -= quantity product.save(update_fields=["stock"]) return product.stock async def reserve_view(request, product_id): remaining = await reserve_stock( product_id=product_id, quantity=1, ) return JsonResponse({"remaining": remaining})

Не оборачивайте каждую строку цикла отдельным sync_to_async(): это размазывает транзакцию и добавляет переходы. Не передавайте объект connection или cursor между потоками.

sync_to_async() по умолчанию использует thread_sensitive=True, что важно для sync-кода, зависящего от потока. Не переключайте его в False для ORM ради мнимого параллелизма.

#Persistent connections и pool

При ASGI Django рекомендует отключить persistent connections через CONN_MAX_AGE=0. Вместо соединения, закреплённого за request thread, используйте встроенный pool поддерживаемого database backend или проверенный внешний pool.

Размер пула должен учитывать число ASGI processes и пиковое количество одновременно выполняемых SQL-запросов. Async view способна быстро исчерпать pool и перенести очередь ожидания к базе — это нормальный предел, который нужно измерять.

#Остальные async API Django

В актуальном Django доступны, среди прочего:

user = await request.auser() value = await cache.aget("dashboard:v2") await cache.aset("dashboard:v2", payload, timeout=60) await request.session.aset("last_project_id", project_id)

Не добавляйте префикс a наугад: сверяйтесь с API конкретного компонента. Некоторые сторонние backends могут выполнять внутреннюю sync-адаптацию, поэтому измеряйте реальное поведение.

#Middleware и смешанный stack

Для полностью async request stack каждый middleware должен поддерживать async-вызов. Django адаптирует синхронный middleware через thread, чтобы сохранить совместимость. Это корректно, но при long-polling/SSE или большом числе ожидающих upstream-запросов дополнительный thread на request может уничтожить главное преимущество ASGI.

Включите debug logging django.request и ищите сообщения вида Asynchronous handler adapted for middleware .... Не переписывайте простой middleware ради моды: сначала найдите адаптацию, которая действительно ограничивает конкурентность.

#Отмена при disconnect

Для долгого запроса клиент может разорвать соединение. Django поднимет asyncio.CancelledError. Освободите прикладные ресурсы и обязательно передайте отмену дальше:

import asyncio async def stream_report(request): try: return await build_streaming_response(request) except asyncio.CancelledError: await release_temporary_resources() raise

Не подавляйте отмену широким except BaseException. Внешние клиенты и собственные корутины тоже должны корректно отменяться.

#sync_to_async и async_to_sync

  • sync_to_async(sync_fn) вызывают из async-кода и ожидают через await;
  • async_to_sync(async_fn) вызывают на синхронной границе, например в legacy management command.

Внутри уже работающей async-функции async callable нужно просто await. async_to_sync() в том же потоке с event loop не предназначен для этого и может поднять RuntimeError; утверждение, что его нужно использовать для вызова sync Celery .delay(), меняет направление адаптера местами.

Публикация задачи в broker — синхронный I/O конкретной Celery-библиотеки. Если её задержка значима для async endpoint, вынесите вызов в одну sync-функцию через sync_to_async() или используйте поддерживаемый async enqueue API. Саму долгую работу не запускают внутри request coroutine.

#WebSocket и streaming

Django ASGI поддерживает эффективные async HTTP views и streaming responses, но стандартный Django handler не предоставляет прикладной WebSocket API. Для WebSocket обычно используют Channels или отдельный ASGI-компонент.

В Channels выбирают sync consumer для преимущественно синхронного Django-кода или async consumer с async ORM/database_sync_to_async() для sync database-фрагментов. Authentication и authorization проверяют при соединении и при сообщениях; имя комнаты из URL нельзя без проверки использовать как право подписки.

Channel layer, например Redis, нужен для обмена сообщениями между процессами, но не является долговечным журналом бизнес-событий.

#Тестирование и измерение

from django.test import TestCase class DashboardTests(TestCase): async def test_dashboard(self): response = await self.async_client.get("/dashboard/") self.assertEqual(response.status_code, 200)

AsyncClient проверяет ASGI-style request path без запуска внешнего сервера. Отдельно тестируйте timeout, частичный отказ, cancellation и предел конкурентности. Нагрузочный тест должен включать реальные latency downstream и ограниченный database pool; benchmark одного пустого endpoint мало что доказывает.

#Legacy и актуальные выводы

  • Исправлено: runserver стандартного Django не переключается автоматически на ASGI.
  • Async views появились в Django 3.1, а не одновременно с первоначальной ASGI-поддержкой Django 3.0.
  • Название «полностью async ORM» преждевременно: query API широк, но async-транзакций пока нет.
  • DJANGO_ALLOW_ASYNC_UNSAFE не является production-решением SynchronousOnlyOperation; при конкуренции возможна порча данных.
  • Async полезен для ожидающего I/O и долгих соединений, но не заменяет очередь фоновых задач и процессы для CPU-bound работы.

#Официальная документация

  • Django: asynchronous support
  • Django: deploy with ASGI
  • Django with Uvicorn

Далее: Фоновые задачи: Celery