Uma regressão de performance é quando uma mudança de código piora métricas que antes eram boas. O perigo é que muitas regressões são pequenas — 5% aqui, 10% ali — e passam despercebidas até acumularem em problemas sérios.
Regressão de performance é dívida técnica paga em milissegundos.
Por Que Regressões Acontecem
Causas comuns
1. Código não otimizado
- Query N+1 introduzida
- Loop desnecessário
- Serialização ineficiente
2. Mudança de dependências
- Upgrade de biblioteca
- Nova versão de framework
- Mudança de runtime
3. Mudança de dados
- Volume cresceu
- Distribuição mudou
- Novos edge cases
4. Mudança de configuração
- Pool size alterado
- Timeout mudado
- Cache desabilitado
A natureza insidiosa
Semana 1: latência = 100ms
Semana 2: +5% → 105ms (passa despercebido)
Semana 3: +3% → 108ms
Semana 4: +7% → 116ms
...
Mês 3: latência = 200ms (dobrou!)
Nenhuma mudança individual foi "ruim", mas o acumulado é crítico
Detectando Regressões
1. Baseline de Performance
# Estabelecer baseline
baseline = {
'api_latency_p50': 45,
'api_latency_p95': 120,
'api_latency_p99': 250,
'throughput': 5000,
'error_rate': 0.001
}
# Detectar desvio
def check_regression(current_metrics, baseline, threshold=0.1):
regressions = []
for metric, baseline_value in baseline.items():
current = current_metrics[metric]
change = (current - baseline_value) / baseline_value
if change > threshold:
regressions.append({
'metric': metric,
'baseline': baseline_value,
'current': current,
'change': f'+{change*100:.1f}%'
})
return regressions
2. Comparação A/B
Canary (nova versão):
latency_p99: 135ms
error_rate: 0.12%
Production (versão atual):
latency_p99: 120ms
error_rate: 0.10%
Comparação:
latency_p99: +12.5% ⚠️
error_rate: +20% ⚠️
Resultado: Regressão detectada, rollback canary
3. Testes de Performance no CI
# GitHub Actions
name: Performance Tests
on: [pull_request]
jobs:
perf-test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Run Load Test
run: k6 run tests/load.js --out json=results.json
- name: Compare with Baseline
run: |
python scripts/compare_perf.py \
--baseline baseline.json \
--current results.json \
--threshold 10
- name: Fail if Regression
if: ${{ steps.compare.outputs.regression == 'true' }}
run: |
echo "Performance regression detected!"
exit 1
Prevenindo Regressões
1. Performance Budget
# Definir orçamento
performance_budget:
api:
latency_p95: 100ms
latency_p99: 200ms
frontend:
lcp: 2500ms
fid: 100ms
cls: 0.1
database:
query_time_p95: 50ms
def enforce_budget(metrics, budget):
violations = []
for endpoint, limits in budget.items():
for metric, limit in limits.items():
if metrics[endpoint][metric] > limit:
violations.append(f"{endpoint}.{metric}: {metrics[endpoint][metric]} > {limit}")
if violations:
raise PerformanceBudgetExceeded(violations)
2. Testes Automatizados
# pytest-benchmark
import pytest
def test_api_latency(benchmark):
result = benchmark(lambda: client.get('/api/users'))
# Assertions de performance
assert benchmark.stats['mean'] < 0.1 # 100ms
assert benchmark.stats['max'] < 0.5 # 500ms
def test_no_n_plus_one(django_assert_num_queries):
with django_assert_num_queries(2):
list(Order.objects.prefetch_related('items').all())
3. Profiling Contínuo
# Em produção com amostragem
from pyinstrument import Profiler
@app.middleware("http")
async def profile_requests(request, call_next):
if random.random() < 0.01: # 1% dos requests
profiler = Profiler()
profiler.start()
response = await call_next(request)
profiler.stop()
save_profile(profiler.output_text())
return response
return await call_next(request)
4. Code Review Focado
## Performance Review Checklist
### Queries
- [ ] Sem N+1 queries
- [ ] Índices adequados para novas queries
- [ ] EXPLAIN analisado para queries complexas
### Loops
- [ ] Sem I/O dentro de loops
- [ ] Batching onde possível
- [ ] Complexidade O() aceitável
### Memória
- [ ] Sem vazamentos óbvios
- [ ] Streams para dados grandes
- [ ] Cache com limite
### Dependências
- [ ] Nova dependência justificada
- [ ] Benchmark de alternativas
- [ ] Tamanho de bundle aceitável
Workflow de Detecção
Pipeline Completo
PR criado
↓
Unit Tests
↓
Build
↓
Performance Tests (comparação com baseline)
↓
├─ Sem regressão → Merge permitido
│
└─ Regressão detectada
↓
Investigação obrigatória
↓
├─ Justificado → Atualiza baseline + Merge
│
└─ Não justificado → Correção necessária
Alertas em Produção
# Detectar regressão comparando com baseline histórico
- alert: PerformanceRegression
expr: |
(
avg_over_time(http_request_duration_seconds[1h])
/ avg_over_time(http_request_duration_seconds[24h] offset 7d)
) > 1.2
for: 30m
annotations:
summary: "Latência 20% maior que semana passada"
Corrigindo Regressões
1. Identificar a Causa
# Git bisect para encontrar commit culpado
git bisect start
git bisect bad HEAD
git bisect good v1.2.0
# Para cada commit, rodar teste de performance
git bisect run ./scripts/perf_test.sh
2. Análise de Profiling
# Comparar profiles antes/depois
from pyinstrument import Profiler
# Profile versão anterior
git checkout HEAD~1
profile_before = run_profiled(workload)
# Profile versão atual
git checkout HEAD
profile_after = run_profiled(workload)
# Comparar
diff = compare_profiles(profile_before, profile_after)
print(diff.get_hotspots())
3. Corrigir ou Reverter
# Se correção rápida possível
if can_fix_quickly():
fix_regression()
add_performance_test() # Prevenir recorrência
deploy()
# Se correção complexa
else:
revert_to_previous_version()
create_ticket_for_fix()
# Correção com mais tempo e testes
Ferramentas
Para CI/CD
k6: Load testing
Lighthouse: Frontend
pytest-benchmark: Python
JMH: Java
BenchmarkDotNet: .NET
Para Produção
Continuous Profiling:
- Pyroscope
- Datadog Continuous Profiler
- Google Cloud Profiler
APM:
- Datadog
- New Relic
- Dynatrace
Para Análise
Flame Graphs: Visualização de CPU
Allocation Profilers: Memória
Query Analyzers: Banco de dados
Métricas de Sucesso
# Indicadores de programa saudável
Detecção:
- 100% das PRs passam por teste de perf
- Regressões detectadas antes de produção: > 90%
Prevenção:
- PRs bloqueadas por regressão: < 10%
- Tempo médio para corrigir: < 2 dias
Produção:
- Regressões que chegam em prod: < 1/mês
- Tempo para detectar em prod: < 1 hora
- Tempo para resolver: < 4 horas
Conclusão
Prevenir regressões de performance requer:
- Baseline claro: saiba o que é "normal"
- Testes automatizados: no CI, toda PR
- Comparação contínua: canary vs production
- Alertas rápidos: detectar em minutos, não dias
- Cultura de performance: todo dev é responsável
O custo de prevenir é muito menor que o custo de corrigir:
Prevenir (teste no CI): ~5 min por PR
Detectar em staging: ~1 hora de investigação
Detectar em produção: ~4 horas + impacto em usuários
Detectar após acumular: dias de refatoração
A melhor regressão é aquela que nunca chega em produção.