Teste de carga na prática: k6 + dashboard
Código funciona no ambiente de dev. Passa nos testes unitários. Passa na revisão. E ainda assim cai na primeira sexta-feira de alto tráfego. Falta uma pergunta que teste nenhum desses cobre: o sistema aguenta carga real?
É pra isso que existe teste de carga. Neste post vou explicar o que é, listar os principais tipos de teste que existem, e mostrar como rodar todos eles com k6, usando um projeto que montei com Docker Compose e dashboard em tempo real.
O que é teste de carga
Teste de carga (load testing) é a prática de simular tráfego real (ou maior que o real) contra um sistema, pra observar como ele se comporta. Não é sobre achar bug de lógica, é sobre responder perguntas de capacidade:
- Quantos usuários simultâneos o sistema aguenta antes de degradar?
- O que acontece quando o tráfego triplica de repente?
- O sistema se recupera depois de um pico, ou fica degradado?
- Rodando por horas, alguma coisa vaza memória ou conexão?
- Qual é o teto absoluto antes de quebrar de vez?
Cada uma dessas perguntas tem um tipo de teste específico pra respondê-la. Não existe “o” teste de carga: existe uma família de testes, cada um com um objetivo diferente.
Por que k6
k6 é uma ferramenta de teste de carga open-source da Grafana Labs, feita pra ser confortável pra quem já programa. Os scripts são JavaScript puro, o binário é uma única ferramenta de linha de comando (escrita em Go, então roda leve), e ela se integra nativamente com o ecossistema Grafana pra visualização de métricas.
Pontos que fazem diferença no dia a dia:
- Script como código: o teste é um arquivo
.jsversionado junto com o resto do projeto, não uma configuração de GUI. - Métricas nativas: latência, taxa de erro, throughput. Tudo já vem medido, sem instrumentar nada.
- Thresholds: você define limites (“p95 abaixo de 500ms”, “taxa de erro abaixo de 1%”) e o k6 falha o teste automaticamente se passar do limite. Dá pra plugar isso em CI.
- Dashboard: o k6 tem um web dashboard embutido pra acompanhar as métricas em tempo real. É o que aparece nas imagens abaixo.
Um script mínimo:
import http from 'k6/http';
export default () => {
http.get('http://localhost:1234');
};
Isso já é um teste válido. O que muda entre os tipos de teste é a seção options: quantos usuários virtuais (VUs), por quanto tempo, e em que padrão.
Os tipos de teste de carga
A documentação oficial do k6 organiza os testes de performance em seis categorias. Cada uma responde uma pergunta diferente.
Smoke Test
Pergunta: o script funciona e o sistema responde numa carga mínima?
Carga: 1 usuário. Duração: ~1 minuto.
É o primeiro teste que você roda, sempre. Não é sobre carga, é sobre validação: confirma que o script está correto, os endpoints respondem, os thresholds estão configurados direito. Roda a cada mudança de código ou de script, antes de qualquer teste mais pesado.
import http from 'k6/http';
import { check } from 'k6';
export const options = {
vus: 3,
duration: '1m',
};
export default function () {
const res = http.get('http://localhost:1234');
check(res, {
'status code 200': (r) => r.status === 200,
});
}
Load Test (Average-Load)
Pergunta: o sistema aguenta a carga esperada do dia a dia?
Carga: volume equivalente à produção (no exemplo abaixo, 100 usuários simultâneos). Duração: 5 a 60 minutos.
É o teste de referência, o que você roda com regularidade pra garantir que o desempenho continua estável nas condições normais de uso. Geralmente segue um padrão de três fases: ramp-up (sobe gradual até a carga alvo), sustentação (mantém a carga), ramp-down (desce gradual).
export const options = {
stages: [
{ duration: '5m', target: 100 }, // ramp-up: 1 -> 100 usuários
{ duration: '30m', target: 100 }, // sustenta 100 usuários
{ duration: '5m', target: 0 }, // ramp-down: volta a 0
],
};
Stress Test
Pergunta: o que acontece quando a carga passa do esperado?
Carga: acima do normal, crescente (no exemplo, 100 a 200 usuários progressivamente). Duração: 5 a 60 minutos.
Aqui o objetivo é forçar o sistema além da zona de conforto pra encontrar o ponto onde ele degrada: latência sobe, erros aparecem, throughput cai. Roda antes de períodos de demanda esperada acima da média: Black Friday, lançamento de campanha, etc.
export const options = {
stages: [
{ duration: '1m', target: 200 }, // ramp-up: 1 -> 200 usuários
{ duration: '30m', target: 200 }, // sustenta 200 usuários
{ duration: '5m', target: 0 }, // ramp-down: volta a 0
],
};
Spike Test
Pergunta: o sistema sobrevive a um pico repentino, e se recupera depois?
Carga: salto abrupto e muito alto (no exemplo, de 100 pra 2000 usuários em 30 segundos). Duração: poucos minutos.
Diferente do stress test, aqui não importa o caminho gradual: importa a transição brusca. Simula o efeito de ficar em destaque de repente, um link viral, uma campanha que estourou. O que se observa não é só se o sistema aguenta o pico, mas se ele volta ao normal depois que o pico passa.
export const options = {
stages: [
{ duration: '2m', target: 2000 }, // salto rápido a 2000 usuários, sem plateau
{ duration: '1m', target: 0 }, // ramp-down rápido de volta a 0
],
};
Soak Test (Endurance)
Pergunta: o sistema se mantém estável ao longo de horas?
Carga: volume normal e constante (no exemplo, 100 usuários). Duração: horas (no exemplo, 8 horas).
É o único teste onde a variável principal é tempo, não volume. Vazamento de memória, conexões de banco que não fecham direito, cache que cresce sem limite: esse tipo de problema só aparece depois de rodar por um bom tempo. Um teste de 10 minutos não pega isso.
export const options = {
stages: [
{ duration: '5m', target: 100 }, // ramp-up: 1 -> 100 usuários
{ duration: '8h', target: 100 }, // sustenta 100 usuários por 8 horas
{ duration: '5m', target: 0 }, // ramp-down: volta a 0
],
};
Breakpoint Test
Pergunta: qual é o limite absoluto do sistema antes de quebrar?
Carga: crescente sem teto definido, até a falha (no exemplo, até 20.000 usuários). Duração: variável, até algumas horas.
É o teste mais agressivo da lista. Não tem meta de carga: sobe até o sistema quebrar de vez, e o ponto de quebra é o resultado. Faz sentido rodar depois de mudança grande de infraestrutura, ou pra saber, de fato, qual é o teto de capacidade atual.
export const options = {
executor: 'ramping-arrival-rate',
stages: [
{ duration: '2h', target: 20000 }, // sobe até 20.000 requisições/s em 2h
],
};
Repara que aqui muda o executor pra ramping-arrival-rate: em vez de controlar número de usuários virtuais (VUs), controla diretamente a taxa de requisições por segundo, deixando o k6 alocar quantos VUs forem necessários pra sustentar essa taxa.
Resumo
| Tipo | Carga | Duração | Frequência | Pergunta que responde |
|---|---|---|---|---|
| Smoke | Mínima | < 1 min | A cada mudança | O script funciona? |
| Load | Normal | 5-60 min | Regular | Aguenta o dia a dia? |
| Stress | Alta, crescente | 5-60 min | Antes de picos esperados | Onde degrada? |
| Spike | Muito alta, súbita | Minutos | Antes de eventos | Sobrevive e se recupera? |
| Soak | Normal, prolongada | Horas | Periódica | Fica estável com o tempo? |
| Breakpoint | Crescente, sem teto | Variável | Ocasional | Qual o limite absoluto? |
O projeto: grafana-k6
Montei um repositório com os seis tipos de teste prontos pra rodar, mais exemplos de funcionalidades específicas do k6 (checks, groups, métricas customizadas, thresholds, tags). Tudo sobe com um único docker compose up.
Estrutura
grafana-k6/
├── docker-compose.yaml
├── go-server/ # aplicação alvo, em Go
│ ├── Dockerfile
│ └── main.go
└── k6/
├── example/ # exemplos de funcionalidades do k6
│ ├── check.js
│ ├── group.js
│ ├── metrics.js
│ ├── request.js
│ ├── tags.js
│ └── thresholds.js
├── scenarios/ # padrões de carga (executors)
│ ├── constant-arrival-rate.js
│ ├── constant-vus.js
│ ├── per-vu-iterations.js
│ └── shared-iterations.js
└── test-types/ # os seis tipos de teste
├── smoke_test.js
├── load_test.js
├── stress_test.js
├── spike_test.js
├── soak_test.js
└── breakpoint_test.js
O alvo dos testes é um servidor Go minimalista: só precisa responder HTTP, não precisa fazer nada de especial. Em qualquer projeto real você aponta os scripts pro seu próprio serviço.
Rodando
# sobe o ambiente
docker compose up -d
# roda o teste desejado
docker exec -it k6_run k6 run /scripts/test-types/smoke_test.js
Substitui smoke_test.js por qualquer outro arquivo de test-types/: todos seguem o mesmo padrão de execução. Enquanto o teste roda, o dashboard fica disponível em http://localhost:5665.
O dashboard em tempo real
Essa é a tela que abre em localhost:5665 durante a execução: taxa de requisição, duração, taxa de falha, VUs ativos e taxa de transferência, atualizando a cada poucos segundos:

A aba Timings quebra a duração da requisição HTTP em fases. É onde se acha onde o tempo está sendo gasto, não só quanto: Request Duration (avg/p90/p95/p99), Request Failed Rate, Request Rate, Request Waiting (TTFB), além de TLS handshaking e Request Sending mais abaixo. Numa investigação de lentidão, é essa aba que diz se o gargalo é conexão, espera do servidor, ou envio de dados:

Na aba Summary, depois que o teste termina (ou em qualquer ponto durante a execução), aparece o resumo estatístico completo: percentis (p90, p95, p99) de cada métrica de tempo, contadores totais de dados trafegados e requisições, e os gauges de VUs:

Além dos tipos de teste: recursos que valem conhecer
O repositório também tem exemplos isolados de features do k6 que aparecem em qualquer script real, independente do tipo de teste:
thresholds.js: define critérios de sucesso/falha automáticos (ex:http_req_duration: ['p(95)<500']). Sem isso, teste de carga vira só “olhar gráfico e achar que está bom”.check.js: assertions dentro do teste (check(res, { 'status 200': (r) => r.status === 200 })), sem parar a execução quando falham, diferente de umasserttradicional.group.js: agrupa requisições relacionadas pra organizar métricas por fluxo (ex: “login”, “checkout”).tags.js: etiqueta requisições e métricas pra filtrar depois, útil quando o mesmo script cobre múltiplos endpoints.metrics.js: métricas customizadas (Counter,Trend,Gauge,Rate) além das que o k6 já coleta por padrão.
E na pasta scenarios/, os diferentes executors: constant-vus, per-vu-iterations, shared-iterations, constant-arrival-rate, que controlam como a carga é gerada (número fixo de VUs vs. taxa fixa de requisições por segundo, entre outras estratégias). Vale explorar quando o padrão simples de stages não é suficiente pro cenário que você precisa simular.
Fechando
Teste de carga não é uma etapa a mais no checklist antes de ir pra produção: é a única forma confiável de responder “isso aguenta?” antes que o tráfego real responda por você. k6 torna essa prática acessível: script em JavaScript, execução via linha de comando, métricas prontas, dashboard sem esforço extra.
Começa pequeno: um smoke test já revela se o script e o alvo estão configurados direito. Depois disso, o tipo de teste que você precisa depende da pergunta que você está tentando responder, e agora você tem os seis principais mapeados.
O projeto completo, com os seis tipos de teste prontos pra rodar via Docker Compose, está em github.com/wander4747/grafana-k6.