Views assíncronas do Django e ASGI em 2026: performance e perguntas de entrevista

Uma análise aprofundada das views assíncronas do Django e do ASGI em 2026: como funcionam por baixo dos panos, qual servidor implantar, a ORM assíncrona e a armadilha do SynchronousOnlyOperation, além de perguntas de entrevista.

Views assíncronas do Django e arquitetura ASGI atendendo requisições concorrentes

As views assíncronas do Django permitem que um único worker atenda centenas de requisições concorrentes limitadas por I/O sem dedicar uma thread a cada conexão, mas isso só acontece quando o caminho de execução permanece assíncrono da view até cada chamada de rede. Desde que o Django 3.1 introduziu as views async def e o Django 4.1 acrescentou a interface de ORM assíncrona, o framework se tornou uma plataforma ASGI confiável, ainda que a maioria dos deploys em produção continue rodando sob WSGI e deixe essa concorrência intocada. Esta análise explica como as views assíncronas se comportam sob ASGI, quais servidores usar em 2026, as armadilhas da ORM que disparam SynchronousOnlyOperation e as perguntas de entrevista que separam quem já colocou Django assíncrono em produção de quem apenas leu a respeito.

Quando o async realmente ajuda

As views assíncronas compensam quando uma requisição passa a maior parte do tempo esperando por I/O que não está sob seu controle: APIs HTTP de terceiros, serviços downstream lentos ou respostas de streaming de longa duração. Para trabalho limitado por CPU ou consultas rápidas ao banco local, uma view síncrona apoiada em mais workers do Gunicorn costuma ser mais rápida e muito mais fácil de raciocinar.

Como as views assíncronas do Django funcionam sob ASGI

O WSGI é fundamentalmente síncrono: cada requisição em andamento ocupa uma thread ou processo de worker do início ao fim, de modo que 50 requisições lentas exigem 50 workers. O ASGI substitui esse modelo por um event loop que intercala muitas requisições em um único worker, suspendendo uma coroutine sempre que ela aguarda I/O e retomando-a quando os dados chegam. A documentação oficial de async do Django descreve os dois adaptadores que tornam essa coexistência possível: sob ASGI, o Django executa as views async def nativamente no loop e empurra as views síncronas para um threadpool com sync_to_async; sob WSGI, ele envolve as views assíncronas em async_to_sync e as executa até o fim, descartando qualquer benefício de concorrência.

A consequência prática é direta: uma view async def implantada em um servidor WSGI se comporta como uma view síncrona mais lenta. A concorrência só se materializa em um servidor ASGI. Uma view assíncrona mínima que chama uma API externa é quase idêntica à sua contraparte síncrona, com a diferença concentrada no await.

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

async def dashboard(request):
    # Sob ASGI esta coroutine roda no event loop, então o worker fica
    # livre para atender outras requisições enquanto o httpx aguarda o ida-e-volta na rede.
    async with httpx.AsyncClient(timeout=5.0) as client:
        response = await client.get("https://api.example.com/metrics")
    return JsonResponse(response.json())

O ganho aqui não é que a requisição individual termine mais rápido; ela leva o mesmo tempo de relógio. O ganho é que o worker não fica bloqueado durante o await, então o throughput sob carga concorrente sobe enquanto a quantidade de processos permanece constante.

O mesmo modelo libera padrões de resposta que são desajeitados sob WSGI. Desde o Django 4.2, o StreamingHttpResponse aceita um gerador assíncrono, de modo que uma view pode emitir chunks à medida que uma fonte upstream os produz, o que se encaixa em Server-Sent Events e em streams de tokens de LLM repassados, nos quais a conexão fica aberta por segundos. Protocolos bidirecionais persistentes como WebSockets ainda passam pelo Django Channels em vez de views HTTP comuns, mas ambos se apoiam na mesma base ASGI, então um projeto que adota views assíncronas já pagou o custo de infraestrutura para recursos em tempo real no futuro.

Escolhendo um servidor ASGI para Django em 2026

O Django fornece um objeto de aplicação ASGI em asgi.py, mas ele não roda sozinho; um servidor ASGI dedicado conduz o event loop. A especificação do ASGI define a interface que cada um desses servidores implementa, e é por isso que eles são intercambiáveis no nível de protocolo e diferem principalmente em performance e cobertura de protocolos.

| Servidor | Implementado em | Melhor uso | |--------|----------------|----------| | Uvicorn | Python (uvloop) | ASGI geral, HTTP/1.1, o padrão mais comum | | Daphne | Python | Django Channels, WebSockets, HTTP/2 | | Granian | Rust | Maior throughput bruto, HTTP/1 e HTTP/2 | | Hypercorn | Python | HTTP/2 e HTTP/3 (QUIC) |

Para a maioria dos deploys de 2026, o Uvicorn executado como uma classe de worker do Gunicorn continua sendo a escolha confiável: o Gunicorn supervisiona o ciclo de vida do processo, reinicia workers que caíram e trata sinais, enquanto o Uvicorn conduz o event loop dentro de cada um. Um detalhe de migração atrapalha os upgrades: as classes de worker do Gunicorn saíram do núcleo do Uvicorn para um pacote separado uvicorn-worker a partir do Uvicorn 0.30, então o caminho de import mudou.

bash
# Instale primeiro o pacote do worker: pip install uvicorn-worker
gunicorn myproject.asgi:application \
    --workers 4 \
    --worker-class uvicorn_worker.UvicornWorker \
    --bind 0.0.0.0:8000

Times que buscam throughput máximo cada vez mais recorrem ao Granian, um servidor baseado em Rust que remove o overhead de parsing HTTP do lado do Python. Qualquer que seja a escolha, o processo de deploy é a razão pela qual o async só aparece em produção quando é configurado deliberadamente; a configuração específica de ASGI reflete o cuidado operacional abordado no módulo de perguntas de entrevista sobre deploy de Django.

A ORM assíncrona e a armadilha do SynchronousOnlyOperation

O Django 4.1 acrescentou métodos de consulta assíncronos por toda a ORM: aget, acreate, asave, adelete, acount, aexists, aaggregate e iteração com async for. Eles oferecem uma superfície amigável ao async, mas uma ressalva importante sobrevive até 2026: a camada de banco de dados por baixo não é nativamente assíncrona. Mesmo com o psycopg3, o Django executa as consultas em um threadpool e as expõe por meio de wrappers assíncronos, de modo que a ORM assíncrona melhora a ergonomia e evita bloquear o loop sem entregar um I/O de banco realmente assíncrono de ponta a ponta.

O modo de falha mais comum é chamar um método síncrono da ORM diretamente dentro de uma view assíncrona. O Django detecta o contexto assíncrono e recusa, disparando SynchronousOnlyOperation para impedir que uma chamada bloqueante congele o event loop para todas as outras requisições que compartilham aquele worker.

SynchronousOnlyOperation é um recurso, não um bug

A exceção é uma trava de segurança, não um bug. Recorrer a DJANGO_ALLOW_ASYNC_UNSAFE=true para silenciá-la reintroduz exatamente o comportamento bloqueante que o async deveria eliminar. A correção certa é um método assíncrono da ORM (aget no lugar de get) ou um wrapper explícito com sync_to_async em torno de código que não tem equivalente assíncrono.

As views assíncronas ficam limpas de ler assim que os métodos de consulta assíncronos substituem seus gêmeos síncronos. O padrão abaixo conta as linhas e percorre os dez primeiros registros sem nunca sair do event loop.

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

async def latest_articles(request):
    # acount() e a iteração assíncrona são wrappers async-safe em torno da 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})

Alguns códigos não têm contraparte assíncrona: um SDK de terceiros que só oferece chamadas síncronas, ou uma cadeia densa de travessias da ORM que não vale a pena reescrever. Envolvê-lo em sync_to_async da biblioteca asgiref transfere o trabalho para uma thread e mantém o loop responsivo. O argumento thread_sensitive=True é o padrão seguro porque roteia toda chamada envolvida para uma mesma thread executora compartilhada, preservando o estado de conexão com o banco e de transação que quebraria se as chamadas se espalhassem por threads arbitrárias.

python
# views.py
from asgiref.sync import sync_to_async
from .services import build_report  # uma função síncrona

async def report_view(request):
    # thread_sensitive=True mantém as chamadas envolvidas em uma única thread compartilhada,
    # preservando o estado de conexão e de transação através da fronteira.
    report = await sync_to_async(build_report, thread_sensitive=True)(request.user.id)
    return JsonResponse(report)

Performance assíncrona do Django: quando a concorrência vence

A performance assíncrona do Django é uma história sobre sobreposição de I/O, não sobre velocidade bruta. O ganho mais claro vem de disparar chamadas de rede independentes com asyncio.gather: três requisições externas de 200ms cada custam cerca de 600ms em sequência, mas terminam em torno de 200ms de forma concorrente, porque os awaits se sobrepõem no mesmo loop.

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:
        # Três chamadas independentes rodam de forma concorrente em vez de uma após a outra.
        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(),
    })

O caso inverso importa tanto quanto. Uma view que executa uma única consulta local rápida não ganha nada com async e muitas vezes perde, porque cada requisição agora paga o agendamento do event loop mais um salto para o threadpool até o driver síncrono do banco. O async também impõe um imposto mais sutil em cada fronteira sync/async: misturar middleware síncrono e assíncrono obriga o Django a alternar entre o loop e uma thread a cada transição, e uma requisição que cruza essa linha várias vezes por resposta acumula latência real. Manter a stack de middleware uniformemente assíncrona, ou uniformemente síncrona, elimina essas alternâncias.

A decisão se reduz ao formato da carga de trabalho, e não a uma preferência por sintaxe moderna.

| Carga de trabalho | Melhor padrão | |----------|----------------| | Várias chamadas lentas a APIs externas por requisição | View assíncrona com asyncio.gather | | Streaming de longa duração ou Server-Sent Events | View assíncrona com um gerador assíncrono | | Consulta única e rápida ao banco, leve em CPU | View síncrona, mais workers do Gunicorn | | Renderização ou computação limitada por CPU | View síncrona, delegar a uma fila de tarefas |

Para jobs realmente demorados, as views assíncronas são a ferramenta completamente errada; manter uma requisição aberta para fazer minutos de trabalho desperdiça a conexão independentemente do modelo de concorrência. Esse trabalho pertence a um worker em background, um trade-off explorado no guia sobre tarefas assíncronas com Celery no Django.

A adoção deve seguir a medição, não a intuição. A forma honesta de justificar o async é submeter o endpoint específico a um teste de carga com uma ferramenta como Locust ou k6 sob concorrência realista, comparando uma versão síncrona em N workers com uma versão assíncrona no mesmo hardware. Endpoints dominados por uma única chamada externa de alta latência mostram uma diferença ampla a favor do async; endpoints dominados por consultas rápidas frequentemente colocam o async um pouco atrás quando se contabiliza o salto pelo threadpool. Fazer o profiling da requisição primeiro também revela se o gargalo é de fato I/O, já que o async não faz nada por uma view que é lenta por causa de consultas sem índice, um problema tratado no passo a passo sobre otimização de consultas na ORM do Django.

Pronto para mandar bem nas entrevistas de Django?

Pratique com nossos simuladores interativos, flashcards e testes tecnicos.

Perguntas de entrevista sobre Django assíncrono para 2026

Uma entrevista sobre Django assíncrono investiga se a pessoa candidata entende o modelo ou apenas reconhece as palavras-chave. As perguntas abaixo refletem o que as entrevistas de nível sênior realmente cobram em 2026, cada uma acompanhada da resposta que uma pessoa engenheira experiente daria.

O que muda quando uma view assíncrona roda sob WSGI em vez de ASGI? Sob WSGI, o Django envolve a coroutine em async_to_sync e a executa até o fim na thread da requisição, então ela se comporta como uma view síncrona mais lenta, sem nenhum benefício de concorrência. Apenas um servidor ASGI rodando um event loop permite que o worker atenda outras requisições durante um await.

Por que Article.objects.get(pk=1) dispara SynchronousOnlyOperation dentro de uma view assíncrona? A chamada síncrona da ORM bloquearia o event loop, travando todas as outras requisições daquele worker. O Django detecta o contexto assíncrono e recusa. A correção é o método assíncrono await Article.objects.aget(pk=1), ou sync_to_async para código sem equivalente assíncrono.

A ORM assíncrona do Django é realmente não bloqueante? Não. Os métodos assíncronos são wrappers ergonômicos; as consultas subjacentes ainda rodam em um threadpool porque os drivers de banco não são integrados para execução assíncrona nativa de ponta a ponta. O benefício é manter o loop livre, não I/O de banco paralelo no nível do driver.

Quando escolher views assíncronas em vez de uma fila de tarefas como o Celery? As views assíncronas servem para concorrência de I/O no escopo da requisição que precisa terminar antes da resposta, como agregar várias APIs. Trabalho que sobrevive à requisição, precisa de retentativas ou roda por minutos pertence ao Celery ou às background tasks do Django, já que manter uma conexão HTTP aberta para isso desperdiça recursos do servidor.

Qual é o custo de misturar middleware síncrono e assíncrono? Cada transição entre um componente síncrono e um assíncrono obriga o Django a saltar entre o event loop e uma thread via sync_to_async ou async_to_sync. Uma requisição que cruza essa fronteira repetidamente paga um overhead cumulativo, então uma stack de middleware uniforme tem desempenho melhor do que uma intercalada.

Conclusão

  • Views assíncronas só entregam concorrência em um servidor ASGI; a mesma view sob WSGI roda de forma síncrona por meio de async_to_sync.
  • Implante o Uvicorn via pacote uvicorn-worker sob o Gunicorn, ou o Granian para throughput máximo, e trate a configuração de ASGI como uma etapa explícita de deploy.
  • Recorra a views assíncronas quando uma requisição espera por várias chamadas externas lentas de I/O; mantenha síncronos, com mais workers, os endpoints de consulta rápida e os limitados por CPU.
  • Use aget, acreate e async for dentro das views assíncronas, e lembre que a ORM ainda executa as consultas em um threadpool em vez de nativamente assíncrono.
  • Trate SynchronousOnlyOperation como uma trava de segurança: corrija com métodos assíncronos ou sync_to_async(thread_sensitive=True), nunca desativando a verificação.
  • Mantenha a stack de middleware uniformemente síncrona ou assíncrona para evitar pagar uma penalidade de troca de thread a cada cruzamento de fronteira.

Comece a praticar!

Teste seus conhecimentos com nossos simuladores de entrevista e testes tecnicos.

Tags

#django
#async
#asgi
#python
#performance

Compartilhar

Artigos relacionados