MÓDULO 06 · CONCEITO 12 DE 12

Performance testing

k6, Locust, Gatling, Vegeta. Load, stress, soak, spike. SLO-driven testing, capacity planning, soak test que desnuda vazamento. Por que "rodei ab e tava ok" não conta como teste de performance.

Tempo de leitura ~22 min Pré-requisito Conceitos 03 (latência vs throughput) e 04 (tail latency) Próximo Módulo 07 — Escalabilidade

Em qualquer sistema que pretende sustentar carga, vai chegar um momento em que alguém pergunta: "isso aguenta o tráfego que esperamos?". A pergunta parece simples. As ferramentas para responder existem há décadas — JMeter (Apache, 1998), Apache Bench (ab, anos 90), wrk (2012), Tsung, Gatling. Mais recentes: k6 (2017, depois Grafana), Locust (2011, Python), Vegeta (Go). Mas a maioria dos times trata performance testing como atividade ad-hoc. Roda alguma ferramenta uma vez antes de grande release, faz prints, decide com base em número de média, e seguem em frente.

Performance testing maduro é diferente. Tem disciplina própria, vocabulário próprio, e integração com SLO de produção. A diferença operacional é qualitativa: times que fazem teste sério descobrem gargalos antes do incidente; times que fazem ad-hoc descobrem durante. Este conceito articula essa disciplina.

O foco está em quatro tipos de teste com nome reconhecível (load, stress, soak, spike), nas ferramentas modernas (k6 como exemplo principal), em capacity planning baseado em SLO, e nos antipadrões mais frequentes (coordinated omission, ambiente sintético, baseline ausente). O conceito fecha o módulo conectando volta com tudo que veio antes — latência percebida pelo usuário (conceitos 03–04), CDN e cache (09–10), banco (11). Performance testing é onde se valida que toda a stack funciona junta sob a carga esperada.

O escopo é deliberado. Não cobrimos chaos engineering (Netflix Chaos Monkey, Litmus) — tema do módulo 08 (Disponibilidade & Resiliência). Não cobrimos teste de carga em sistemas distribuídos com fan-out complexo — tema do módulo 07 (Escalabilidade). Aqui o foco é validar que esta aplicação atende ao SLO sob a carga prevista.

Os quatro tipos canônicos de teste

Performance testing tem taxonomia clássica. Cada tipo responde pergunta diferente, e times maduros executam todos os quatro em sequência antes de release significativo.

Load test — performance sob carga esperada

Pergunta: "como o sistema se comporta sob a carga que esperamos em produção?". Você define um perfil de carga (X req/s, distribuição realista de endpoints, pico esperado), executa por tempo significativo (15 min a 1h), e mensura. SLO definido antes: P99 < Y ms, error rate < Z%. Aprovação: SLO atendido sustentadamente.

Load test é o teste base. Sem ele, todos os outros são especulativos. A carga deve ser realística — idealmente baseada em logs de produção (replay), ou em modelo razoável construído com produto.

Stress test — onde o sistema quebra

Pergunta: "qual é o limite, e o que falha primeiro?". Carga cresce gradualmente até o sistema falhar (latência além do SLO, erros aparecendo, ou crash). Você documenta: em qual nível de carga o knee apareceu? O que saturou primeiro — CPU, memória, banco, rede? Como o sistema falhou — degradação graceful ou cascading failure?

Stress test é onde se descobre o teto real e a ordem dos gargalos. É também onde se valida (ou derruba) os timeouts e circuit breakers — o sistema sob estresse tem que falhar de forma diagnóstica, não simplesmente queimar.

Soak test — desgaste ao longo do tempo

Pergunta: "o sistema se mantém estável sob carga sustentada por horas/dias?". Carga moderada (~70% da capacidade de pico) por 4–24 horas. O que se observa: crescimento de memória (vazamento), degradação de cache, esgotamento de connection pool, fragmentação de heap, log file rotation problemas, certificado expirando.

Soak test é onde se descobre os bugs que load test não pega — nada que dura 30 minutos revela vazamento de memória que cresce 10 MB/hora. Em sistemas críticos (financeiro, gaming, ad tech), soak test de 72 horas antes de cada release maior é prática de higiene.

Spike test — reação a aumento súbito

Pergunta: "como o sistema reage a aumento súbito de tráfego (anúncio, evento, viral)?". Carga normal por tempo, depois pulo abrupto (10× ou mais), depois volta. O que se observa: cold cache (CDN, application, query plan), connection pool ramping, autoscaling (se houver), graceful degradation.

Spike test é especialmente crítico em sistemas com eventos previsíveis: lançamento de produto, Black Friday, pico de horário, campanha de marketing. Sem spike test, o sistema pode aguentar carga steady mas cair ao receber a primeira onda.

SLO-driven — definir antes de testar

Performance test sem SLO é exercício acadêmico. Você precisa definir antes do teste: qual é o critério de aprovação?. SLO articulado, mensurável, comparável.

A formulação canônica conecta com o conceito 03 (percentis, throughput qualificado):

SLO de produção do endpoint /api/produtos:
  - P50 < 50 ms a 1000 RPS sustentado
  - P99 < 200 ms a 1000 RPS sustentado
  - P99.9 < 1 s a 1000 RPS sustentado
  - error rate < 0.1% a 1000 RPS sustentado
  - throughput sustentável de pelo menos 5000 RPS antes de SLO degradar

Aprovação do load test:
  Sistema atende todos os critérios de SLO em janela de 15 minutos
  contínuos sob carga representativa.

Aprovação do stress test:
  Sistema atinge pelo menos 5000 RPS antes do P99 estourar 200 ms.
  Quando estourar, a falha é diagnosticada (logs claros, métricas
  apontando saturação) e o sistema retorna a SLO em < 30 segundos
  após a carga retornar a 1000 RPS.

Aprovação do soak test:
  Sistema sustenta SLO por 12 horas contínuas a 700 RPS.
  RSS de memória cresce no máximo 10% no período.
  Connection pool não esgota.
  Cache hit rate estabiliza em > 90%.

Sem articular esses critérios, "passou no teste" é conversa subjetiva. Com eles, é binário e auditável.

k6 — a ferramenta de referência em 2026

k6 foi criada pela Load Impact em 2017 e adquirida pela Grafana Labs em 2021. Em 2026, é a ferramenta mainstream em times modernos, tendo substituído largamente JMeter em adoção nova. As razões: scripts em JavaScript ES6+ (familiar), execução em runtime Go (alto throughput, pouca CPU), relatórios via Grafana Cloud ou self-hosted, e integração com OTel.

// teste-load.js — k6 script com SLO articulado
import http from 'k6/http';
import { check, sleep } from 'k6';
import { Trend, Counter } from 'k6/metrics';

const obterProdutoTrend = new Trend('obter_produto_duration', true);
const errorCount = new Counter('errors');

export const options = {
    // SLO thresholds — k6 falha o teste se violado
    thresholds: {
        'http_req_duration{name:obter_produto}': [
            'p(50)<50',     // P50 abaixo de 50ms
            'p(99)<200',    // P99 abaixo de 200ms
            'p(99.9)<1000', // P99.9 abaixo de 1s
        ],
        'errors': ['count<10'],     // máximo 10 erros no teste todo
    },

    // perfil de carga
    scenarios: {
        load_steady: {
            executor: 'constant-arrival-rate',
            rate: 1000,            // 1000 requests/segundo
            timeUnit: '1s',
            duration: '15m',
            preAllocatedVUs: 100,
            maxVUs: 500,
        },
    },
};

export default function () {
    const produtoId = '0123456789'.split('')[Math.floor(Math.random() * 10)];
    const res = http.get(`https://api.example.com/produtos/${produtoId}`,
        { tags: { name: 'obter_produto' } });

    const ok = check(res, {
        'status is 200': (r) => r.status === 200,
        'has body': (r) => r.body.length > 0,
    });

    if (!ok) errorCount.add(1);
    obterProdutoTrend.add(res.timings.duration);
}

// rodar:
// k6 run teste-load.js
// k6 falha (exit code != 0) se thresholds violados

A configuração mostra três disciplinas. Primeiro, thresholds articula SLO no próprio script — passa ou falha automaticamente. Segundo, scenarios com constant-arrival-rate garante que k6 envia X RPS independente da resposta do sistema (evita coordinated omission, ver mais abaixo). Terceiro, tags em métricas (name: 'obter_produto') permitem segmentar análise por tipo de operação.

Coordinated omission — o erro estatístico que arruina testes

Já mencionado no conceito 03: coordinated omission, formalizado por Gil Tene em 2013, é o viés que afeta quase todas as ferramentas de load test tradicionais. O cenário: a ferramenta envia uma request, espera resposta, registra latência, envia próxima. Quando o sistema fica lento, a ferramenta também espera mais — e portanto envia menos requests durante o período lento. As requests "perdidas" não entram no histograma.

O efeito é que P99 reportado pela ferramenta pode ser ordens de magnitude melhor que o P99 real que usuários experienciam. Sistema que tem stall de 2 segundos vira "1 request de 2s no relatório" quando, em produção, dezenas de requests estariam afetadas.

Ferramentas modernas resolvem isso de duas formas. Open-loop testing: ferramenta envia requests em rate fixo (X RPS) independente da resposta. Se o sistema fica lento, requests novas empilham — e cada uma registra a latência completa desde quando deveria ter sido processada (não desde quando foi enviada). k6 com constant-arrival-rate faz isso por design. wrk2 (criado por Tene como sucessor do wrk) também.

Closed-loop testing é o oposto — VUs (virtual users) enviam um request, esperam, enviam próximo. É mais realístico para simular usuários, mas suscetível a coordinated omission. JMeter tradicional, Locust, ferramentas mais antigas tipicamente usam closed-loop.

Para testes que validam SLO, prefira open-loop. Para testes que simulam comportamento de usuário com pensar (think time entre requests), closed-loop com VUs apropriados.

Capacity planning — calculando recursos

Performance testing alimenta capacity planning: dado o tráfego esperado, quantos servidores precisamos? Com qual config de banco? Quanto custa? A análise tipicamente segue:

1. Determinar tráfego esperado. Em pico (não médio). Inclua margem para crescimento (3-6 meses) e para spikes (campanhas, eventos).

2. Determinar throughput por instância. Via stress test em ambiente staging: quanto cada instância sustenta antes de SLO degradar? Tipicamente operar a 50–70% da capacidade máxima como margem de segurança (volta ao conceito 03 — antes do knee).

3. Calcular número de instâncias. tráfego_esperado / throughput_por_instância / 0.7. O 0.7 é a margem.

4. Validar com banco e cache. Instâncias podem escalar; banco e cache têm limites. Replicar via stress test que o banco aguenta o throughput agregado.

5. Validar custos. Multiplicar pelo custo cloud, somar infrastructure (cache, balanceador, CDN). Comparar com orçamento. Se não cabe, otimizar (cache mais agressivo, queries mais eficientes, codepath mais leve) ou ajustar arquitetura.

Capacity planning bem feito é trabalho contínuo, não pontual. Tráfego cresce, código muda, infra muda — o número precisa ser revisitado a cada release/quartil.

Geographic load testing

Sistemas com usuários globais precisam de teste distribuído geograficamente. Ferramenta rodando local a partir de São Paulo testa só o caminho local. Para validar latência geográfica (CDN funcionando, edge compute respondendo de cada PoP), você precisa de load generators distribuídos.

k6 Cloud (Grafana) permite executar teste a partir de múltiplas regiões cloud simultaneamente. BlazeMeter, LoadView oferecem alternativas comerciais. Em times com infraestrutura própria, configurar runners em múltiplas regiões da cloud (AWS, GCP) é alternativa.

Em sistemas com latência crítica para usuários diversos, esse é o único teste válido — testar só em uma região é otimismo.

Continuous performance testing em CI

A prática emergente em times maduros é integrar performance testing com CI: cada release significativo passa por load test automatizado, e regressões fazem build falhar antes de produção.

O setup mínimo: ambiente staging que aproxima produção (mesma infra, mesmos dados de tamanho representativo); script k6 que reflete carga realista; thresholds articulados em SLO; CI que roda o script e bloqueia merge se thresholds falham.

Em times com investimento, Grafana k6 Cloud ou JMeter Distribuido + BlazeMeter podem ser parte do pipeline. Para projetos open-source, k6 self-hosted é gratuito. Bencher e Codspeed oferecem dashboards de regressão de microbenchmarks com integração a Git.

A prática captura regressões silenciosas — cada PR que adiciona 5% de latência sem ninguém perceber, em 30 PRs, dobrou a latência. Sem teste contínuo, a degradação acumula.

Os padrões em três stacks

C# — k6 + dotnet-counters durante teste
// k6 script roda contra a aplicação em staging
// em paralelo, captura métricas de runtime via dotnet-counters

# terminal 1 — captura runtime metrics
dotnet-counters monitor --process-id $APP_PID \
  --counters System.Runtime,Microsoft.AspNetCore.Hosting \
  --output csv -o counters.csv

# terminal 2 — k6 com SLO
k6 run --out csv=results.csv teste-load.js

# análise pós-teste
# correlacionar latência reportada por k6 com métricas .NET:
#   - cpu-usage durante o teste
#   - working-set (memória)
#   - gen-2-gc-count, time-in-gc
#   - thread-pool-completed-items-count
# se P99 estourou em momento específico, qual métrica subiu junto?

Em .NET, combinação k6 (gerador) + dotnet-counters (telemetria de runtime) é stack moderno e gratuito. PerfView para profile detalhado durante picos.

Python — Locust ou k6, profiling com py-spy
# Locust — DSL Python natural para load test
# locustfile.py
from locust import HttpUser, task, between

class CatalogoUser(HttpUser):
    wait_time = between(0.1, 0.5)
    host = "https://api.example.com"

    @task(3)
    def listar_produtos(self):
        self.client.get("/api/produtos", name="listar_produtos")

    @task(1)
    def obter_produto(self):
        produto_id = "abc123"  # idealmente randomizado
        self.client.get(f"/api/produtos/{produto_id}",
                       name="obter_produto")

# rodar (1000 users, ramp-up 100/s, 15 min)
# locust -f locustfile.py --headless --users 1000 \
#   --spawn-rate 100 --run-time 15m

# em paralelo, profile o servidor com py-spy
# py-spy record -o servidor.svg --pid $PID --duration 900

Locust é mais ergonomico para devs Python; k6 é mais rápido e escalável. Ambos integram com Grafana via Prometheus/InfluxDB. py-spy permite ver hot path da aplicação durante o teste.

Go — k6 ou vegeta, pprof concurrent
# vegeta — alternativa Go nativa, simples
echo "GET https://api.example.com/api/produtos/abc" | \
  vegeta attack -rate=1000 -duration=15m | \
  vegeta report

# saída inclui histograma de latência
# Latencies     [mean, 50, 95, 99, max]
# Bytes In      [total, mean]
# Success       [ratio]

# em paralelo, capturar pprof
go tool pprof -http=:8080 \
  http://localhost:6060/debug/pprof/profile?seconds=300

# integração k6 com OTel — k6 emite traces e métricas
# que aparecem em Jaeger/Prometheus junto com server traces

vegeta (em Go) é leve e CLI-first; k6 é mais completo. Ambos comunicam bem com pprof e OTel para correlação cliente↔servidor durante teste.

Anti-padrões frequentes

Teste em ambiente sintético sem dados representativos. Ambiente staging com 100 registros, produção com 100 milhões. Queries que passam em teste viram lentas em prod (full table scan possível em 100 mas catastrófico em 100M). Defesa: ambiente staging com volume representativo, mesmo que sintético; ou usar production replica anonimizada.

SLO baseado em média. Já visto. Em load test, sempre articule percentis, não média.

Não validar erro durante teste. Sistema que retorna 500 rapidíssimo aparenta latência ótima — porque erros são rápidos. Métrica de latência sem error rate associado mente. Sempre rode thresholds de error rate (em k6: http_req_failed: ['rate<0.01']) junto com latência.

Coordinated omission por usar closed-loop sem pensar. Especialmente em ferramentas tradicionais. Use open-loop (constant-arrival-rate) para testes de SLO; use closed-loop só para simular comportamento de usuário com think time.

Cold cache no início do teste subestima latência. Primeiros segundos do teste têm cache miss em todas as camadas (CDN, app, banco). Se você inclui essa janela, métricas saem otimistas indevidamente para sistema operacional. Defesa: warmup explícito por 1-2 minutos antes de começar a coletar métricas.

Testar em horário de produção real. Em sistemas SaaS, load test em produção pode afetar usuários. Defesa: ambiente staging dedicado, ou — em produção — teste em horário de baixa, com flags de feature off, e com plano de cancelamento se degradar UX.

armadilha em produção

Validar SLO só em load steady, ignorar spike. Time valida que sistema sustenta 1000 RPS por 15 minutos: SLO ok, declara sucesso. No lançamento de feature com campanha de marketing, tráfego salta de 100 RPS para 2000 RPS em 30 segundos. Cache fica cold; connection pool toma minutos para crescer; autoscaling toma minutos para subir nova instância. Sistema cai. Causa: load steady não testou rampup agressivo. Defesa: spike test obrigatório antes de evento previsível. Em sistemas com tráfego volátil, spike test deve fazer parte de release maior.

heurística do sênior

Antes de declarar que sistema está pronto para produção, complete a checklist: load test passou com SLO articulado em percentis; stress test identificou knee e o componente que satura; soak test de pelo menos 12h sem vazamento; spike test simulando o pior cenário previsível (lançamento, pico). Para cada teste, métricas coletadas e arquivadas com baseline. Em release seguinte, comparação com baseline detecta regressão antes de produção. Sem essa disciplina, a primeira detecção é a chamada do oncall.

Resumindo o módulo — performance como sistema

Os doze conceitos do módulo cobrem performance em camadas que se reforçam. Disciplina (medir antes de mexer, conceito 01), pilha física (latências, conceito 02), métricas certas (latência distribuída, throughput, conceitos 03–04), ferramentas de diagnóstico (profiling, conceito 05), método de validação (benchmarking, conceito 06), camada hardware (CPU cache, conceito 07), camada runtime (alocação, conceito 08), camada de protocolo (HTTP cache, conceito 09), camada de infraestrutura (CDN, conceito 10), camada de banco (conceito 11), e validação sistêmica (este conceito).

Em sistema bem arquitetado, cada camada contribui. Browser cache elimina viagem; CDN elimina viagem ao origin para 90% dos requests; HTTP cache valida com 304 quando vale; OutputCache server-side serve do memory; HybridCache acessa Redis quando cache miss; Redis hit rate alto evita banco; banco com índice e query plan cache responde em ms; aplicação com hot path otimizado, sem pressão de GC, com layout cache- friendly, retorna em < 10 ms. P99 do usuário em < 200 ms. Tudo medido, perfilado, testado.

Em sistema mal arquitetado, qualquer camada ausente — sem CDN, sem cache HTTP, sem OutputCache, query N+1, GC sob pressão, profile não capturado — vira o gargalo. E como performance é multiplicativa, um único elo fraco define o teto. Daí a disciplina de senior: olhar cada camada, articular o que cada uma deveria estar fazendo, e validar que está.

Por que importa para a sua carreira

Performance testing maduro é diferencial em times sêniores. Em entrevista para tech lead ou arquitetura, "como você prepararia esse sistema para lançamento de Black Friday?" é convite direto para mostrar a disciplina inteira: definir SLO, executar load/stress/soak/spike test, capacity planning, monitorar baseline. A resposta forte é metódica — "primeiro articulo SLO em percentis; depois rodo load test que reflete carga esperada; stress test para entender knee; soak test para descobrir vazamentos; spike test para validar elasticidade". Em revisão de proposta de release, ver checklist completo de testes de performance é sinal de maturidade. Em pos-mortem onde "não esperávamos esse tráfego", a articulação correta é "não tínhamos teste que cobrisse esse cenário" — e a ação corretiva é estender a suite de testes.

Como praticar

  1. Suite k6 completa. Para projeto seu (ou aberto que você usa), implemente: script de load test com SLO articulado em thresholds; script de stress test que aumenta carga gradualmente; script de soak test (ainda que de 30 min para experimento). Rode os três; analise resultados; documente em README/docs. Esse setup, feito uma vez, vira template para projetos futuros.
  2. Capacity planning para um sistema. Pegue um sistema seu. Articule tráfego esperado em pico. Mensure throughput por instância via stress test. Calcule número de instâncias necessárias com margem. Compare com infraestrutura atual — está sub-provisioned, over-provisioned, ou adequado? Documente em ADR. Esse exercício é diferencial para sêniores que tocam decisões de infra.
  3. Diagnóstico durante teste. Durante load test, capture profile do servidor com ferramenta apropriada (dotnet-trace, py-spy, pprof). Identifique hot paths sob carga real (não sintética). Compare com flame graph capturado em idle. Identifique pelo menos uma diferença que sugere gargalo emergente sob carga. Esse exercício torna concreta a conexão entre carga e comportamento da aplicação.

Referências para aprofundar

  1. livro Site Reliability Engineering — Beyer et al., Google (O'Reilly, 2016). Cap. 4 (SLOs) e cap. 6 (Monitoring) cobrem como Google define e valida SLOs em sistemas reais. Cap. 22 cobre cascading failures observadas em load test agressivo.
  2. livro The Art of Capacity Planning — Arun Kejariwal, John Allspaw (O'Reilly, 2017). Atualização do livro de Allspaw de 2008. Cobre capacity planning como disciplina contínua, com casos de Twitter e Etsy.
  3. livro Performance Testing with JMeter (3ª ed.) — Bayo Erinle (Packt, 2017). Para times que ainda usam JMeter, é a referência. Datado mas conceitualmente sólido.
  4. livro Web Performance Tuning — Patrick Killelea (O'Reilly, 2002). Antigo mas com fundamentos sólidos sobre performance testing de aplicações web. Cap. sobre tipos de teste é canônico.
  5. artigo How NOT to Measure Latency — Gil Tene (Strange Loop, 2015). YouTube. Já citado várias vezes. Aqui especialmente relevante por articular coordinated omission e como ferramentas modernas devem evitar.
  6. artigo Testing in Production — Charity Majors (Honeycomb blog, 2017+). honeycomb.io/blog/testing-in-production — Argumenta provocativamente que load test em staging é insuficiente; produção é o único ambiente verdadeiro. Essencial.
  7. docs k6 Documentation. grafana.com/docs/k6 — Documentação canônica. As páginas sobre executores (constant-arrival-rate, ramping-vus) e thresholds são essenciais.
  8. docs Locust Documentation. docs.locust.io — Locust permanece relevante para times Python. Documentação clara e didática.
  9. docs Vegeta. github.com/tsenart/vegeta — Para Go, vegeta é a ferramenta CLI canônica. README e exemplos cobrem uso típico.
  10. docs wrk2. github.com/giltene/wrk2 — A versão de wrk corrigida por Gil Tene para evitar coordinated omission. Para times que querem ferramenta minimalista mas correta.
  11. artigo Performance Testing Patterns — Microsoft Azure Architecture Center. learn.microsoft.com/en-us/azure/architecture/patterns — Catálogo de padrões com diagramas. Útil mesmo fora de Azure.
  12. vídeo Production Readiness — Susan Fowler (várias palestras 2015–2018). YouTube. Fowler escreveu o livro "Production-Ready Microservices" (2016). Apresentação cobre checklist completo, com performance testing como peça central.