Guia: como planejar e executar testes de carga em aplicações PHP monolito usando k6 e Docker Compose — passo a passo

Por · Publicado em · 9 min de leitura

Tela de laptop com código desfocado e reflexo de um bichinho de pelúcia
Laptop com código desfocado e pelúcia — visual técnico sem dados legíveis. Foto: Daniil Komov / Pexels

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 carga

Veja também