Visão geral
No ano passado, a maior venda do BoxOffice caiu às 10:02. O time tinha feito teste de carga: 200 usuários virtuais contra o staging, p95 de 180 ms, "tudo verde". No post-mortem, encontraram cinco problemas nesse teste. O staging tinha um décimo dos dados da produção, então toda consulta acertava um cache quente. Os usuários virtuais esperavam cada resposta antes de enviar a próxima requisição, então, quando o sistema ficou lento, o teste educadamente ficou lento junto e nunca viu a fila que os fãs reais formaram. O teste durou dez minutos e nunca chegou ao ponto em que o pool de conexões do banco se esgotava. Ninguém tinha escrito o que significava "bom o suficiente" antes de rodá-lo. E o relatório mostrava médias.
Engenharia de desempenho é a disciplina que evita esse post-mortem. Não é "rodar uma ferramenta e olhar um gráfico": é um método — declarar o objetivo, modelar a carga de trabalho, gerar carga que se comporte como usuários reais, medir o que os usuários sentem, encontrar o único recurso que limita o sistema, corrigi-lo e medir de novo. Esta aula ensina o método e as ferramentas, usando a venda relâmpago do BoxOffice como caso de teste.
A história até aqui
Capítulo 15 da história da disciplina. Cada capítulo acrescentou uma caixa ao BoxOffice porque um número pediu: uma borda e réplicas sem estado (aula 4, aula 5), um banco que vende cada assento uma vez só (aula 6), um cache (aula 7), uma sala de espera (aula 8), SLOs e disjuntores (aula 9, aula 10), Kubernetes (aula 11) — e, desde a aula 14, as mudanças chegam mais rápido do que alguém consegue reverificá-las à mão. Cada caixa foi medida sozinha, no seu próprio lab. Esta aula responde: o projeto inteiro aguenta a próxima venda — 2 M de fãs às 10:00, p99 abaixo de 300 ms —, como provar isso, e como manter a prova a cada mudança?
Objetivos de aprendizagem
Ao final desta aula você deverá ser capaz de:
- Definir latência, tempo de serviço, vazão, concorrência, utilização e saturação, e relacioná-los com a Lei de Little.
- Ler uma curva vazão–latência, localizar o joelho e o ponto de saturação e explicar por que a latência explode quando a utilização se aproxima de 100 %.
- Aplicar uma metodologia de desempenho — caracterização da carga, o método USE, métricas RED — para encontrar o gargalo de um sistema em vez de adivinhar.
- Projetar e escrever testes smoke, load, stress, spike, soak e breakpoint em k6, escolhendo entre os modelos de carga aberto (taxa de chegada) e fechado (usuários virtuais), com thresholds que codificam os SLOs.
- Detectar a omissão coordenada e outros erros de medição (dados irreais, caches frios, gerador de carga saturado) que fazem um teste mentir.
- Diagnosticar um gargalo com profiling (flame graphs do async-profiler e do py-spy), ferramentas de banco de dados (
EXPLAIN ANALYZE,pg_stat_statements) e dimensionamento de pools. - Planejar capacidade com a Lei de Little e folga, e impor orçamentos de desempenho na CI.
Roteiro
- Esta visão geral — o vocabulário, a curva vazão–latência e seu joelho, e o ciclo de engenharia de desempenho.
- Metodologia — anti-métodos, caracterização da carga, os métodos USE e RED, ambientes de teste, dados de teste e aquecimento.
- Testes de carga — os seis tipos de teste em k6, modelos aberto vs fechado, thresholds como SLOs, omissão coordenada, caos sob carga e os perfis do playground.
- Encontrando gargalos — leitura dos resultados, profiling e flame graphs, gargalos de banco de dados, dimensionamento de pools.
- Planejamento de capacidade — Lei de Little e folga, atraso do autoscaling, um modelo de capacidade para a venda e orçamentos de desempenho na CI.
- Quiz — teste seu entendimento.
Slides
Clique nos slides e use ← → para navegar; F abre em tela cheia e P abre a visão do apresentador, com as notas. Abrir os slides em uma nova aba
O vocabulário de desempenho
Palavras precisas evitam a maioria das discussões sobre desempenho. A aula de Estimativas as apresentou; aqui elas viram alvos de medição.
| Termo | Definição | Exemplo no BoxOffice |
|---|---|---|
| Tempo de serviço (S) | Tempo que um recurso passa de fato trabalhando em uma requisição | 4 ms de CPU e 6 ms de banco para fazer uma reserva de assento |
| Tempo de espera (fila) | Tempo que uma requisição passa esperando um recurso ocupado | Esperar uma conexão livre no pool |
| Latência / tempo de resposta (R) | O que o cliente mede: espera + serviço + rede | POST /holds levou 230 ms de ponta a ponta |
| Vazão (X) | Requisições concluídas por unidade de tempo | 1 800 reservas por segundo |
| Concorrência (N) | Requisições no sistema no mesmo instante (em serviço ou esperando) | 120 reservas em andamento |
| Utilização (U) | Fração do tempo em que um recurso está ocupado | CPU do banco ocupada 82 % do tempo |
| Saturação | Trabalho que o recurso ainda não consegue atender — o tamanho da fila | 40 requisições esperando uma conexão |
| Percentil (p50, p99, p99.9) | O valor abaixo do qual fica aquela fração das observações | p99 = 300 ms: 1 reserva em 100 é mais lenta que 300 ms |
| Taxa de erros | Fração de requisições com falha — incluindo timeouts | 0,3 % das reservas retornaram 503 |
Três relações valem para qualquer sistema estável e merecem ser memorizadas:
- Lei de Little: \(N = X \times R\) — a concorrência é igual à vazão vezes o tempo de resposta1. A 1 800 reservas/s e 0,12 s cada, há em média 216 reservas em andamento: é de quantas threads, conexões ou slots assíncronos o caminho precisa.
- Lei da utilização: \(U = X \times S\) — um banco que gasta 0,4 ms de CPU por consulta (por núcleo) a 2 000 consultas/s em um núcleo está 80 % ocupado.
- Tempo de resposta = tempo de serviço + tempo de espera: otimizar código encurta o tempo de serviço; adicionar capacidade ou reduzir a carga encurta a espera. Perto da saturação, a espera domina.
Médias mentem
Uma média de 120 ms é compatível com "todos esperam 120 ms" e com "99 % esperam 20 ms e 1 % espera 10 s". Usuários — e SLOs — vivem na cauda. Reporte percentis a partir de histogramas (HDR ou histogramas nativos), nunca a média de percentis de máquinas ou intervalos diferentes: percentis não podem ser somados e divididos. Veja Percentis e latência de cauda.
A curva vazão–latência e seu joelho
Todo sistema, de uma thread a um data center, mostra a mesma forma quando você aumenta a carga. O gráfico da esquerda modela um pod de inventory com 8 threads de trabalho e 20 ms de tempo médio de serviço — capacidade de 400 requisições por segundo — como uma fila M/M/8. O da direita usa a Lei Universal de Escalabilidade (USL, Universal Scalability Law), que adiciona contenção (σ, esperar por recursos compartilhados) e coerência (κ, o custo de manter o estado compartilhado consistente) à escala linear ideal2:
Leia os dois gráficos juntos:
- A região plana. Com carga baixa, a latência é só o tempo de serviço. Dobrar a carga quase não a muda. A maioria dos sistemas passa a maior parte da vida aqui, e é por isso que parecem rápidos.
- O joelho. Acima de cerca de 70–80 % de utilização, as requisições começam a encontrar todos os servidores ocupados e entram na fila. A latência — e o p99 primeiro — se curva para cima. O joelho é a capacidade útil do sistema: a maior vazão em que o SLO de latência ainda é cumprido.
- Saturação. Quando a carga oferecida se aproxima da capacidade, as filas crescem sem limite; a latência fica vertical e os timeouts viram erros. A vazão para de crescer.
- Escala retrógrada. Com custos de contenção e coerência, adicionar mais concorrência depois do pico reduz a vazão — threads brigam por locks, caches se invalidam, conexões expiram e são refeitas. Um sistema empurrado além do pico pode vender menos ingressos por segundo do que um mantido logo abaixo dele, que é o argumento central para controle de admissão e salas de espera.
BoxOffice: o joelho é o número com que você planeja
Se um pod de inventory mantém o p99 abaixo de 300 ms até 275 req/s e colapsa em 360, o número de capacidade para o planejamento é 275, não 360. Para uma venda que espera 4 500 requisições de reserva por segundo no pico, isso significa ⌈4 500 / 275⌉ = 17 pods antes da folga — 22 a 75 % do joelho, 33 para sobreviver à perda de uma de três zonas — e um banco que aguente as conexões de 33 pods. A página de Planejamento de capacidade constrói esse modelo de ponta a ponta.
No código do BoxOffice: o joelho do catalog, medido (lab 2)
O Hands-on 2 — Medir primeiro encontra o joelho exatamente como esta seção descreve: o sweep.sh roda o load.js (modelo aberto) uma vez por taxa de chegada, um minuto cada, depois de um aquecimento de 30 s que não é registrado, e espera 10 s entre os passos para as filas esvaziarem. Na execução de exemplo do lab (catalog limitado a 1 CPU, pool de 10 conexões com o banco), o p99 é 38,6 ms a 300 req/s, 223,7 ms a 400 e 2.056 ms a 500 — onde o k6 também descartou 410 iterações. O joelho fica entre 300 e 400 req/s, e o recurso que saturou foi o pool de conexões, não a CPU.
#!/usr/bin/env bash
# Lab 2: run load.js once per arrival rate and save one k6 summary per run.
# bash scripts/sweep.sh 25 50 100 150 200 300 400
# Then: uv run scripts/summarize.py results/lab2/rate-*.json
set -euo pipefail
OUT=${OUT:-results/lab2}
DURATION=${DURATION:-1m}
mkdir -p "$OUT"
echo ">>> warm-up: 30 s at RATE=$1 (not recorded)"
k6 run -q -e RATE="$1" -e DURATION=30s load/load.js > /dev/null || true
for rate in "$@"; do
echo ">>> RATE=$rate req/s for $DURATION"
# || true: a run that crosses a threshold still produces a summary; keep sweeping
k6 run -q -e RATE="$rate" -e DURATION="$DURATION" \
--summary-export "$OUT/rate-$rate.json" load/load.js > /dev/null || true
sleep 10 # let queues drain before the next step
done
Experimente: este cenário do playground é o sistema mais simples possível — cliente → servidor de aplicação → banco SQL — rodando o perfil breakpoint, que aumenta a taxa de chegada passo a passo até algo quebrar. Pressione Run e observe três números juntos: vazão, latência p99 e a utilização de cada componente. Anote a taxa em que o p99 começa a se curvar (o joelho) e qual componente chega a 100 % primeiro (o gargalo). Depois dê mais capacidade a esse componente e rode de novo: o joelho se move, e qual componente vira o novo gargalo?
Abra o playground completo para adicionar um cache ou réplicas de leitura e ver como a curva muda.
O ciclo de engenharia de desempenho
Trabalho de desempenho é um ciclo científico, não um evento único antes do lançamento3:
flowchart LR
slo["1. Objetivos<br/>SLOs por endpoint,<br/>pico esperado"] --> model["2. Modelo de carga<br/>mix, padrão de chegada,<br/>dados"]
model --> test["3. Teste<br/>cenário k6 em um<br/>ambiente parecido com produção"]
test --> analyze["4. Análise<br/>curva, USE, RED,<br/>perfis"]
analyze --> fix["5. Mude UMA coisa<br/>(código, config,<br/>capacidade)"]
fix --> test
analyze --> ship["6. Publique + orçamento na CI<br/>monitore em produção"]
ship --> slo - Objetivos primeiro. "Rápido" não é um requisito. "
POST /holds: p99 < 300 ms e erros < 0,1 % a 4 500 req/s" é. Sem isso, nenhum teste pode passar ou falhar. - Mude uma coisa de cada vez e rode o mesmo teste de novo — senão você não consegue atribuir a melhoria.
- A produção fecha o ciclo. O tráfego real é o melhor modelo de carga: métricas e traces de produção alimentam o próximo teste, e o orçamento de desempenho na CI impede que regressões voltem.
| Nível | Pergunta | Ferramentas |
|---|---|---|
| Micro-benchmark | Esta função/estrutura de dados é mais rápida? | JMH (Java), pytest-benchmark |
| Teste de componente | Quantas req/s um pod de inventory sustenta dentro do SLO? | k6 contra um pod, com profiler acoplado |
| Teste de sistema | O caminho de compra inteiro aguenta o pico da venda? | Cenários k6 contra um ambiente parecido com produção4 |
| Produção | Está aguentando agora? De onde vem a cauda? | Dashboards RED, traces, profiling contínuo, alertas de SLO |
Na entrevista
Quando um entrevistador perguntar "como você saberia que este design aguenta o pico?", não diga "faríamos um teste de carga". Diga o que você testaria e como leria: "Um teste k6 de modelo aberto na taxa de chegada de pico esperada, com o mix realista de endpoints, thresholds de p99 e taxa de erros por endpoint, longo o suficiente para atingir o regime permanente com caches aquecidos; depois um teste breakpoint para achar o joelho e calcular a folga; o método USE no banco e no pool de conexões para descobrir o que satura primeiro."
Boas práticas
- Mantenha os testes de carga no repositório do serviço, versionados com o código — um script que se afasta da API mede um endpoint que já não existe. No BoxOffice: a pasta
load/do repositório, do lab 2 ao lab 11. - Registre cada execução com suas condições — commit, configuração (tamanho do pool, limite de CPU), snapshot dos dados e máquina ao lado dos números; um p99 sem as condições não pode ser comparado com o próximo. No BoxOffice:
results/lab2.mde as pastas da varredura do lab 2. - Publique a capacidade como um número por réplica, com o seu SLO — "
catalog: 300 req/s com p99 < 300 ms em 1 CPU" é o número de que saem o planejamento de capacidade, o autoscaling e a taxa de admissão da sala de espera. No BoxOffice: o joelho medido no lab 2. - Leia o cliente e o servidor no mesmo eixo de tempo — o k6 diz que o p99 quebrou, as métricas RED e USE dos serviços dizem onde; marque cada execução e coloque os dois no mesmo Grafana. No BoxOffice: a pilha
otel-lgtmdo lab 8. - Barre cada mudança com um orçamento curto; rode a campanha completa antes de cada grande evento — um teste de 1 minuto em cada pull request pega o índice que falta; só os testes longos de load, spike e breakpoint provam a venda. No BoxOffice: o job
performance budgetdo lab 11.
Armadilhas comuns
- Sem objetivo: um teste sem critério de aprovação produz gráficos, não decisões.
- Testar o sistema errado: dados minúsculos, caches quentes, tamanho de instância diferente, sem TLS, sem gateway.
- Testes de modelo fechado para tráfego de mundo aberto: o gerador de carga desacelera junto com o sistema e esconde a fila (omissão coordenada).
- Médias e execuções únicas: reporte percentis a partir de histogramas; repita as execuções e olhe a variância.
- Otimizar sem gargalo: otimizar um componente a 20 % de utilização não muda nada.
- Um gerador de carga saturado: se a máquina do k6 está a 100 % de CPU, você mediu o k6.
O mapa de conceitos de desempenho
A engenharia de desempenho não acrescenta caixas novas à paleta: ela mede as que você já tem. O mapa liga cada componente da paleta (verde) aos conceitos usados para carregá-lo, observá-lo e dimensioná-lo (azul):
flowchart LR
client["client · mobile<br/>(k6 generates them)"]:::comp --> edge["apigw · lb"]:::comp
edge --> app["app"]:::comp
app --> sql["sql"]:::comp
app --> cache["cache"]:::comp
queue["queue<br/>(waiting room)"]:::comp --> app
obs["metrics · tracing"]:::comp -.- app
client -.- c1["open vs closed model,<br/>6 test types, thresholds"]:::tech
app -.- c2["Little's Law, the knee,<br/>USL"]:::tech
sql -.- c3["EXPLAIN ANALYZE, hot rows,<br/>pool sizing"]:::tech
cache -.- c4["warm-up, cold cache,<br/>hit ratio"]:::tech
obs -.- c5["USE, RED,<br/>flame graphs"]:::tech
queue -.- c6["headroom, autoscaling lag,<br/>admission control"]:::tech
classDef comp fill:#1e9e5a22,stroke:#1e9e5a
classDef tech fill:#2f6fed22,stroke:#2f6fed | Componente da paleta | Pergunta que responde | Conceitos | Ensinado em | Cenários |
|---|---|---|---|---|
client · mobile | Como a carga real chega? | modelo de carga, aberto (taxa de chegada) vs fechado (usuários virtuais), think time, omissão coordenada | Metodologia · Testes de carga | flash-sale, monolith |
app | Quanto uma réplica aguenta dentro do SLO? | tempo de serviço, Lei de Little, lei da utilização, o joelho, USL, profiling | esta visão geral · Encontrando gargalos | monolith (perfil breakpoint), scale-out |
sql | Qual consulta ou lock limita o caminho? | pg_stat_statements, EXPLAIN ANALYZE, linhas quentes, SKIP LOCKED, dimensionamento do pool de conexões | Encontrando gargalos | flash-sale |
cache | O teste está medindo um cache quente que a produção não terá? | aquecimento, teste com cache frio, taxa de acerto, stampede | Metodologia | read-heavy-cache |
metrics · tracing | Qual recurso satura primeiro? | USE, RED e sinais de ouro, flame graphs, profiling contínuo | Metodologia · Encontrando gargalos | observability-stack |
queue · serverless | Quantas réplicas, e quanto admitimos? | folga, atraso do autoscaling, cold starts, pré-escalonamento, taxa de admissão da sala de espera | Planejamento de capacidade | flash-sale, serverless-burst |
thirdparty | O que acontece quando uma dependência fica lenta sob carga? | carga e caos juntos, Toxiproxy, timeouts, circuit breakers | Testes de carga | cascading-failure |
| (o pipeline) | A próxima mudança vai regredir? | orçamentos de desempenho na CI, thresholds como SLOs | Planejamento de capacidade | — |
Relacionados
- Hands-on: 2. Medir primeiro (k6 smoke/load/stress no
catalog, encontrar o joelho, verificação da Lei de Little), 6. Dados em escala (EXPLAIN ANALYZE, índices, PgBouncer), 9. Resiliência e caos (k6 sob falhas do Toxiproxy) e 11. Entrega (gate de orçamento de desempenho com k6 na CI). - Exercício: Relatório de teste de carga; projeto: Campanha de testes de carga e Game day.
- Aulas: Estimativas — filas e percentis, Observabilidade, Confiabilidade — engenharia do caos.
- Estudo de caso: Ticketing (venda relâmpago do BoxOffice); ferramentas: Referência — ferramentas.
Pontos-chave
- Tempo de resposta = tempo de serviço + espera; perto da saturação, a espera domina. \(N = X \times R\) e \(U = X \times S\) conectam os números.
- Todo sistema tem um joelho: planeje com a vazão em que o SLO de latência ainda vale, não com o máximo.
- Depois do pico, mais concorrência pode significar menos vazão (USL) — controle de admissão é melhor que sobrecarga.
- Engenharia de desempenho é um ciclo: objetivos → modelo de carga → teste → análise → mudar uma coisa → testar de novo → orçamento na CI.
O que quebra em seguida
A engenharia de desempenho prova este projeto para esta carga: um sistema de ingressos, uma venda, uma mistura de cerca de 80 % de leituras e 20 % de reservas e pedidos, um joelho medido nos próprios pods do BoxOffice. O método — modelo de carga, testes de modelo aberto, o joelho, a folga, um orçamento na CI — vale para qualquer sistema; os números e as caixas, não. Um feed de notícias espalha cada post para milhões de seguidores, um chat mantém milhões de conexões abertas por horas, um app de corridas grava a posição de cada motorista a cada poucos segundos: números diferentes pedem blocos diferentes. Reconhecer quais blocos dos quinze capítulos anteriores um sistema novo precisa, e qual número pede cada um, é a aula 16 — Estudos de caso.
-
LITTLE, J. D. C. A Proof for the Queuing Formula: L = λW. Operations Research, v. 9, n. 3, p. 383–387, 1961. ↩
-
GUNTHER, N. J. Guerrilla Capacity Planning. Springer, 2007. ↩
-
GREGG, B. Systems Performance: Enterprise and the Cloud. 2nd ed. Addison-Wesley, 2020. ↩
-
Grafana Labs. k6 documentation — Test types. ↩