Por que o Django é "lento" — mas o Instagram ainda usa?

Published on: 2026-06-29
Post image
pt django python performance wsgi asgi gil concorrencia async escalabilidade instagram

O Django carrega fama de “lento” há anos. Mesmo com todo o “batteries included”, o veredito que sempre aparece é o mesmo: “não aguenta escala”. Aí bate a dúvida clássica: vale a pena abandonar o Django e correr para um framework “rápido” em Node.js, Go ou Rust?

Mas tem um detalhe que não fecha com essa história. Se o Django é tão lento assim, por que gigantes como Instagram e Pinterest continuam rodando nele? Este artigo destrincha o que “lento” realmente significa aqui, de onde vem a diferença de performance entre Python e Go/Rust, e quando — se é que — vale trocar.

Antes de tudo: quatro conceitos

Quase toda confusão sobre performance some quando esses quatro conceitos ficam claros:

  • Concorrência: um trabalhador alternando entre várias tarefas.
  • Paralelismo: vários trabalhadores tocando várias tarefas ao mesmo tempo.
  • I/O-bound: o trabalho está esperando algo externo (banco, rede). É como olhar a torradeira esperando a torrada pular.
  • CPU-bound: o trabalho está sendo feito de verdade (cálculo, criptografia, serialização). É como moer o café na mão.

Em cima disso, dois modelos de execução. No sync, um trabalhador faz uma tarefa por vez, do início ao fim, sem desviar: o garçom anota o pedido, vai até a cozinha, espera ficar pronto, entrega — e só então atende o próximo cliente. No async, um trabalhador cuida de várias tarefas com um event loop coordenando: sempre que uma tarefa trava esperando I/O, o loop passa outra para o trabalhador, em vez de deixá-lo parado. O garçom anota o pedido, deixa na cozinha e já vai anotar o do próximo; quando a cozinha avisa que ficou pronto, ele volta para entregar.

O Django só é “lento” quando o tráfego explode

Com uma requisição comum (nem muito CPU, nem muito I/O), praticamente todo framework tem a mesma velocidade. O “lento” só aparece no volume. Vale um modelo simplificado para criar intuição: imagine 100 mil requisições, cada uma precisando de 1 segundo de I/O e 1 segundo de CPU, com um único worker.

  • Modo WSGI (sync): o worker faz uma tarefa por vez — 2 segundos cada — e a fila leva ~200 mil segundos para escoar.
  • Modo ASGI (async) — Django ou FastAPI, tanto faz: o async “esconde” o segundo de I/O (porque o worker pula para outra requisição enquanto espera), mas o segundo de CPU continua na fila. Dá ~100 mil segundos. Metade do tempo, na mesma máquina.

Esse número é uma ilustração de um worker para fixar a ideia — na vida real você roda vários processos e cores. Mas a lição vale: async ajuda no I/O, não no CPU. Na prática, o Django moderno deixa você escolher por view:

# Sync view (WSGI): o worker fica bloqueado durante a chamada ao banco
def profile(request):
    user = User.objects.get(id=request.user.id)   # blocks here
    return JsonResponse({"name": user.name})

# Async view (ASGI): o worker serve outras requisições enquanto espera o I/O
async def profile(request):
    user = await User.objects.aget(id=request.user.id)   # frees the loop
    return JsonResponse({"name": user.name})

Por que o Python em si é mais lento que Go ou Rust?

Quando se compara a execução pura do código (a parte de CPU), o Python perde para Go e Rust por três motivos bem concretos.

1. O GIL (Global Interpreter Lock). O CPython tem um cadeado com uma regra dura: apenas uma thread executa bytecode Python por vez. Isso torna multi-threading praticamente inútil para trabalho CPU-bound (para I/O-bound ainda ajuda). Resultado prático: rodar o mesmo código CPU-bound em 1 core ou em 8 cores dá quase no mesmo, porque 7 cores ficam ociosos. Go, por outro lado, já nasce com paralelismo real e um modelo de concorrência embutido (goroutines).

A saída no Python é multiprocessing: cada processo tem o seu próprio GIL, então vários processos rodam de verdade em paralelo.

# Threads não ajudam trabalho CPU-bound no CPython (GIL). Processos ajudam.
from concurrent.futures import ProcessPoolExecutor

def crunch(n):
    return sum(i * i for i in range(n))

# Um GIL por processo => paralelismo real entre os cores
with ProcessPoolExecutor() as pool:
    results = list(pool.map(crunch, [10_000_000] * 8))

2. O intermediário. O Python compila o seu código para bytecode e, em tempo de execução, o CPython interpreta esse bytecode instrução por instrução. Go e Rust compilam para código de máquina antes do deploy, então a CPU executa direto, sem intermediário.

3. Tipagem dinâmica. A cada a + b, o Python precisa perguntar em runtime: “o que é a? Um int? Uma string? Uma lista? Qual + usar?”. Go e Rust já conhecem os tipos em tempo de compilação e emitem uma única instrução de soma da CPU. Zero perguntas.

Como o Django escala mesmo assim

Como o gargalo de CPU vem do GIL, a forma clássica de escalar é rodar vários processos — um por core — atrás de um servidor de aplicação. É assim que se arranca paralelismo real de uma stack Python:

# WSGI: vários processos = paralelismo real (cada processo tem seu próprio GIL)
gunicorn myproject.wsgi:application --workers 8

# ASGI: worker async para concorrência de I/O, escalado em vários processos
gunicorn myproject.asgi:application -k uvicorn.workers.UvicornWorker --workers 8

O segredo não é um core mágico mais rápido, e sim multiplicar processos e máquinas, com o banco e o cache aguentando o tranco. É engenharia de infraestrutura, não de linguagem.

Então o Python está condenado na web?

De jeito nenhum. Sim, o Python é “mais lento” para servir requisição pura — mas isso atende com folga startups e empresas de porte médio. Tudo é trade-off: o Django é rápido de desenvolver, vem com tudo incluso, e a performance pode ser compensada com infraestrutura mais forte. Go e Rust são mais lentos de construir, mas servem mais rápido na mesma infra.

Veredito

Velocidade importa. Mas entregar importa mais. Não abandone o Django só por causa de benchmark. Cresça com ele até que um serviço específico realmente bata numa parede de performance — digamos, na casa dos 10 mil requests por segundo — e aí sim extraia aquele serviço e reescreva em algo mais eficiente, como Go, Rust ou Java. É otimização cirúrgica, no ponto que dói, não um rewrite por medo.

TL;DR

O Instagram não largou o Django — ele clonou o Django em milhares de servidores. O trabalho pesado de verdade aconteceu nos bancos de dados e caches, que foram fatiados (sharding), trocados e reconstruídos para suportar o volume gigante de requisições.

A sugestão que fica: domine um framework a fundo e entenda os demais por cima. Conhecer os prós e contras de cada um é o que permite tomar a decisão certa na hora certa — inclusive a decisão de não trocar de stack. Às vezes, a escolha de infraestrutura mais inteligente é a mais sem-graça: continuar no que você já entrega bem e escalar com cabeça. 🚀