Django ORM

Async в Django ORM | Курс Django ORM урок 9.3

Async в Django ORM | Курс Django ORM урок 9.3
Mikhail
Автор
Mikhail
Опубликовано 16.03.2026
0,0
Views 15

Цель урока

Разобрать асинхронные методы 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 приложений

<< Урок 9.2


Подписывайтесь на мой Telegram канал

Если вам нужен ментор и вы хотите научиться веб-разработке, узнать подробнее

Авторизуйтесь, чтобы оставить комментарий.

Комментариев: 0

Нет комментариев.

Тут может быть ваша реклама

Пишите info@aisferaic.ru

Похожие туториалы