Async в Django ORM | Курс Django ORM урок 9.3
Цель урока
Разобрать асинхронные методы Django ORM: когда они нужны, как работают под капотом и какие ограничения имеют. Понять разницу между sync и async контекстом для работы с БД.
Необходимые знания
- Урок 2.2: QuerySet API
- Базовое понимание async/await в Python
- Понимание ASGI vs WSGI
Зачем async ORM
Django исторически синхронный, каждый запрос к БД блокирует поток. В WSGI это нормально, каждый запрос имеет свой поток.
В ASGI (async Django) блокирующий вызов к БД блокирует event loop, что убивает всю асинхронность:
# ASGI view - неправильно
async def product_view(request, product_id):
# Это блокирует event loop!
product = Product.objects.get(id=product_id) # sync DB call
return JsonResponse({"name": product.name})
Для ASGI нужны async ORM методы:
# ASGI view - правильно
async def product_view(request, product_id):
product = await Product.objects.aget(id=product_id) # async DB call
return JsonResponse({"name": product.name})
Если приложение работает на WSGI (Gunicorn, uWSGI), async ORM не нужен. Если на ASGI (Daphne, Uvicorn), то нужен.
Async методы QuerySet
Django предоставляет async версии всех основных методов с префиксом a:
Получение объектов
# Синхронные → Асинхронные
product = Product.objects.get(id=1)
product = await Product.objects.aget(id=1)
product = Product.objects.get_or_create(slug="iphone", defaults={"name": "iPhone"})
product, created = await Product.objects.aget_or_create(slug="iphone", defaults={"name": "iPhone"})
product = Product.objects.update_or_create(slug="iphone", defaults={"name": "iPhone"})
product, created = await Product.objects.aupdate_or_create(...)
product = Product.objects.first()
product = await Product.objects.afirst()
product = Product.objects.last()
product = await Product.objects.alast()
exists = Product.objects.filter(slug="iphone").exists()
exists = await Product.objects.filter(slug="iphone").aexists()
count = Product.objects.filter(is_active=True).count()
count = await Product.objects.filter(is_active=True).acount()
Создание и обновление
product = Product.objects.create(name="Test", price=Decimal("100"))
product = await Product.objects.acreate(name="Test", price=Decimal("100"))
updated = Product.objects.filter(is_active=False).update(stock=0)
updated = await Product.objects.filter(is_active=False).aupdate(stock=0)
deleted, _ = Product.objects.filter(stock=0).delete()
deleted, _ = await Product.objects.filter(stock=0).adelete()
Агрегация
result = Product.objects.aggregate(avg=Avg("price"))
result = await Product.objects.aaggregate(avg=Avg("price"))
Итерация - async for и aiterator()
Обычный for в async функции работает нормально, если итерируетесь по списку, словарю, range или любому другому синхронному объекту, никакого SQL нет, блокировки нет.
Проблема возникает с QuerySet. Обычный for product in queryset вызывает __iter__, который выполняет SQL запрос синхронно и блокирует event loop. Пока ждёт БД, никакие другие корутины не работают.
async for вызывает __aiter__ / __anext__. Django реализует эти методы, оборачивая запрос через sync_to_async. Event loop получает управление обратно пока ждёт БД:
async def bad_view(request):
for product in Product.objects.all(): # блокирует event loop!
...
async def good_view(request):
async for product in Product.objects.all(): # event loop свободен
...
Для итерации по большим наборам данных используйте aiterator(), он загружает данные чанками, а не всё сразу:
async def export_products():
async for product in Product.objects.aiterator():
await process_product(product)
# С only() для экономии памяти
async def send_newsletters():
async for product in Product.objects.filter(is_active=True).only("name").aiterator():
await send_email(product)
aiterator() не поддерживает prefetch_related, аналогично sync iterator().
Async в Django views
# views.py
from django.http import JsonResponse
from django.views import View
class ProductListView(View):
async def get(self, request):
products = []
async for product in Product.objects.filter(is_active=True).only("id", "name", "price"):
products.append({
"id": product.id,
"name": product.name,
"price": str(product.price),
})
return JsonResponse({"products": products})
class ProductDetailView(View):
async def get(self, request, pk):
try:
product = await Product.objects.select_related("category").aget(id=pk, is_active=True)
except Product.DoesNotExist:
return JsonResponse({"error": "Not found"}, status=404)
return JsonResponse({
"id": product.id,
"name": product.name,
"price": str(product.price),
"category": product.category.name,
})
Транзакции в async
from asgiref.sync import sync_to_async
@sync_to_async # запускает функцию в thread pool, event loop остается свободным
@transaction.atomic
def _create_order(user_id, items):
# Только sync ORM, без await
order = Order.objects.create(user_id=user_id, status="pending")
order_items = [
OrderItem(
order=order,
product_id=item["product_id"],
quantity=item["quantity"],
price=item.get("price", Decimal("0"))
)
for item in items
]
OrderItem.objects.bulk_create(order_items)
return order
async def place_order_async(user_id, items):
order = await _create_order(user_id, items)
sync_to_async по умолчанию использует thread_sensitive=True - это важно для Django, так как соединения с БД привязаны к потокам.
sync_to_async и async_to_sync
Если нужно вызвать sync код из async контекста или наоборот, используйте адаптеры из asgiref:
from asgiref.sync import sync_to_async, async_to_sync
# Обернуть sync функцию для вызова в async контексте
sync_get_products = sync_to_async(lambda: list(Product.objects.filter(is_active=True)))
async def my_view(request):
products = await sync_get_products()
# Или декоратор
@sync_to_async
def get_product(product_id):
return Product.objects.select_related("category").get(id=product_id)
async def my_view(request, pk):
product = await get_product(pk)
Django автоматически оборачивает sync views при запуске на ASGI.
Ограничения async ORM
prefetch_related_objects в async контексте
from django.db.models import prefetch_related_objects
# Sync контекст - OK
products = list(Product.objects.all())
prefetch_related_objects(products, "reviews")
# Async - используйте aprefetch_related_objects (Django 5.0+)
products = [p async for p in Product.objects.all()]
from django.db.models import aprefetch_related_objects
await aprefetch_related_objects(products, "reviews")
aprefetch_related_objects() добавлен в Django 5.0.
aiterator() с prefetch_related
# Не работает - аналогично sync
async for product in Product.objects.prefetch_related("reviews").aiterator():
...
# PrefetchRelatedLookupNotCompatibleError
Lazy QuerySet в async контексте
QuerySet ленивый: Product.objects.all() сам по себе не выполняет SQL. Запрос выполняется когда QuerySet принудительно вычисляется через list(), срез или итерацию. В async контексте list(queryset) вызывает __iter__ и выполняет SQL синхронно:
async def bad_view(request):
products = list(Product.objects.all()) # list() вызывает __iter__ - синхронный SQL!
return JsonResponse({"count": len(products)})
async def good_view(request):
count = await Product.objects.acount()
return JsonResponse({"count": count})
async def good_view_with_list(request):
# Если нужен список, async итерация
products = [p async for p in Product.objects.filter(is_active=True)]
return JsonResponse({"count": len(products)})
Когда async ORM нужен и не нужен
Async ORM нужен:
- Django ASGI с Channels, Daphne, Uvicorn
- WebSocket handlers
- Long-polling views
- Приложения с высоким concurrency и большим количеством I/O
Async ORM не нужен:
- WSGI приложения (Gunicorn, uWSGI), sync ORM работает нормально
- Celery задачи, они выполняются в отдельных workers
- Management commands
Правило: если весь ваш проект на WSGI, не трогайте async ORM. Если переходите на ASGI, используйте async методы в async views, sync методы в sync функциях.
Практическое задание
1) Напишите async view ProductListView который возвращает список активных продуктов в формате JSON. Используйте aiterator() для итерации. Запустите на Daphne или Uvicorn.
2) Напишите async view PlaceOrderView который создает заказ с позициями в транзакции. Используйте @sync_to_async + @transaction.atomic.
3) Реализуйте async функцию get_products_with_stats(category_slug) которая:
- Получает категорию через
aget() - Получает продукты через
aiterator() - Для каждого продукта получает количество отзывов через
acount() - Собирает результат
Обратите внимание: вызов acount() для каждого продукта, это N+1. Решите через aaggregate() или annotate().
4) Сравните производительность sync vs async views для 100 параллельных запросов (используйте asyncio.gather или httpx async). На WSGI (Gunicorn) и на ASGI (Uvicorn).
5) Используйте Product.objects.abulk_create(products) для создания 1000 продуктов в async view. Проверьте что это работает быстрее чем acreate() в цикле.
Возможные ошибки
Sync ORM в async view без sync_to_async
async def my_view(request):
# SynchronousOnlyOperation: нельзя вызывать sync ORM из async контекста
products = list(Product.objects.all())
Django выбрасывает SynchronousOnlyOperation если вызвать sync ORM из async контекста. Используйте async методы или sync_to_async.
Ленивая оценка QuerySet в list comprehension в async
async def my_view(request):
# Это sync итерация!
products = [p for p in Product.objects.all()]
# Правильно async comprehension
products = [p async for p in Product.objects.all()]
Использование async ORM в sync функциях
def sync_function():
# RuntimeError: нельзя запустить event loop внутри уже работающего
product = asyncio.run(Product.objects.aget(id=1)) # неправильно
Async методы ORM предназначены для async контекста. В sync функциях используйте обычные sync методы.
N+1 через async итерацию
async def my_view(request):
# N+1 - для каждого продукта отдельный запрос для категории
async for product in Product.objects.all():
category_name = product.category.name # sync access - проблема!
В async контексте обращение к deferred related полям не выполняет async запрос автоматически. Используйте select_related() заранее:
async for product in Product.objects.select_related("category").all():
category_name = product.category.name # данные уже загружены
Итог курса
Мы прошли путь от базовых операций ORM до продвинутых техник:
- Блок 1-2: Модели, миграции, базовые операции, фундамент
- Блок 3: Продвинутые запросы, Q, F, annotate, Subquery, Case, Window
- Блок 4: Связи и оптимизация - select_related, prefetch_related, N+1
- Блок 5: Производительность, индексы, EXPLAIN, only/defer/iterator, bulk
- Блок 6: Транзакции и целостность - atomic, select_for_update, constraints
- Блок 7: Raw SQL и возможности PostgreSQL, JSONField, full-text search, функции
- Блок 8: Архитектура - custom managers, QuerySets, soft delete
- Блок 9: Продвинутые паттерны, наследование, аудит, кэши, async
Ключевые принципы которые проходили через весь курс:
- Всегда смотрите на генерируемый SQL
- Понимайте когда QuerySet ленивый, когда выполняется
- Измеряйте: не оптимизируйте то, что не измерили
- Constraints в БД это последний рубеж, всегда добавляйте их для ключевых бизнес-правил
- N+1 - главный враг производительности Django приложений
Подписывайтесь на мой Telegram канал
Если вам нужен ментор и вы хотите научиться веб-разработке, узнать подробнее
Авторизуйтесь, чтобы оставить комментарий.
Нет комментариев.
Тут может быть ваша реклама
Пишите info@aisferaic.ru