Arquitetura de software
Metodologia de testes de carga: modelos de carga, tipos de teste e análise
Intermediário11 min de leituraAtualizado
Esta página foi traduzida do inglês com a ajuda de IA e conferida com o original. Ler a versão em inglês
Um teste de carga é um experimento, e a maioria falha como experimento muito antes de derrubar o software. Este guia cobre os diferentes tipos de teste e a pergunta que cada um responde, como montar um modelo de carga a partir do tráfego real, quanta fidelidade de ambiente você realmente precisa, quais métricas coletar dos dois lados e como ler um resultado para encontrar o ponto de saturação em vez de um número para colocar num slide.
O que você vai aprender
- Escolher o tipo de teste certo — carga, estresse, resistência, pico, capacidade — para a pergunta em questão
- Montar um modelo de carga a partir de evidências de produção, e não de suposições
- Projetar o ramp-up, o think time e os dados de teste para que o teste se pareça com o tráfego real
- Coletar as métricas do lado do cliente e do lado do servidor necessárias para interpretar uma execução
- Identificar o ponto de saturação e distinguir um gargalo real de um artefato do teste
A maioria dos testes de carga produz um número em vez de uma resposta. Um time roda um script, anota que o sistema aguentou 1.200 requisições por segundo, coloca isso num relatório e não aprende nada que mude uma decisão. O problema raramente é a ferramenta; é que o teste nunca foi formulado como uma pergunta. “Vamos aguentar a promoção de sexta-feira?” e “por que o p99 degrada depois de duas horas?” exigem experimentos completamente diferentes, e nenhum dos dois é respondido por um único número de throughput.
Trate um teste de desempenho como um experimento: uma hipótese, condições controladas, medições dos dois lados da rede e uma análise que confirma ou refuta a hipótese. Este guia mostra como montar esse experimento. Diagnosticar o que os resultados revelam — profiling, GC, acesso ao banco de dados — é o assunto de desempenho de aplicações.
Tipos de teste e as perguntas que eles respondem
Os tipos se diferenciam pelo formato da carga e pela duração, e usar o errado produz uma resposta errada com toda a convicção.
| Tipo | Formato da carga | Pergunta que responde |
|---|---|---|
| Smoke | Muito baixa, minutos | O script funciona e o ambiente sequer responde? |
| Carga | Pico esperado, estável | Atingimos nossos objetivos com o tráfego que esperamos? |
| Estresse | Crescente até a falha | Onde ele quebra, e como quebra? |
| Capacidade | Aumentos em degraus | Qual é o throughput máximo sustentável, e o que o limita? |
| Resistência (soak) | Moderada, de horas a dias | Algo se degrada com o tempo — vazamentos, fragmentação, crescimento de logs? |
| Pico (spike) | Salto repentino e queda | Ele sobrevive a uma avalanche súbita de acessos, e se recupera? |
| Escalabilidade | Carga por unidade de recurso | Dobrar as instâncias dobra o throughput? |
Dois deles são sistematicamente subestimados. Os testes de resistência encontram as falhas que só aparecem com o tempo: uma coleção que cresce devagar, vazamentos de conexão, um cache que nunca remove nada, discos que enchem de logs. São essas as falhas que derrubam a produção às 3 da manhã do quarto dia, e nenhum teste curto consegue encontrá-las. Os testes de estresse importam menos pelo número em que o sistema quebra do que pelo comportamento ao quebrar: um sistema que descarta o excesso de carga com respostas 429 rápidas e se recupera quando a carga diminui é imensamente melhor do que um que enfileira até esgotar a memória, mesmo que os dois quebrem no mesmo throughput.
Montando o modelo de carga
O fator que mais determina se um teste prevê alguma coisa é o quanto a carga modelada se parece com a real. Tráfego uniforme contra um único endpoint é fácil de escrever e não diz nada.
Comece pelas evidências. Logs de acesso, analytics do gateway e traces mostram a distribuição real: quais operações, em que proporção, em que ritmo e com que variedade de parâmetros. Use um horário de pico representativo em vez de uma média diária, porque é o pico que define a capacidade.
Modele processos de negócio, não endpoints. Usuários reais seguem jornadas — navegar, buscar, ver um produto, adicionar ao carrinho, finalizar a compra — com taxas de abandono realistas entre as etapas. Um modelo em que 100% dos usuários virtuais finalizam a compra superestima enormemente a carga de escrita e subestima a busca.
Scenario mix for a retail peak hour (from 30 days of access logs):
Browse-only ........ 62% home → category → 3 × product
Search-driven ...... 24% search → 2 × product → add to basket (40% of these)
Checkout ............ 9% basket → address → payment → confirmation
Account admin ....... 4% login → orders → order detail
API partners ........ 1% steady 25 req/s, no think time
Peak measured arrival rate: 340 sessions/minute
Read:write ratio: 94:6
Think time não é opcional. Usuários reais fazem pausas entre as ações, o que muda a concorrência para uma dada taxa de chegada — e, pela lei de Little, muda quantas sessões são necessárias para produzir um dado throughput. Remover o think time transforma 5.000 usuários realistas na carga de uns 200, e produz um teste que martela um único caminho de código com uma localidade de cache irreal. Use uma distribuição, não uma constante: uma pausa aleatória entre 3 e 12 segundos é muito melhor do que uma fixa de 5.
Varie os dados. Se todo usuário virtual pede o mesmo ID de produto, você está medindo o cache, não o sistema. Use uma distribuição de chaves realista — em geral uma cauda longa com alguns itens realmente quentes — e dados únicos onde a unicidade importa, para que o teste não esbarre por acidente em restrições de unicidade nem encha uma única partição.
Modele as chegadas, não só a concorrência. Ferramentas de modelo fechado mantêm N usuários virtuais, cada um esperando uma resposta antes de continuar, o que significa que a carga oferecida cai automaticamente quando o sistema fica lento — é o problema da coordinated omission, e ele joga a seu favor. Ferramentas de modelo aberto injetam numa taxa de chegada definida, independentemente do tempo de resposta, que é como o tráfego real da internet se comporta e como as filas de fato se formam. Prefira a injeção por taxa de chegada para tudo o que for exposto ao público.
Ambiente e dados de teste
Fidelidade perfeita é cara demais; a pergunta útil é quais diferenças distorcem o resultado.
| Fator | Importa | Pode ser reduzido em geral |
|---|---|---|
| Volume e distribuição dos dados | De forma crítica — os planos mudam com a cardinalidade | Raramente; use dados do tamanho da produção |
| Topologia da aplicação | Sim — número de saltos, comportamento do balanceamento de carga | Mesmo formato, menos instâncias |
| Dimensionamento do banco | Sim; geralmente é o gargalo | Só com interpretação cuidadosa |
| Caminho de rede e latência | Sim, para arquiteturas com muitas idas e vindas | Simule latência adicional |
| Dependências de terceiros | Sim | Stub com latência realista, incluindo a cauda |
| Geração de CPU, tipo de disco | Sim para valores absolutos | Compare apenas resultados relativos |
O volume de dados merece destaque. Um plano de execução que usa um índice com 10.000 linhas pode passar para uma varredura sequencial com 10 milhões, então um teste contra um banco pequeno valida a funcionalidade e nada sobre o desempenho. Se você não puder copiar os dados de produção, gere um conjunto com o mesmo volume, a mesma cardinalidade e a mesma assimetria — incluindo aquele cliente com 40.000 pedidos que quebra todas as premissas de paginação.
Stubs de terceiros precisam reproduzir a distribuição de latência da dependência, não a média. Uma API de parceiro que responde em 80 ms, com um ou outro outlier de 3 segundos, vai expor o comportamento dos seus timeouts e retries; um stub que sempre responde em 80 ms, não. Quando possível, inclua um modo de falha em algumas execuções.
Por fim, reduza a escala com honestidade. Resultados de um ambiente com um quarto do tamanho só preveem a produção se o sistema escalar linearmente e o banco for reduzido na mesma proporção — exatamente o que um teste de escalabilidade verifica. Declare a premissa de escala no relatório em vez de deixar o leitor supor uma fidelidade que você não tinha.
Escrevendo o teste
Três ferramentas dominam, e a escolha importa menos do que o modelo por trás dela.
JMeter é maduro, operado por interface gráfica com um plano de teste em XML, tem um ecossistema enorme de protocolos e plugins e é bem conhecido pelos times de teste. Suas threads correspondem a usuários virtuais, então concorrência muito alta exige injetores distribuídos, e versionar o plano XML é desagradável, mas viável.
Gatling descreve os cenários numa DSL em Scala ou Java, usa um motor assíncrono que permite a um único injetor sustentar alta concorrência e gera bons relatórios HTML. É indicado para times à vontade com uma linguagem da JVM que querem revisar os testes como código.
k6 usa JavaScript, é fácil de programar e gera diffs legíveis, tem suporte nativo a executors por taxa de chegada e a thresholds que reprovam uma execução automaticamente, e se encaixa bem em pipelines.
// k6: open model — inject a fixed arrival rate regardless of response time.
import http from 'k6/http';
import { check, sleep } from 'k6';
import { Trend } from 'k6/metrics';
const checkoutLatency = new Trend('checkout_latency', true);
export const options = {
scenarios: {
browse: {
executor: 'ramping-arrival-rate',
startRate: 20, timeUnit: '1s',
preAllocatedVUs: 400, maxVUs: 2000,
stages: [
{ target: 120, duration: '5m' }, // ramp
{ target: 120, duration: '20m' }, // steady state — this is what we measure
{ target: 0, duration: '2m' }, // ramp down
],
},
},
thresholds: {
'http_req_failed': ['rate<0.005'], // fail the run above 0.5% errors
'checkout_latency': ['p(95)<800', 'p(99)<2000'],
},
};
const products = JSON.parse(open('./product-ids.json')); // realistic key distribution
export default function () {
const id = products[Math.floor(Math.random() * products.length)];
const home = http.get(`${__ENV.BASE}/`, { tags: { name: 'home' } });
check(home, { 'home 200': (r) => r.status === 200 });
sleep(Math.random() * 9 + 3); // think time 3–12 s
const product = http.get(`${__ENV.BASE}/products/${id}`, { tags: { name: 'product' } });
check(product, { 'product 200': (r) => r.status === 200 });
sleep(Math.random() * 6 + 2);
if (Math.random() < 0.09) {
const res = http.post(`${__ENV.BASE}/orders`,
JSON.stringify({ sku: id, quantity: 1 }),
{ headers: { 'Content-Type': 'application/json', 'X-Synthetic': 'loadtest' },
tags: { name: 'checkout' } });
checkoutLatency.add(res.timings.duration);
check(res, { 'order created': (r) => r.status === 201 });
}
}
Repare nas tags em cada requisição: sem marcação por operação, um p95 agregado mistura um asset estático de 20 ms com um checkout de 900 ms e não descreve nenhum dos dois. Repare também no header X-Synthetic, que permite à plataforma excluir o tráfego de teste das métricas de negócio.
Seja qual for a ferramenta, mantenha os scripts de teste no repositório, ao lado da aplicação, revise-os e parametrize o ambiente. Um teste de carga que só uma pessoa sabe rodar do próprio notebook deixa de ser rodado.
Métricas a coletar
Os números do lado do cliente dizem o que os usuários experimentariam; os do lado do servidor dizem por quê. Você precisa dos dois, no mesmo relógio.
Do gerador: a taxa de chegada obtida versus a solicitada (uma diferença significa que o gerador está saturado e todos os outros números são suspeitos), os percentis de tempo de resposta por operação, a taxa de erros separada por status e por causa, erros de conexão e timeouts separados dos erros HTTP, e o volume de dados transferido.
Do sistema: os sinais expostos pelo Actuator e pela observabilidade — taxa de requisições, taxa de erros e percentis de latência por serviço e endpoint; CPU, memória, disco e rede por instância; threads ativas e profundidade de fila dos pools de threads; uso do pool de conexões e tempo de espera para obter uma conexão; duração e frequência das pausas de GC; transações por segundo, esperas por lock, quantidade de consultas lentas e taxas de acerto de cache no banco; e a profundidade das filas em qualquer broker.
Verifique também o próprio gerador. Um injetor a 95% de CPU, sem portas efêmeras disponíveis ou com suas próprias pausas de GC reporta uma latência que é só dele. Monitore-o com o mesmo cuidado que o sistema sob teste, e distribua a injeção assim que uma única máquina não der mais conta com folga.
Lendo os resultados
Plote o throughput, o tempo de resposta e a taxa de erros em função da carga oferecida, no mesmo eixo de tempo, e o formato das curvas conta a história.
À medida que a carga aumenta, o throughput cresce de forma mais ou menos linear enquanto o tempo de resposta fica estável: o sistema tem capacidade sobrando. Depois o crescimento do throughput desacelera e o tempo de resposta começa a subir — é o joelho, onde um recurso se aproxima da saturação e as filas começam a se formar. Continue forçando e o throughput estabiliza ou cai enquanto o tempo de resposta dispara e surgem erros: é a saturação, e além dela vem o colapso, em que o throughput despenca porque o sistema gasta seu esforço com timeouts, retries e trocas de contexto.
O número que vale reportar é a maior carga oferecida em que o sistema ainda atinge seus objetivos — não o throughput máximo observado, que em geral acontece com uma latência que ninguém aceitaria. Depois responda à pergunta seguinte: o que o limitou? Uma CPU saturada, um pool de conexões cheio, um lock, um serviço downstream ou o gerador. Se você não consegue apontar o recurso limitante, o teste produziu um número em vez de uma resposta.
Alguns padrões e o que eles costumam significar:
- A latência sobe enquanto a CPU continua baixa — o sistema está esperando alguma coisa: um pool, um lock, uma chamada downstream.
- O throughput estabiliza bem abaixo do esperado com um sistema aparentemente saudável — um limite artificial de concorrência: tamanho do pool de threads, pool de conexões, um semáforo ou o limite do próprio gerador.
- Os erros só aparecem acima de um limite — uma fila transbordando ou um timeout expirando; descubra qual e se esse é o comportamento correto.
- Os tempos de resposta degradam aos poucos ao longo de horas com carga constante — um vazamento, crescimento sem limite ou inchaço de índices. Estenda para um teste de resistência.
- Uma mudança brusca depois de alguns minutos — um cache enchendo, um job batch começando ou a compilação JIT terminando; verifique o que mais roda nesse horário.
Compare com uma baseline do mesmo ambiente em vez de com uma meta absoluta de outro sistema. E repita: três execuções da mesma configuração mostram se uma diferença de 7% é uma regressão ou ruído.
Erros comuns
Testar um único endpoint. Mede um único caminho de código com localidade de cache perfeita e não prevê nada sobre o tráfego misto da produção.
Sem think time. A concorrência e o comportamento do cache ficam errados, e na direção que favorece o sistema.
Reportar a média. Como em qualquer trabalho com latência, a média esconde justamente o comportamento que importa.
Um conjunto de dados minúsculo ou irreal. Os planos de execução mudam, os caches guardam tudo e a assimetria some.
Ignorar o aquecimento. Medir durante a compilação JIT, com pools frios e caches vazios, gera números pessimistas que escondem o problema do regime estável.
Não restaurar o estado entre execuções. Os dados crescem, os caches continuam quentes e cada execução mede um sistema diferente.
Ignorar o cliente. Um gerador saturado faz um sistema doente parecer saudável.
Testar só o caminho feliz. Nenhuma dependência lenta, nenhuma falha parcial, nenhuma tempestade de retries — e o comportamento de resiliência, que decide se um incidente vira uma indisponibilidade total, nunca é exercitado.
Rodar uma única vez, antes do go-live. O desempenho regride a cada release. Um teste automatizado curto no pipeline de entrega, somado a um teste completo agendado, é o que mantém o resultado verdadeiro.
Principais conclusões
- Formule cada teste como uma pergunta e escolha o tipo — carga, estresse, capacidade, resistência, pico — que a responde; resistência e estresse revelam as falhas que mais doem.
- O modelo de carga define o valor do resultado: mix real de operações, cenários de negócio com abandono, think time aleatório, dados variados e injeção por taxa de chegada.
- Volume e distribuição dos dados importam mais do que quase qualquer outra diferença de ambiente; simule terceiros com uma distribuição de latência realista, não com uma constante.
- Marque cada requisição por operação, mantenha os scripts sob controle de versão e registre versão, configuração e conjunto de dados a cada execução.
- Colete juntas as métricas do lado do cliente e do lado do servidor, e sempre verifique se o próprio gerador de carga não é o gargalo.
- Reporte a maior carga que ainda atinge os objetivos e aponte o recurso limitante; um número de throughput sem causa não é uma conclusão.
Fontes e leituras complementares
Perguntas frequentes
- Posso fazer teste de carga em produção?
- Muitas organizações fazem, e é o único ambiente com fidelidade de verdade. Isso exige salvaguardas: rodar em horários de pouco tráfego, marcar as requisições sintéticas com um header para que possam ser excluídas das métricas de negócio, limitar a taxa injetada, ter um botão de parada imediata e garantir que o tráfego de teste não consiga criar pedidos reais nem enviar e-mails reais. Comece com uma pequena fatia da capacidade de produção e aumente devagar.
- Quanto tempo um teste de carga deve durar?
- O suficiente para passar do aquecimento e chegar ao regime estável, o que, para um serviço JVM, significa pelo menos dez a quinze minutos de medição depois do ramp-up, para que a compilação JIT, os pools de conexões e os caches se acomodem. Qualquer coisa mais curta mede a inicialização. Testes de resistência respondem a uma pergunta totalmente diferente e precisam de horas a dias para revelar vazamentos e fragmentação.
- Por que meus resultados mudam a cada execução?
- Geralmente porque alguma coisa não está controlada. Os culpados comuns são um ambiente compartilhado com outro tráfego, um banco de dados cujo volume cresce a cada execução, caches ainda quentes de um teste anterior, vizinhos barulhentos em ambiente virtualizado e um gerador de carga que está ele mesmo saturado. Restaure os dados entre as execuções, acompanhe a CPU e a rede do gerador e repita cada configuração pelo menos três vezes antes de acreditar numa diferença.
- Os testes de carga devem rodar no pipeline de CI?
- Um teste de desempenho curto, no nível de smoke, a cada build vale muito: alguns minutos com carga moderada, falhando diante de um limite claro de regressão, o que pega a consulta N+1 acidental antes do merge. Testes completos de capacidade e de resistência devem rodar de forma agendada em um ambiente dedicado, porque precisam de tempo, de infraestrutura estável e de uma pessoa para interpretar o resultado.