"O sistema está lento, deve ser o banco de dados." Quantas vezes você ouviu isso? E quantas vezes estava errado? Encontrar o verdadeiro gargalo é uma das habilidades mais valiosas em performance engineering. Este artigo ensina como fazer isso de forma sistemática.
Um gargalo real é o ponto que limita o throughput de todo o sistema. O resto é ruído.
O Que É um Gargalo
Definição
Gargalo = O componente que, se melhorado,
aumentaria o throughput total do sistema
O que NÃO é gargalo
❌ Código que parece lento (mas não está no caminho crítico)
❌ Função que consome CPU (mas executa raramente)
❌ Query que demora 500ms (mas roda uma vez por dia)
❌ Serviço com alta latência (mas não é chamado pelos usuários)
Características de um gargalo real
✅ Está no caminho crítico do request
✅ É acessado com frequência significativa
✅ Limita o throughput máximo do sistema
✅ Melhorá-lo melhora a experiência do usuário
A Lei de Amdahl
O conceito
Speedup máximo = 1 / ((1 - P) + P/S)
Onde:
P = Fração do tempo gasto no componente
S = Fator de melhoria aplicado
Exemplo:
Se componente A representa 10% do tempo total,
mesmo melhorando A em 10x, o ganho máximo é ~11%
Implicação prática
Cenário: Request de 1000ms
Componentes:
- API Gateway: 50ms (5%)
- Auth: 100ms (10%)
- Business Logic: 150ms (15%)
- Database: 600ms (60%)
- Response: 100ms (10%)
Se otimizar Auth em 50%:
Economia: 50ms
Novo total: 950ms
Melhoria: 5%
Se otimizar Database em 50%:
Economia: 300ms
Novo total: 700ms
Melhoria: 30%
→ Sempre ataque o maior contribuidor primeiro
Metodologia para Identificar Gargalos
Passo 1: Mapear o fluxo completo
Request do usuário
↓
┌─────────────────┐
│ Load Balancer │ → Métricas: latência, conexões
└────────┬────────┘
↓
┌─────────────────┐
│ API Gateway │ → Métricas: rate, errors, duration
└────────┬────────┘
↓
┌─────────────────┐
│ Auth Service │ → Métricas: cache hit, token time
└────────┬────────┘
↓
┌─────────────────┐
│ Order Service │ → Métricas: processing time
├────────┬────────┤
│ ↓ │ ↓ │
│ DB │ Cache │ → Métricas: query time, hit rate
└────────┴────────┘
↓
Response
Passo 2: Medir cada componente
# Latência por componente
histogram_quantile(0.95,
sum by(component) (rate(component_duration_seconds_bucket[5m]))
)
# Tempo relativo por componente
sum by(component) (rate(component_duration_seconds_sum[5m]))
/ sum(rate(request_duration_seconds_sum[5m]))
# Chamadas por componente
sum by(component) (rate(component_calls_total[5m]))
Passo 3: Identificar o maior contribuidor
Análise de traces (exemplo):
Request total: 450ms
Breakdown:
├─ Gateway: 15ms (3%)
├─ Auth: 25ms (6%)
├─ Validation: 10ms (2%)
├─ DB Query 1: 180ms (40%) ← GARGALO
├─ DB Query 2: 120ms (27%) ← SEGUNDO MAIOR
├─ External API: 80ms (18%)
└─ Serialization: 20ms (4%)
Foco: DB Query 1 (40% do tempo)
Passo 4: Validar hipótese
Antes de otimizar, confirme:
1. É consistente?
→ Gargalo aparece em múltiplos traces?
2. É frequente?
→ Quantos requests passam por esse caminho?
3. Impacta o usuário?
→ Está no caminho crítico da jornada?
4. É otimizável?
→ Existe potencial de melhoria realista?
Tipos de Gargalos
1. Gargalo de CPU
Sintomas:
- CPU em 100%
- Latência aumenta com carga
- Throughput estagna em ponto fixo
Causas comuns:
- Algoritmos ineficientes (O(n²))
- Serialização/deserialização excessiva
- Criptografia em CPU
- Regex complexos
Diagnóstico:
- CPU profiler (async-profiler, py-spy)
- top/htop para identificar processo
- perf para flame graphs
2. Gargalo de I/O
Sintomas:
- CPU baixa, latência alta
- Muitas conexões em WAIT
- iowait alto
Causas comuns:
- Queries sem índice
- Disco lento
- Network latency
- Connection starvation
Diagnóstico:
- iostat para disco
- netstat para conexões
- Database slow query log
3. Gargalo de memória
Sintomas:
- OOM events
- GC frequente
- Swap usage alto
Causas comuns:
- Memory leaks
- Caches unbounded
- Objetos grandes em memória
Diagnóstico:
- Heap dumps
- GC logs
- Memory profilers
4. Gargalo de concorrência
Sintomas:
- Baixa utilização de CPU
- Latência alta sob carga
- Threads em WAIT
Causas comuns:
- Locks contentious
- Connection pool pequeno
- Thread pool exausto
Diagnóstico:
- Thread dumps
- Lock profiler
- Pool metrics
Ferramentas de Diagnóstico
Para código
Java:
- async-profiler (CPU, allocation)
- JFR (Java Flight Recorder)
- VisualVM
Python:
- py-spy (CPU profiler)
- memory_profiler
- cProfile
Node.js:
- clinic.js
- 0x (flame graphs)
- v8-profiler
Go:
- pprof
- trace
- bench
Para sistema
Linux:
- perf (CPU profiling)
- strace (system calls)
- eBPF/bcc tools
- sar (histórico)
Containers:
- cAdvisor
- Prometheus node_exporter
- kubectl top
Para database
PostgreSQL:
- pg_stat_statements
- EXPLAIN ANALYZE
- pg_stat_user_tables
MySQL:
- slow query log
- EXPLAIN
- performance_schema
Redis:
- SLOWLOG
- INFO stats
- MEMORY DOCTOR
Exemplo Prático: Investigação Completa
Cenário
Problema: Checkout lento (p95 = 3s, SLO = 1s)
Investigação
## Passo 1: Métricas gerais
- p95 checkout: 3.2s
- Throughput: 50 req/s
- Error rate: 0.5%
- CPU: 35%, Memory: 60%
→ Não é problema de recursos de infra
## Passo 2: Breakdown por serviço
- API Gateway: 100ms (3%)
- Cart Service: 200ms (6%)
- Inventory: 300ms (10%)
- Payment: 2400ms (75%) ← SUSPEITO
- Notification: 200ms (6%)
→ Payment service domina o tempo
## Passo 3: Drill-down no Payment
- Auth: 50ms
- Validation: 100ms
- Stripe API: 2200ms ← GARGALO
- Logging: 50ms
→ Chamada externa para Stripe é o gargalo
## Passo 4: Análise da chamada Stripe
- Timeout configurado: 30s (muito alto)
- Retries: 3 (total wait pode ser 90s)
- p50: 400ms, p99: 8s
- Variância altíssima
→ Stripe tem latência variável, sem circuit breaker
## Passo 5: Validação
- 95% dos checkouts passam por Stripe
- É o caminho crítico (sem pagamento, sem venda)
- Impacta diretamente conversão
## Root cause:
Latência variável da API Stripe + configuração
de timeout/retry inadequada
## Solução:
1. Timeout: 30s → 5s
2. Retry: exponential backoff
3. Circuit breaker: fail fast se Stripe instável
4. Cache: validações de cartão quando possível
Armadilhas Comuns
1. Otimizar o que é fácil, não o que importa
❌ "Vou cachear esse endpoint porque sei fazer"
→ Endpoint representa 0.1% do tráfego
✅ "Vou investigar o que realmente importa"
→ Identificar os 20% que causam 80% do problema
2. Confundir latência com gargalo
❌ "Essa função demora 500ms, é o gargalo"
→ Mas é chamada 1x por hora
✅ "Essa função demora 5ms mas é chamada 10K/s"
→ Representa 50s de CPU por segundo
3. Ignorar efeitos cascata
❌ "O serviço A está lento"
→ Mas A depende de B que depende de C
✅ "A está lento porque C está saturado"
→ Resolver C resolve A
Conclusão
Identificar gargalos reais requer método:
- Mapeie o fluxo completo do request
- Meça cada componente no caminho crítico
- Identifique o maior contribuidor (Lei de Amdahl)
- Valide a hipótese antes de otimizar
- Ataque o gargalo real, não o percebido
O pior desperdício em performance é otimizar algo que não é gargalo.
Este artigo faz parte da série sobre a metodologia OCTOPUS de Performance Engineering.