When Async Django Views Pay Off in Production

Decide when async views improve a production Django app: concurrent external API calls, streaming, and long-polling benefit, while ORM-heavy views usually do not. Measure, avoid blocking calls, and run async code under ASGI correctly.

Problem statement

Django supports async def views, an async ORM interface, and ASGI servers. It is tempting to convert views to async expecting a speed-up. For a typical view that runs a few database queries and renders a template, async brings little or nothing, and can be slightly slower because sync code runs in threads. Async pays off in specific situations: waiting on several slow external services at once, holding many concurrent connections open, or streaming responses. Knowing which views fit, and measuring, avoids complexity without benefit.

Where async helpsView waits on I/OAPIs, LLMs, webhooksasync + gatherwaits overlapEvent loopserves othersORM-heavy viewslittle gainAsync wins when a view spends its time waiting on several external calls at once.
  1. Async helps views that wait: several HTTP calls to external APIs, LLM or search services, long-polling, server-sent events, WebSockets.
  2. Async rarely helps views dominated by database queries and Python work; the ORM runs in a thread pool anyway.
  3. Run async views under ASGI (Uvicorn workers, Granian, Daphne); under WSGI they work but run in a per-request event loop with no benefit.
  4. Use async clients (httpx.AsyncClient) and asyncio.gather for concurrent calls.
  5. Never call blocking code in async views; use the async ORM (aget, async for) or sync_to_async.
  6. Measure latency and throughput before and after on realistic traffic.

Step-by-step solution

1. Find candidate views

Look at request traces or logs for views where most time is spent waiting on external services: payment providers, shipping rates from several carriers, AI completions, aggregating data from multiple internal APIs. A view that calls three APIs sequentially at 300 ms each takes 900 ms; running them concurrently takes about 300 ms.

2. Rewrite with concurrent I/O

import asyncio
import httpx
from django.http import JsonResponse

CARRIERS = ["https://api.carrier-a.example/rates", "https://api.carrier-b.example/rates", "https://api.carrier-c.example/rates"]

async def shipping_quotes(request):
    params = {"zip": request.GET["zip"], "weight": request.GET["weight"]}
    async with httpx.AsyncClient(timeout=5) as client:
        responses = await asyncio.gather(*(client.get(u, params=params) for u in CARRIERS), return_exceptions=True)
    quotes = [r.json() for r in responses if isinstance(r, httpx.Response) and r.status_code == 200]
    return JsonResponse({"quotes": quotes})

return_exceptions=True keeps one failing carrier from failing the whole view. In production, reuse a client across requests rather than creating one per request.

three 300 ms API calls (illustrative)sync view, sequential930 msasync view, sequential awaits920 msasync view, asyncio.gather320 ms0 ms500 ms1000 msAsync alone changes little; concurrency inside the view is the windjango-deployment.com
Illustrative numbers: awaiting calls one by one is no faster than sync code.

3. Run under ASGI

gunicorn config.asgi:application -k uvicorn_worker.UvicornWorker --workers 4

Under WSGI, Django runs each async view in its own event loop for that request, so there is no concurrency across requests and some overhead. Under ASGI, a worker's event loop serves other requests while one view awaits.

4. Keep the event loop unblocked

Any blocking call (sync ORM access, requests.get, time.sleep, heavy CPU work) inside an async view stalls every request on that worker. Django guards the ORM with SynchronousOnlyOperation; other libraries have no guard. Use:

order = await Order.objects.select_related("customer").aget(pk=pk)
items = [i async for i in order.items.all()]
result = await sync_to_async(legacy_pricing)(order.pk)

5. Mixed apps are fine

Under ASGI, sync views still work: Django runs them in a thread pool (with thread_sensitive defaults). Convert only the views that benefit. Middleware should be async-capable to avoid extra context switches; most of Django's built-in middleware is.

6. Measure

Load-test the converted endpoints and compare p50, p95, and throughput, plus CPU and memory per worker. If gains are small, revert: sync code is simpler to debug.

should this view be async?Where does the view spend its time?several external callsAsync + gatheroverlapping waitsopen connections, streamsAsync under ASGISSE, WebSocketsDB queries, templatesKeep it syncoptimise queries insteadProfile first; async is a tool for waiting, not for computingdjango-deployment.com
Most Django views belong in the third branch.

Verification check: compare a load test of the endpoint before and after (for example 50 concurrent users) and confirm lower p95 latency and no SynchronousOnlyOperation errors in logs; check that worker CPU did not rise significantly.

Explanation

An event loop lets one thread make progress on many tasks by switching between them whenever one awaits I/O. A view that awaits several network calls at once overlaps their waiting time, and the worker serves other requests while those calls are in flight. Django's database layer is still synchronous underneath (the async ORM methods run queries in a thread pool), so views dominated by database work do not get faster by becoming async. The benefit is therefore specific to I/O concurrency, not a general speed-up.

Edge cases / notes

  • Timeouts. Always set timeouts on async HTTP clients; a hung call holds the coroutine indefinitely.
  • Connection pools. Reuse httpx.AsyncClient instances; creating one per request wastes TLS handshakes.
  • Testing uses django.test.AsyncClient and async def test methods.
  • Celery remains the right tool for work that does not need to finish within the request.

Next steps

FAQ

Are async Django views faster?

Only when the view waits on I/O concurrently. ORM-heavy views see little or no gain.

Do async views need an ASGI server?

To benefit, yes. Under WSGI they run but without concurrency across requests.

Can I mix sync and async views?

Yes. Under ASGI, Django runs sync views in a thread pool.

Is the Django ORM async?

It has an async interface (aget, async for), but queries still run in threads underneath.

Keep going