Metodologia8 min

Performance Regressions: detectando e prevenindo degradação

Regressões de performance são mudanças que pioram a performance do sistema. Aprenda a detectar, prevenir e corrigir antes que afetem usuários.

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:

  1. Baseline claro: saiba o que é "normal"
  2. Testes automatizados: no CI, toda PR
  3. Comparação contínua: canary vs production
  4. Alertas rápidos: detectar em minutos, não dias
  5. 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.

regressõesCI/CDtestesmonitoramento
Compartilhar:
Read in English

Quer entender os limites da sua plataforma?

Entre em contato para uma avaliação de performance.

Fale Conosco