Django async views en ASGI in 2026: performance en interviewvragen

Een diepgaande analyse van Django async views en ASGI in 2026: hoe ze onder de motorkap werken, welke server ingezet moet worden, de async ORM en de SynchronousOnlyOperation-valkuil, plus interviewvragen.

Django async views en ASGI-architectuur die gelijktijdige requests verwerkt

Met Django async views kan één enkele worker honderden gelijktijdige I/O-gebonden requests afhandelen zonder voor elke verbinding een aparte thread te reserveren, maar alleen wanneer het codepad asynchroon blijft van de view tot aan elke netwerkaanroep. Sinds Django 3.1 async def-views introduceerde en Django 4.1 de async ORM-interface toevoegde, is het framework een geloofwaardig ASGI-platform geworden, en toch draaien de meeste productiedeployments nog steeds onder WSGI, waardoor die concurrency onbenut blijft. Dit artikel legt uit hoe async views zich onder ASGI gedragen, welke servers in 2026 het overwegen waard zijn, welke ORM-valkuilen SynchronousOnlyOperation veroorzaken, en welke interviewvragen ingenieurs die async Django daadwerkelijk in productie hebben gebracht onderscheiden van degenen die er alleen over gelezen hebben.

Wanneer async echt helpt

Async views lonen wanneer een request het grootste deel van zijn tijd wacht op I/O die het niet zelf beheert: externe HTTP-API's, trage downstream-services of langlopende streaming-responses. Voor CPU-gebonden werk of snelle lokale databasequery's is een synchrone view met meer Gunicorn-workers meestal sneller en veel eenvoudiger te doorgronden.

Hoe Django async views werken onder ASGI

WSGI is fundamenteel synchroon: elke lopende request bezet één worker-thread of -proces van begin tot eind, dus 50 trage requests vereisen 50 workers. ASGI vervangt dat model door een event loop die veel requests op één worker afwisselt: een coroutine wordt onderbroken zodra deze op I/O wacht en hervat wanneer de data binnenkomt. De officiële Django async-documentatie beschrijft de twee adapters die deze co-existentie mogelijk maken: onder ASGI draait Django async def-views native op de loop en schuift het synchrone views via sync_to_async naar een threadpool; onder WSGI verpakt het async views in async_to_sync en voert ze volledig uit, waarmee elk concurrency-voordeel verdwijnt.

De praktische consequentie is bot: een async def-view die op een WSGI-server draait, gedraagt zich als een tragere synchrone view. Concurrency ontstaat pas op een ASGI-server. Een minimale async view die een externe API aanroept, ziet er bijna identiek uit aan zijn synchrone tegenhanger; het verschil zit geconcentreerd in de await.

python
# views.py
import httpx
from django.http import JsonResponse

async def dashboard(request):
    # Onder ASGI draait deze coroutine op de event loop, zodat de worker
    # vrij is om andere requests te bedienen terwijl httpx op de netwerk-round-trip wacht.
    async with httpx.AsyncClient(timeout=5.0) as client:
        response = await client.get("https://api.example.com/metrics")
    return JsonResponse(response.json())

De winst zit hier niet in dat de enkele request sneller klaar is; die kost dezelfde wall-clock-tijd. De winst is dat de worker tijdens de await niet geblokkeerd is, waardoor de doorvoer onder gelijktijdige belasting stijgt terwijl het aantal processen gelijk blijft.

Hetzelfde model ontsluit responspatronen die onder WSGI omslachtig zijn. Sinds Django 4.2 accepteert StreamingHttpResponse een async generator, zodat een view chunks kan yielden zodra een upstream-bron ze produceert. Dat past bij Server-Sent Events en doorgestuurde LLM-tokenstreams, waarbij de verbinding seconden lang open blijft. Persistente bidirectionele protocollen zoals WebSockets lopen nog altijd via Django Channels in plaats van gewone HTTP-views, maar beide steunen op hetzelfde ASGI-fundament, waardoor een project dat async views adopteert de infrastructuurkosten voor latere realtime-functies al heeft betaald.

Een Django ASGI-server kiezen in 2026

Django levert een ASGI-applicatieobject in asgi.py, maar draait zichzelf niet; een aparte ASGI-server stuurt de event loop aan. De ASGI-specificatie definieert de interface die elk van deze servers implementeert, en juist daarom zijn ze op protocolniveau uitwisselbaar en verschillen ze vooral in performance en protocoldekking.

| Server | Geïmplementeerd in | Beste keuze voor | |--------|----------------|----------| | Uvicorn | Python (uvloop) | Algemene ASGI, HTTP/1.1, de gangbare standaard | | Daphne | Python | Django Channels, WebSockets, HTTP/2 | | Granian | Rust | Hoogste ruwe doorvoer, HTTP/1 en HTTP/2 | | Hypercorn | Python | HTTP/2 en HTTP/3 (QUIC) |

Voor de meeste deployments in 2026 blijft Uvicorn als Gunicorn worker-class de betrouwbare keuze: Gunicorn bewaakt de proceslevenscyclus, herstart gecrashte workers en verwerkt signalen, terwijl Uvicorn binnen elke worker de event loop aanstuurt. Eén migratiedetail zorgt voor problemen bij upgrades: de Gunicorn worker-classes zijn sinds Uvicorn 0.30 uit Uvicorn core verplaatst naar een apart uvicorn-worker-pakket, waardoor het importpad is gewijzigd.

bash
# Installeer eerst het worker-pakket: pip install uvicorn-worker
gunicorn myproject.asgi:application \
    --workers 4 \
    --worker-class uvicorn_worker.UvicornWorker \
    --bind 0.0.0.0:8000

Teams die maximale doorvoer nastreven grijpen steeds vaker naar Granian, een op Rust gebaseerde server die de HTTP-parsing-overhead aan de Python-kant wegneemt. Wat de keuze ook is, het deploymentverhaal verklaart waarom async pas in productie verschijnt wanneer het bewust wordt geconfigureerd; de ASGI-specifieke opzet weerspiegelt de operationele zorgvuldigheid uit de module met Django deployment-interviewvragen.

De async ORM en de SynchronousOnlyOperation-valkuil

Django 4.1 voegde asynchrone querymethoden toe over de hele ORM: aget, acreate, asave, adelete, acount, aexists, aaggregate, en iteratie met async for. Deze bieden een async-vriendelijk oppervlak, maar een cruciale kanttekening blijft in 2026 overeind: de onderliggende databaselaag is niet native asynchroon. Zelfs met psycopg3 voert Django query's uit in een threadpool en stelt het ze via async wrappers beschikbaar, waardoor de async ORM de ergonomie verbetert en blokkeren van de loop voorkomt, zonder echte end-to-end async database-I/O te leveren.

De meest voorkomende fout is het rechtstreeks aanroepen van een synchrone ORM-methode binnen een async view. Django detecteert de async-context en weigert dit door SynchronousOnlyOperation op te werpen, om te voorkomen dat een blokkerende aanroep de event loop bevriest voor elke andere request die die worker deelt.

SynchronousOnlyOperation is een feature

De exception is een vangrail, geen bug. DJANGO_ALLOW_ASYNC_UNSAFE=true gebruiken om hem het zwijgen op te leggen, herintroduceert precies het blokkerende gedrag dat async moest wegnemen. De juiste oplossing is een async ORM-methode (aget in plaats van get) of een expliciete sync_to_async-wrapper rond code zonder async-equivalent.

Async views lezen helder zodra de async querymethoden hun synchrone tegenhangers vervangen. Het onderstaande patroon telt rijen en doorloopt de eerste tien records zonder ooit de event loop te verlaten.

python
# views.py
from django.http import JsonResponse
from .models import Article

async def latest_articles(request):
    # acount() en async-iteratie zijn async-veilige wrappers rond de ORM.
    count = await Article.objects.acount()
    titles = []
    async for article in Article.objects.order_by("-created_at")[:10]:
        titles.append(article.title)
    return JsonResponse({"count": count, "titles": titles})

Sommige code heeft geen async-tegenhanger: een externe SDK die alleen synchrone aanroepen biedt, of een dichte keten van ORM-traversals die een herschrijving niet waard is. Door deze in sync_to_async uit de asgiref-library te verpakken, wordt het werk naar een thread verplaatst en blijft de loop responsief. Het argument thread_sensitive=True is de veilige standaard omdat het elke verpakte aanroep naar één gedeelde executor-thread stuurt, wat de databaseverbinding en transactiestatus behoudt die zou breken als aanroepen over willekeurige threads verspreid raakten.

python
# views.py
from asgiref.sync import sync_to_async
from .services import build_report  # een synchrone functie

async def report_view(request):
    # thread_sensitive=True houdt verpakte aanroepen op één gedeelde thread,
    # waardoor verbindings- en transactiestatus over de grens behouden blijven.
    report = await sync_to_async(build_report, thread_sensitive=True)(request.user.id)
    return JsonResponse(report)

Django async performance: wanneer concurrency wint

Django async performance gaat over overlappende I/O, niet over ruwe snelheid. De duidelijkste winst komt van het uitwaaieren van onafhankelijke netwerkaanroepen met asyncio.gather: drie externe requests van elk 200 ms kosten sequentieel ongeveer 600 ms, maar zijn gelijktijdig in ongeveer 200 ms klaar, omdat de awaits op dezelfde loop overlappen.

python
# views.py
import asyncio
import httpx
from django.http import JsonResponse

async def aggregate(request):
    async with httpx.AsyncClient(timeout=5.0) as client:
        # Drie onafhankelijke aanroepen draaien gelijktijdig in plaats van na elkaar.
        weather, prices, news = await asyncio.gather(
            client.get("https://api.example.com/weather"),
            client.get("https://api.example.com/prices"),
            client.get("https://api.example.com/news"),
        )
    return JsonResponse({
        "weather": weather.json(),
        "prices": prices.json(),
        "news": news.json(),
    })

Het omgekeerde geval telt net zo zwaar. Een view die één snelle lokale query uitvoert, wint niets bij async en verliest vaak, omdat elke request nu event-loop-scheduling plus een threadpool-sprong naar de synchrone databasedriver betaalt. Async legt bovendien een subtielere belasting op bij elke sync/async-grens: het mengen van synchrone en asynchrone middleware dwingt Django om bij elke overgang te wisselen tussen de loop en een thread, en een request die die grens meerdere keren per respons passeert, stapelt echte latentie op. Een middleware-stack die uniform async of uniform sync is, elimineert die wisselingen.

De beslissing komt neer op de vorm van de workload, niet op een voorkeur voor moderne syntaxis.

| Workload | Betere standaardkeuze | |----------|----------------| | Meerdere trage externe API-aanroepen per request | Async view met asyncio.gather | | Langlopende streaming of Server-Sent Events | Async view met een async generator | | Eén snelle databasequery, licht qua CPU | Sync view, meer Gunicorn-workers | | CPU-gebonden rendering of berekening | Sync view, uitbesteden aan een taskqueue |

Voor echt langlopende taken zijn async views volledig het verkeerde gereedschap; een request openhouden om minutenlang werk te doen verspilt de verbinding, ongeacht het concurrency-model. Dat werk hoort thuis in een background worker, een afweging die de gids over Celery async tasks in Django uitdiept.

Adoptie moet meting volgen, niet intuïtie. De eerlijke manier om async te rechtvaardigen is het specifieke endpoint te load-testen met een tool als Locust of k6 onder realistische concurrency, waarbij een synchrone versie op N workers wordt vergeleken met een async versie op dezelfde hardware. Endpoints die worden gedomineerd door één externe aanroep met hoge latentie tonen een groot verschil in het voordeel van async; endpoints die worden gedomineerd door snelle query's laten async vaak licht achterblijven zodra de threadpool-sprong wordt meegeteld. Het request eerst profileren onthult bovendien of de bottleneck überhaupt I/O is, want async doet niets voor een view die traag is door niet-geïndexeerde query's, een probleem dat de walkthrough over Django ORM-query's optimaliseren behandelt.

Klaar om je Django gesprekken te halen?

Oefen met onze interactieve simulatoren, flashcards en technische tests.

Django async interviewvragen voor 2026

Een Django async-interview toetst of een kandidaat het model begrijpt of enkel de trefwoorden herkent. De onderstaande vragen weerspiegelen wat senior-interviews in 2026 daadwerkelijk stellen, elk gekoppeld aan het antwoord dat een ervaren ingenieur geeft.

Wat verandert er wanneer een async view onder WSGI draait in plaats van ASGI? Onder WSGI verpakt Django de coroutine in async_to_sync en voert deze volledig uit op de request-thread, waardoor hij zich als een tragere synchrone view gedraagt zonder enig concurrency-voordeel. Alleen een ASGI-server met een draaiende event loop laat de worker tijdens een await andere requests bedienen.

Waarom werpt Article.objects.get(pk=1) een SynchronousOnlyOperation op binnen een async view? De synchrone ORM-aanroep zou de event loop blokkeren en elke andere request op die worker stilleggen. Django detecteert de async-context en weigert. De oplossing is de async-methode await Article.objects.aget(pk=1), of sync_to_async voor code zonder async-equivalent.

Is de async ORM van Django werkelijk non-blocking? Nee. De async-methoden zijn ergonomische wrappers; de onderliggende query's draaien nog steeds in een threadpool omdat de databasedrivers niet end-to-end geïntegreerd zijn voor native async-uitvoering. Het voordeel is dat de loop vrij blijft, niet parallelle database-I/O op driverniveau.

Wanneer moeten async views worden gekozen boven een taskqueue zoals Celery? Async views passen bij request-gebonden I/O-concurrency die vóór de respons klaar moet zijn, zoals het aggregeren van meerdere API's. Werk dat de request overleeft, retries nodig heeft of minutenlang draait, hoort thuis in Celery of Django's background tasks, aangezien een HTTP-verbinding daarvoor openhouden serverbronnen verspilt.

Wat kost het mengen van sync- en async-middleware? Elke overgang tussen een synchroon en een asynchroon component dwingt Django om via sync_to_async of async_to_sync te wisselen tussen de event loop en een thread. Een request die die grens herhaaldelijk passeert, betaalt cumulatieve overhead, dus een uniforme middleware-stack presteert beter dan een afwisselende.

Conclusie

  • Async views leveren alleen concurrency op een ASGI-server; dezelfde view draait onder WSGI synchroon via async_to_sync.
  • Zet Uvicorn in via het uvicorn-worker-pakket onder Gunicorn, of Granian voor maximale doorvoer, en behandel de ASGI-opzet als een expliciete deploymentstap.
  • Grijp naar async views wanneer een request wacht op meerdere trage externe I/O-aanroepen; houd endpoints met snelle query's en CPU-gebonden endpoints synchroon met meer workers.
  • Gebruik aget, acreate en async for binnen async views, en onthoud dat de ORM query's nog steeds in een threadpool draait in plaats van native async.
  • Behandel SynchronousOnlyOperation als een vangrail: los het op met async-methoden of sync_to_async(thread_sensitive=True), nooit door de controle uit te schakelen.
  • Houd de middleware-stack uniform synchroon of asynchroon om te voorkomen dat elke grensovergang een thread-switch-straf kost.

Begin met oefenen!

Test je kennis met onze gespreksimulatoren en technische tests.

Tags

#django
#async
#asgi
#python
#performance

Delen

Gerelateerde artikelen