Guia: como planejar e executar testes de carga em aplicações PHP monolito usando k6 e Docker Compose — passo a passo
Por Kevin Marcondes · Publicado em · 9 min de leitura
Guia: como planejar e executar testes de carga em aplicações PHP monolito usando k6 e Docker Compose. Você vai definir objetivos e SLOs, modelar jornadas e RPS, montar um ambiente isolado com Compose, escrever scripts k6 com checks e thresholds e analisar resultados para priorizar correções e automatizar execuções.
Por que teste de carga em monólitos PHP exige cuidado
Em monólitos PHP, o gargalo raramente é único. PHP‑FPM, banco de dados, cache e I/O competem por recursos. O teste precisa estressar o fluxo completo, não só uma rota. Meça p95 e taxa de erros, mas observe também saturação de workers e filas internas.
O PHP‑FPM tem fila por processo. Se o pool está curto, você verá latência alta e timeouts. Se está longo, pode faltar memória e começar swap. Planeje hipóteses de falhas e sinais de saturação para orientar o teste.
Monitore web server, PHP‑FPM e banco durante o teste. Ter métricas de base evita diagnósticos errados após um spike de latência. Veja como expor métricas do PHP‑FPM para Prometheus.
- Atenção ao pool do PHP‑FPM e conexões de banco.
- Evite teste só de GET estático; cubra autenticação e escrita.
- Colete métricas antes, durante e depois do teste.
Planejamento: objetivos, riscos e critérios de sucesso (SLOs)
Defina o objetivo do teste antes do primeiro script. Procure critérios observáveis que guiem decisões. Sem um alvo, qualquer resultado parece aceitável, mas não orienta melhorias. Foque em fluxos críticos de negócio e eventos sazonais.
Crie SLOs simples e verificáveis. Não prometa números fechados; use faixas e metas realistas, considerando carga histórica e picos previstos.
- Escopo: rotas, operações de leitura/escrita e integrações.
- Sinais: p95 < alvo, erro HTTP < x%, throughput mínimo.
- Riscos: quedas de cache, migrações, lock de tabelas, GC do PHP.
- Duração: warm‑up, fase estável, ramp‑down.
- Saída: relatório com métricas e próximas ações.
Modelagem de carga realista: jornadas, RPS e VUs
Traduza comportamento de usuários em cenários. Evite distribuir tráfego igualmente entre rotas. Use mix ponderado e inclua tempos de think time. Em e‑commerce, busca e navegação dominam; checkout é minoritário, porém crítico.
Escolha entre modelar por usuários virtuais (VUs) ou por taxa de chegada (RPS). Para backends, ramping‑arrival‑rate tende a representar melhor acessos concorrentes imprevisíveis.
- Identifique jornadas: login, navegação, busca, checkout.
- Estime mix: 70% leitura, 25% navegação, 5% escrita sensível.
- Aplique think time para evitar loop irreal.
- Inclua aquecimento para estabilizar caches e JITs.
- Varie payloads e parâmetros para cobrir cache misses.
Ambiente de teste com Docker Compose: isolamento e reprodutibilidade
Use Docker Compose para isolar a aplicação e reproduzir testes. Configure rede dedicada, variáveis de ambiente e dependências. Evite compartilhar banco de dados com desenvolvimento para não contaminar dados.
Para limitar CPU, memória e I/O do host, complemente com cgroup v2 e systemd quando necessário. Isso simula máquinas menores e revela degradação cedo. Veja o tutorial de cgroup v2 e systemd ResourceControl.
- Separe a rede do Compose para DNS estável (app → http://app:8080).
- Replique cache e banco em containers próprios.
- Versione docker‑compose.yml junto dos scripts k6.
- Evite dados sensíveis; prefira fixtures dedicadas.
version: "3.9"
services:
app:
image: nginx:alpine
ports:
- "8080:8080"
volumes:
- ./app:/var/www/html:ro
depends_on:
- php
- db
- cache
php:
image: php:8.2-fpm
volumes:
- ./app:/var/www/html:ro
db:
image: postgres:16
environment:
- POSTGRES_DB=app
- POSTGRES_USER=app
- POSTGRES_PASSWORD=app
volumes:
- db:/var/lib/postgresql/data
cache:
image: redis:7-alpine
k6:
image: grafana/k6:latest
volumes:
- ./k6:/scripts:ro
depends_on:
- app
command: ["run", "/scripts/script.js"]
volumes:
db: {}
Escrevendo scripts com k6: cenários, checks, thresholds
Estruture o script em módulos por jornada. Use tags para diferenciar transações e rotas. Checks validam respostas; thresholds falham o teste quando metas são violadas. Prefira ramping‑arrival‑rate para controlar RPS e mantenha think time explícito.
Parametrize a BASE_URL via variável de ambiente. Assim, você muda o alvo sem alterar o código. Use datasets em JSON para variedade de entradas e cubra cache misses de forma previsível.
- Use tags: { journey: "checkout", route: "/orders" }.
- Aplique checks para status e conteúdo chave.
- Defina thresholds em p95/p99 e taxa de erros.
- Separar cenários por jornada facilita análise.
import http from 'k6/http';
import { check, sleep } from 'k6';
export const options = {
scenarios: {
browse: {
executor: 'ramping-arrival-rate',
startRate: 10, timeUnit: '1s',
preAllocatedVUs: 50, maxVUs: 200,
stages: [
{ target: 50, duration: '2m' },
{ target: 100, duration: '5m' },
{ target: 0, duration: '1m' }
],
exec: 'browse',
},
checkout: {
executor: 'constant-vus', vus: 20, duration: '5m',
exec: 'checkout', startTime: '2m'
}
},
thresholds: {
http_req_duration: ['p(95)<400'],
http_req_failed: ['rate<0.02']
}
};
const BASE_URL = __ENV.BASE_URL || 'http://app:8080';
export function browse() {
const res = http.get(`${BASE_URL}/products?query=test`);
check(res, { 'status 200': (r) => r.status === 200 });
sleep(1);
}
export function checkout() {
const login = http.post(`${BASE_URL}/login`, { u: 'user', p: 'pass' });
check(login, { 'login ok': (r) => r.status === 200 });
const order = http.post(`${BASE_URL}/orders`, JSON.stringify({ sku: 'ABC', qty: 1 }), { headers: { 'Content-Type': 'application/json' }});
check(order, { 'order 201': (r) => r.status === 201 });
sleep(0.5);
}
Dados, autenticação e logs: sem isso, o teste engana
Sem dados realistas, você mede o cache feliz, não o sistema real. Prepare fixtures, usuários e tokens com expiração adequada. Gere payloads variados para exercitar índices e cardinalidade no banco. Registre correlações para unir requisições do k6 a logs do servidor.
Padronize um header de correlação e inclua o ID em todas as requisições do k6. Isso acelera o diagnóstico. Para rastrear ponta a ponta, implemente tracing distribuído com OpenTelemetry no PHP.
- Crie usuários e dados sintéticos representativos.
- Renove tokens para evitar falhas falsas por expiração.
- Inclua X‑Request‑ID em cada chamada.
- Parametrizações por ambiente via .env.
Execução, observabilidade e automação no CI
Execute o k6 com Docker Compose e mantenha tudo versionado. Durante o teste, acompanhe CPU, memória, I/O, conexões e filas. Planeje warm‑up, patamar estável e ramp‑down. Exporte o sumário em JSON para anexar no relatório e comparar rodadas.
Colete métricas de infraestrutura e aplicação em paralelo. Use janelas de tempo para relacionar picos de chegada com latência. Veja como configurar Prometheus e Grafana via Docker Compose.
Automatize: rode cenários rápidos a cada PR e cenários longos em jobs noturnos. Guarde artefatos por execução. Isso evita regressões e completa o fluxo descrito em guia: como planejar e executar testes de carga em aplicações PHP monolito usando k6 e Docker Compose.
- Acompanhe p95/p99, throughput e erro por rota.
- Observe saturação de PHP‑FPM e conexões de DB.
- Salve sumário do k6 e dashboards como evidência.
docker compose run --rm \
-e BASE_URL=http://app:8080 \
-v "$PWD/results:/results" \
k6 run /scripts/script.js --summary-export=/results/summary.json
Interpretando resultados: do sintoma à ação
Traduza métricas em hipóteses e próximas ações. Foque no que derruba disponibilidade: erros do servidor e latências extremas. Depois, trate hotspots que limitam throughput. Valide mudanças com um novo ciclo curto de teste, repetindo o mesmo cenário.
Aprofunde diagnósticos quando houver picos irregulares de latência. O artigo sobre diagnóstico de spikes de latência em APIs PHP pode ajudar na leitura de sintomas comuns.
| Métrica | Sinal observado | Possíveis ações |
|---|---|---|
| p95 http_req_duration alto | Fila no PHP‑FPM ou DB lento | Aumentar workers, otimizar queries, introduzir cache por rota |
| http_req_failed > 2% | Erros 5xx em picos | Circuit‑breaker, limites por cliente, revisar timeouts |
| CPU do app > 85% sustentado | Saturação de CPU | Otimizar código, JIT, opcache, dimensionar CPU |
| Espera em DB alta | Locks e scans custosos | Índices, plano de consultas, reduzir N+1 |
| Baixo cache hit ratio | Latência por misses | Chaves eficientes, TTLs e warming de cache |
Perguntas frequentes
Quantos usuários virtuais (VUs) devo usar no k6?
Depende do objetivo. Para simular taxa de chegada, prefira executores por RPS, como ramping‑arrival‑rate, ajustando a taxa e o número máximo de VUs conforme sua meta. Para cenários por sessão, VUs constantes funcionam.
Devo testar no ambiente de produção?
Evite rodar testes de carga em produção. Eles disputam recursos com usuários reais e podem causar incidentes. Prefira um ambiente dedicado e representativo, com dados sintéticos e limites de recursos semelhantes. Se for inevitável, limite a taxa.
Qual é a diferença entre load, stress e soak test?
Load testa metas realistas de tráfego. Stress empurra além do normal para achar o ponto de ruptura e recuperação. Soak mantém carga prolongada para revelar vazamentos de memória, filas crescentes e efeitos de longo prazo.
Como escolher metas de p95 e throughput?
Use histórico e expectativas de negócio como referência. Defina faixas por jornada crítica e considere dependências externas. Estabeleça metas graduais e revise após mudanças arquiteturais. Thresholds devem refletir a realidade do seu sistema.
Posso rodar k6 via Docker Compose no CI?
Sim. Versione docker‑compose.yml e scripts k6, e rode no job do CI. Exporte o sumário para análise e faça o job falhar se thresholds forem violados. Use variáveis de ambiente para apontar alvos de teste e parametrizar dados.
Como gerar dados realistas para o teste?
Crie fixtures e datasets que cubram variedade de parâmetros, tamanhos e combinações. Gere usuários e pedidos sintéticos, com chaves, índices e padrões de acesso próximos do real. Evite dados sensíveis. Renove tokens e limpe estados quando necessário.
Quer estruturar seus testes de carga com k6 e Docker Compose?
Se você precisa revisar a modelagem, montar o ambiente e automatizar execuções, vamos conversar sobre o próximo passo para o seu cenário.
Falar sobre testes de cargaVeja também
- Guia prático: como configurar failover ativo-passivo entre duas VPS com Keepalived, HAProxy e lsyncd para uploads sincronizados
- Guia: checklist técnico para preparar VPS Debian com Docker Compose para aplicações PHP em produção
- Guia prático para dimensionamento de VPS e tuning de PHP‑FPM para lojas Magento/OpenMage com picos sazonais: do sizing ao teste de carga
- guia prático: reconstruir índices no PostgreSQL em produção usando CREATE INDEX CONCURRENTLY e índices parciais (passo a passo seguro)