404 Not Found

404 Not Found


nginx

Otimização de Performance — De 100 a 1 Milhão de QPS

A otimização de performance é como construir uma rodovia — você começa corrigindo as seções mais congestionadas (os gargalos), medindo os resultados após cada seção concluída, em vez de alargar toda a estrada indiscriminadamente. Otimização às cegas é desperdício; otimização orientada por medição é a abordagem mais eficiente.

1. O Que Você Vai Aprender


2. A História Real da Alice

(1) Problema: 500 QPS em servidor único não é suficiente

Após o lançamento do PriceTracker, um único servidor só conseguia lidar com 500 QPS, mas durante grandes promoções, o tráfego subia para 5.000 QPS, causando timeouts nas respostas da API. Charlie adicionou mais servidores, mas a melhoria foi insignificante — o pool de conexões do banco de dados estava esgotado, o código síncrono bloqueava o event loop, e a serialização JSON consumia 40% da CPU.

(2) Uma Abordagem Sistemática para Otimização de Performance

A otimização de performance não é adivinhação; é um ciclo de medição → identificação de gargalos → otimização → validação. Resolvemos problemas camada por camada, do nível de código (async/serialização) ao nível de banco de dados (índices/pools de conexão) até o nível arquitetural (Workers/balanceamento de carga).

(3) Resultado

O QPS do PriceTracker foi otimizado de 500 para 10.000 (em servidor único), e com um cluster de balanceamento de carga, pode atingir 1 milhão de QPS, enquanto a latência P99 foi reduzida de 500 ms para 30 ms.


3. Níveis de Otimização de Performance

(1) Modelo de Otimização em Três Camadas

100%
graph TD
    L3[Camada de Infraestrutura] --> L2[Camada de Banco de Dados]
    L2 --> L1[Camada de Aplicação]
    
    L1 --- A1[async/await - Detecção de bloqueio]
    L1 --- A2[Serialização - orjson]
    L1 --- A3[Compressão - GZip]
    
    L2 --- D1[Índices - Otimização de consultas]
    L2 --- D2[Pool de Conexões - Ajuste de tamanho]
    L2 --- D3[Padrões de Consulta - Prevenção N+1]
    
    L3 --- I1[Workers - Config Gunicorn]
    L3 --- I2[Load Balancer - Nginx]
    L3 --- I3[Auto-scaling - K8s HPA]

(2) Caminhos para Melhorar o QPS

100%
flowchart LR
    A[500 QPS\nBaseline] -->|async + orjson| B[2000 QPS]
    B -->|DB indexes + pool| C[10000 QPS]
    C -->|4 Workers| D[40000 QPS]
    D -->|Nginx LB x 5| E[200000 QPS]
    E -->|K8s x 5 pods| F[1000000 QPS]
Fase Método de Otimização QPS Fator de Melhoria
Baseline Configuração Padrão 500 1x
Camada de Aplicação async + orjson 2.000 4x
Camada de Banco de Dados Índices + Pool de Conexões 10.000 20x
Workers 4 Workers + Gunicorn 40.000 80x
Balanceamento de Carga 5 instâncias Nginx 200.000 400x
Elasticidade K8s 5 Pods Auto-scaling 1.000.000 2000x

4. Otimização da Camada de Aplicação

(1) Resolução de Bloqueio Assíncrono

(1) ▶Exemplo: Código Síncrono Que Bloqueia o Event Loop

PYTHON
import asyncio
import time

# RUIM: I/O síncrono bloqueia o event loop
async def get_price_bad(product_id: int):
    time.sleep(0.1)  # Bloqueia TODAS as outras requisições por 100ms!
    return {"product_id": product_id, "price": 9.99}

# BOM: usar asyncio para I/O
async def get_price_good(product_id: int):
    await asyncio.sleep(0.1)  # Não-bloqueante, outras requisições continuam
    return {"product_id": product_id, "price": 9.99}

Saída:

TEXT
# Função definida com sucesso

(2) ▶Exemplo: run_in_executor encapsula código síncrono

PYTHON
import asyncio
from functools import partial

# Função síncrona que não pode ser tornada async
def sync_scrape_price(url: str) -> float:
    # Usa a biblioteca requests (apenas síncrona)
    import requests
    response = requests.get(url, timeout=10)
    return parse_price(response.text)

async def get_price_async(product_id: int):
    loop = asyncio.get_event_loop()
    # Executar função síncrona no pool de threads - não bloqueia o event loop
    price = await loop.run_in_executor(
        None,  # Pool de threads padrão
        partial(sync_scrape_price, f"https://api.example.com/prices/{product_id}"),
    )
    return {"product_id": product_id, "price": price}

Saída:

TEXT
# Função definida com sucesso

(2) Otimização de Serialização JSON

(3) ▶Exemplo: Usando orjson em vez de json

PYTHON
# Instalar: uv add orjson
from fastapi import FastAPI
from fastapi.responses import ORJSONResponse

app = FastAPI(default_response_class=ORJSONResponse)

@app.get("/api/v1/products")
async def list_products():
    # orjson é 3-10x mais rápido que json da stdlib
    return {"products": [{"id": i, "name": f"Product {i}"} for i in range(100)]}

Saída:

TEXT
# Função definida com sucesso
Biblioteca de Serialização Velocidade Descrição
json (biblioteca padrão) Referência Implementação Python pura
orjson 3-10x Implementado em Rust, lida automaticamente com datetime
ujson 2-3x Implementação C, alguns problemas de compatibilidade

(4) ▶Exemplo: Compressão com GZipMiddleware

PYTHON
from fastapi import FastAPI
from starlette.middleware.gzip import GZipMiddleware

app = FastAPI()
app.add_middleware(GZipMiddleware, minimum_size=1000)  # Comprimir respostas > 1KB

@app.get("/api/v1/products")
async def list_products():
    # Respostas JSON grandes são automaticamente comprimidas
    return {"products": [...]}  # 50KB → ~5KB com gzip

Saída:

TEXT
# Função definida com sucesso

5. Otimização da Camada de Banco de Dados

(1) Estratégia de Indexação

(1) ▶Exemplo: Design de Índices do PriceTracker

PYTHON
from sqlalchemy import Index

class Product(Base):
    __tablename__ = "products"
    __table_args__ = (
        # Índices de coluna única para filtros comuns
        Index("ix_products_category", "category"),
        Index("ix_products_created_at", "created_at"),
        # Índice composto para consultas de categoria + faixa de preço
        Index("ix_products_category_base_price", "category", "base_price"),
        # Índice parcial apenas para produtos ativos (específico do PostgreSQL)
        Index("ix_products_active", "created_at", postgresql_where=("deleted_at IS NULL")),
    )
    ...

Saída:

TEXT
# Execução Bem-sucedida
Tipo de Índice Consultas Adequadas Exemplo
Índice de coluna única Filtro de igualdade/faixa WHERE category = ?
Índice Composto Combinações de Múltiplas Condições WHERE category = ? AND price > ?
Índice Parcial Subconjunto Condicional WHERE deleted_at IS NULL
Índice de Cobertura Evitar Lookup na Tabela INCLUDE (name, price)

(2) Ajuste do Pool de Conexões

(2) ▶Exemplo: Configuração de Pool de Conexões em Nível de Produção

PYTHON
engine = create_async_engine(
    DATABASE_URL,
    pool_size=25,           # Conexões persistentes por worker
    max_overflow=10,        # Conexões extras quando pool esgotado
    pool_timeout=30,        # Tempo de espera por conexão disponível
    pool_recycle=1800,      # Reciclar conexões após 30 min
    pool_pre_ping=True,     # Testar conexão antes de usar
    echo=False,             # Desativar log SQL em produção
)

Saída:

TEXT
# Execução Bem-sucedida
Parâmetro Valor Recomendado Descrição
pool_size núcleos CPU x 2 + 1 Contagem básica de conexões
max_overflow pool_size x 0.5 Número máximo de conexões de burst
pool_timeout 30s Timeout
pool_recycle 1800s Prevenir MySQL/PG de fechar conexões ociosas
pool_pre_ping True Prevenir uso de conexões desconectadas

6. Otimização do Modelo de Concorrência

(1) Configuração de Workers

(1) ▶Exemplo: Gunicorn + Uvicorn Workers

BASH
# Produção: Gunicorn gerencia Uvicorn workers
gunicorn app.main:app \
    --workers 9 \
    --worker-class uvicorn.workers.UvicornWorker \
    --bind 0.0.0.0:8000 \
    --timeout 120 \
    --graceful-timeout 30 \
    --access-logfile - \
    --error-logfile -

Saída:

TEXT
# Comando executado com sucesso
DOCKERFILE
# No Dockerfile
CMD ["gunicorn", "app.main:app", \
     "--workers", "9", \
     "--worker-class", "uvicorn.workers.UvicornWorker", \
     "--bind", "0.0.0.0:8000"]
Configuração Fórmula/Valor Descrição
Número de Workers (2 x CPU) + 1 4 núcleos = 9 workers
worker-class UvicornWorker Worker ASGI
timeout 120s Worker morto por timeout
graceful-timeout 30s Timeout de desligamento gracioso
max-requests 10.000 Reinício automático para prevenir vazamentos de memória

❓Perguntas Frequentes

P Como identificar gargalos de performance?
R Use uma cadeia de ferramentas — cProfile para analisar hotspots de CPU, py-spy para amostragem em tempo real, Prometheus para monitorar distribuições de latência, e logs de consultas lentas para analisar o banco de dados. Meça primeiro, depois otimize.
P Qual é o tamanho do pool de threads para run_in_executor?
R O padrão é min(32, os.cpu_count() + 4). Para tarefas CPU-intensivas, use ProcessPoolExecutor para evitar limitações do GIL.
P orjson é compatível com Pydantic?
R Sim, é. O default_response_class=ORJSONResponse do FastAPI serializa automaticamente modelos Pydantic usando orjson para gerar a saída model_dump().
P Como equilibrar o pool de conexões e o número de workers?
R Cada worker tem seu próprio pool de conexões. 9 workers x 25 conexões = 225 conexões de banco de dados. A configuração padrão de max_connections do PostgreSQL é 100; você precisará aumentar ou diminuir o pool_size conforme necessário.
P A compressão GZip tem overhead de performance?
R A compressão consome recursos de CPU, mas reduz o tempo de transmissão na rede. Para respostas JSON maiores que 1 KB, os benefícios da compressão superam em muito o overhead de CPU (a taxa de compressão é tipicamente de 5 a 10 vezes).
P Como verificar a eficácia da otimização?
R Use Locust ou k6 para realizar testes de carga, e compare o QPS, latência P50/P95/P99 e uso de CPU/memória antes e depois da otimização. Altere apenas uma variável por vez.

📖Resumo


📝Exercícios

  1. Exercício Básico (Dificuldade ⭐): Instale orjson, altere a classe de resposta padrão do FastAPI para ORJSONResponse, e use curl para comparar os tempos de resposta para JSON e orjson. Dica: default_response_class=ORJSONResponse
  2. Exercício Avançado (Dificuldade ⭐⭐): Adicione índices à tabela products do PriceTracker (category, created_at, e um índice composto de category e base_price), e compare os tempos de execução da mesma consulta antes e depois de adicionar os índices. Dica: Index("ix_name", "col1", "col2") + EXPLAIN ANALYZE
  3. Desafio (Dificuldade ⭐⭐⭐): Otimização abrangente de performance — serialização orjson + GZipMiddleware + ajuste de pool de conexões + Gunicorn com 4 workers. Use Locust para executar testes de carga e compare QPS e latência P99 antes e depois da otimização. Dica: locust -f locustfile.py --host=http://localhost:8000

---|

Web-Tutorial.com

Equipe Técnica Web-Tutorial

Uma plataforma de tutoriais mantida por diversos desenvolvedores. Cada tutorial é escrito e revisado por profissionais da área correspondente. Trabalhamos para manter nosso conteúdo preciso e confiável — se encontrar algum problema, avise-nos.

100%