O ajuste de desempenho de uma API em FastAPI envolve mais do que a velocidade do framework. A performance final depende de como a aplicação é executada em produção, como as conexões são gerenciadas, quantos processos atendem requisições e como o tráfego chega até o serviço.
Uma implantação bem configurada costuma combinar múltiplos workers (processos de atendimento), um servidor de processos como Gunicorn, um proxy reverso como Nginx e camadas de cache. Esses elementos reduzem gargalos comuns, aumentam concorrência e estabilizam picos de carga sem exigir infraestrutura excessiva.
Base de desempenho: concorrência assíncrona e o “event loop”
FastAPI se destaca por suportar I/O assíncrono, ou seja, operações de entrada e saída que podem aguardar resultados sem travar o servidor. Essa capacidade é sustentada por um componente chamado event loop, que coordena múltiplas tarefas enquanto aguarda rede, disco ou banco de dados. Em endpoints async, o servidor pode atender várias requisições de forma eficiente enquanto uma delas espera uma consulta ou chamada externa. A vantagem some quando uma tarefa longa bloqueia o event loop e impede outras de avançarem.
O uso de async def é mais indicado quando o endpoint realiza chamadas de rede, consultas ao banco ou leituras e gravações em arquivo. Já funções def costumam ser mais apropriadas para trabalho pesado de CPU, como cálculo intenso, processamento de imagem e inferência de modelos. Misturar cálculo pesado dentro de uma função assíncrona cria o efeito de “travamento”, pois a função não libera o event loop para alternar entre tarefas. Essa distinção ajuda a manter a concorrência real em ambientes com muitas requisições simultâneas.
Servidor de aplicação em produção: Uvicorn, Gunicorn e workers
Uvicorn é um servidor ASGI, isto é, um servidor que entende o padrão assíncrono usado por FastAPI. Executar apenas um processo de Uvicorn pode funcionar em desenvolvimento, mas em produção é comum precisar de múltiplos processos para aproveitar melhor os núcleos de CPU e isolar falhas. Gunicorn é um gerenciador de processos que cria e supervisiona vários workers, reiniciando-os quando necessário. A combinação mais usada é Gunicorn controlando workers do tipo Uvicorn, unindo supervisão com execução ASGI.
Workers são processos independentes que atendem requisições em paralelo, e cada processo tem seu próprio event loop. Isso permite que a aplicação use mais de um núcleo de CPU e distribua carga entre processos. Um número inadequado de workers pode causar subutilização do servidor ou excesso de overhead, quando processos demais competem por CPU e memória. Por isso, a contagem de workers é um ponto central do ajuste de desempenho.
Escolha do número ideal de workers (e por que a fórmula não basta)
Uma regra prática conhecida é (núcleos de CPU × 2) + 1, usada como ponto de partida para aplicações web. Ela busca equilibrar concorrência e custo de contexto entre processos, principalmente quando há I/O. Em um servidor com 4 núcleos, essa conta sugere 9 workers, o que pode funcionar bem em APIs com muitas esperas de rede. Ainda assim, a melhor contagem depende do perfil real da carga e do tipo de endpoint predominante.
Em serviços I/O-bound (limitados por entrada/saída), mais workers podem aumentar a capacidade de lidar com conexões simultâneas. Em serviços CPU-bound (limitados por CPU), workers demais tendem a piorar a latência por disputa de CPU e aumento de trocas de contexto. A avaliação correta envolve observar consumo de CPU, tempo de resposta e filas de requisições sob carga. O objetivo prático é manter alta vazão sem criar saturação constante de CPU ou memória.
A seguir está um exemplo de inicialização com Gunicorn usando workers Uvicorn, que representa um padrão estável para produção.
gunicorn main:app \
--workers 4 \
--worker-class uvicorn.workers.UvicornWorker \
--bind 0.0.0.0:8000 \
--timeout 30
Nginx como proxy reverso: proteção, estabilidade e melhor uso de rede
Nginx é um servidor web frequentemente usado como proxy reverso, isto é, um intermediário que recebe requisições da internet e as encaminha para a aplicação interna. Essa camada evita expor Gunicorn diretamente e adiciona recursos importantes de produção. Entre eles estão terminação de TLS/HTTPS, buffering de clientes lentos e controle mais previsível de conexões. Em picos de tráfego, o Nginx ajuda a suavizar variações e reduzir impacto direto sobre os workers.
Além disso, o Nginx pode padronizar cabeçalhos, registrar logs de acesso e impor limites básicos. Encaminhar IP real e cadeia de encaminhamento é essencial para auditoria e controle de origem. Quando configurado corretamente, a aplicação recebe informações confiáveis sobre o host e o endereço do cliente. Isso melhora diagnósticos e evita interpretações erradas de IP em ambientes com proxy.
O exemplo abaixo mostra uma configuração simples de servidor, encaminhando tráfego para a aplicação local na porta 8000.
server {
listen 80;
server_name seu-dominio.com;
location / {
proxy_pass http://127.0.0.1:8000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}
Cache com impacto real: aplicação, respostas, banco e microcache
Cache é o armazenamento temporário de dados para evitar recomputações e reduzir chamadas repetidas a recursos lentos. Em APIs, cache costuma trazer ganhos imediatos quando há respostas repetidas, validações frequentes ou consultas idênticas ao banco. O cache pode existir em várias camadas, desde a aplicação até o proxy. A escolha depende de quão frequentemente o dado muda e do risco de servir informação desatualizada.
No nível da aplicação, um padrão comum é usar Redis, um armazenamento em memória com alta velocidade. Ele é útil para guardar tokens, preferências de usuário, resultados de consultas frequentes e objetos calculados. Em cenários distribuídos, Redis também funciona como estado compartilhado, evitando dependência de memória local do processo. O cuidado principal é definir chaves e expirações coerentes, para evitar crescimento descontrolado e inconsistência.
O exemplo abaixo ilustra operações básicas de cache com Redis em modo assíncrono, armazenando e recuperando uma mensagem.
import redis.asyncio as redis
async def exemplo_cache():
cliente = redis.from_url("redis://localhost", decode_responses=True)
await cliente.set("mensagem_boas_vindas", "Olá!", ex=60) # expira em 60s
mensagem = await cliente.get("mensagem_boas_vindas")
await cliente.aclose()
return mensagem
No nível de resposta, o cache pode armazenar o resultado completo de um endpoint quando o conteúdo muda pouco. Essa técnica reduz latência e protege o banco em rotas muito acessadas, mas exige critério para invalidar quando houver atualização. No banco de dados, cache pode significar evitar consultas repetidas em tabelas estáticas ou pré-calcular agregações. No proxy, o microcaching do Nginx pode guardar respostas por frações de segundo, amortecendo rajadas de tráfego sem exigir mudanças grandes na aplicação.
Keep-alive e compressão: menos custo por requisição
Keep-alive mantém conexões TCP abertas por um tempo, evitando o custo de abrir e fechar conexão a cada chamada. Em APIs com muitas requisições curtas, isso reduz latência e uso de CPU na camada de rede. Outra otimização comum é a compressão gzip, que reduz o tamanho de respostas, especialmente JSON, economizando banda. O ganho é maior quando as respostas são médias ou grandes e o cliente suporta compressão.
No Nginx, essas configurações são diretas e costumam trazer melhoria perceptível em tráfego repetitivo. O keep-alive é ajustado por tempo de permanência da conexão ociosa. A compressão pode ser aplicada seletivamente por tipos MIME, como application/json. O equilíbrio envolve não comprimir conteúdos pequenos demais, onde o custo de compressão pode superar o benefício de banda.
gzip on;
gzip_types application/json;
keepalive_timeout 65;
Timeouts e limites: prevenção de travamentos e recursos presos
Timeout define quanto tempo o servidor espera por determinada etapa antes de desistir, evitando que recursos fiquem presos por clientes lentos ou conexões problemáticas. Sem timeouts, um pequeno número de requisições “penduradas” pode consumir workers e derrubar a capacidade de atendimento. Há timeouts no Gunicorn (tempo máximo de execução por worker) e no Nginx (tempo de conexão e leitura da resposta). Esses limites são uma forma de proteção operacional e melhoram previsibilidade sob falhas.
No Gunicorn, o timeout impede que um worker fique bloqueado indefinidamente, forçando reciclagem quando excede o limite. No Nginx, parâmetros como proxy_read_timeout e proxy_connect_timeout controlam o tempo máximo para conectar e ler respostas do upstream. Ajustes muito baixos podem interromper operações legítimas mais demoradas, enquanto valores altos demais podem favorecer acúmulo de conexões presas. O ponto prático é escolher limites alinhados ao SLA de latência e ao tempo esperado de operações comuns.
gunicorn main:app \
--workers 4 \
--worker-class uvicorn.workers.UvicornWorker \
--bind 0.0.0.0:8000 \
--timeout 30
proxy_read_timeout 30s;
proxy_connect_timeout 30s;
Escala horizontal: múltiplas instâncias e estado compartilhado
Quando a aplicação já está bem ajustada e ainda assim falta capacidade, entra em cena a escala horizontal, que consiste em rodar múltiplas instâncias do serviço. Em vez de aumentar apenas recursos de uma máquina, distribui-se a carga entre vários processos ou servidores. Isso melhora tolerância a falhas e permite crescer por adição incremental de capacidade. Um balanceador de carga, como o próprio Nginx, pode distribuir requisições entre instâncias.
Para funcionar bem, a aplicação precisa evitar estado local não compartilhado, porque cada instância é independente. Sessões, tokens revogáveis e filas devem usar componentes compartilhados como Redis, e dados persistentes devem ficar em um banco como PostgreSQL. Essa organização reduz inconsistência e impede que requisições dependam do “mesmo servidor” para funcionar. Em arquiteturas assim, a performance não depende apenas de rapidez por requisição, mas também de estabilidade e previsibilidade sob aumento de tráfego.
Encerramento: práticas que mais influenciam o desempenho em produção
O desempenho de uma API FastAPI em produção é resultado da soma entre concorrência correta, processos bem dimensionados, proxy reverso e uso inteligente de cache. Endpoints assíncronos entregam grande ganho quando lidam com I/O, desde que o event loop não seja bloqueado por tarefas de CPU. Gunicorn com workers Uvicorn melhora o aproveitamento de CPU e oferece supervisão, enquanto Nginx estabiliza o tráfego e protege a aplicação. Cache em Redis, cache de resposta e microcaching reduzem trabalho repetido e amortecem picos.
Keep-alive e gzip diminuem custos por requisição e melhoram eficiência de rede. Timeouts e limites evitam que conexões presas consumam capacidade, tornando a operação mais previsível. Quando a demanda excede uma única instância, a escala horizontal distribui carga e aumenta resiliência, desde que o estado seja compartilhado corretamente. Com essas práticas combinadas, a implantação tende a ficar rápida, estável e consistente, mesmo com altos níveis de concorrência.