Pular para o conteúdo principal

Teste de carga k6: teste de resistência de suas APIs antes do lançamento

Domine o teste de carga k6 para APIs Node.js. Abrange aumentos de usuários virtuais, limites, cenários, HTTP/2, testes WebSocket, painéis Grafana e padrões de integração de CI.

E
ECOSIRE Research and Development Team
|19 de março de 202611 min de leitura2.1k Palavras|

Enviar um produto sem teste de carga é uma aposta. Você não sabe qual é o seu ponto de ruptura até que os usuários o encontrem para você – geralmente durante o lançamento de um produto, um momento viral ou um pico de vendas.

Parte da nossa série Desempenho e escalabilidade

Leia o guia completo

Enviar um produto sem teste de carga é uma aposta. Você não sabe qual é o seu ponto de ruptura até que os usuários o encontrem para você – geralmente durante o lançamento de um produto, um momento viral ou um pico de vendas. k6 é a ferramenta moderna de teste de carga que permite escrever testes em JavaScript, executá-los a partir de CI e descobrir seu limite de desempenho antes dos usuários. É amigável ao desenvolvedor, eficiente em termos de recursos (k6 usa goroutines, não threads) e integra-se perfeitamente com Grafana e Prometheus para métricas em tempo real.

Este guia cobre o k6 desde o primeiro script até testes de carga complexos em vários cenários, métricas personalizadas, limites, integração de CI e padrões de teste seguros para produção para APIs Node.js/NestJS.

Principais conclusões

  • Os scripts k6 são JavaScript, mas são executados em um tempo de execução Go — sem APIs Node.js (sem require, sem fs, sem setTimeout)
  • Usuários Virtuais (VUs) são usuários simulados simultâneos; iterações são execuções de script individuais
  • Sempre defina limites antes de executar — limites com falha interrompem o teste e falham no CI
  • Usar cenários (vus constante, vus em rampa, taxa de chegada constante) para modelar padrões de tráfego reais
  • Nunca carregue a produção de teste sem coordenação com as operações — teste em relação à preparação ou a um clone de produção
  • A métrica http_req_duration é sua principal métrica de SLA — p95 e p99 são mais importantes que as médias
  • Limite de taxa para seus corredores k6 – o tráfego de teste de carga não deve esgotar seu orçamento de limite de taxa
  • Use k6 Cloud ou Grafana k6 para testes de carga distribuídos em regiões geográficas

Instalação e primeiro script

# macOS
brew install k6

# Ubuntu/Debian
sudo gpg -k && sudo gpg --no-default-keyring --keyring /usr/share/keyrings/k6-archive-keyring.gpg --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys C5AD17C747E3415A3642D57D77C6C491D6AC1D69
echo "deb [signed-by=/usr/share/keyrings/k6-archive-keyring.gpg] https://dl.k6.io/deb stable main" | sudo tee /etc/apt/sources.list.d/k6.list
sudo apt-get update && sudo apt-get install k6

# Windows (via Chocolatey)
choco install k6

Seu primeiro script k6:

// k6/homepage-load.js
import http from 'k6/http';
import { check, sleep } from 'k6';
import { Rate } from 'k6/metrics';

// Custom metric: error rate
const errorRate = new Rate('error_rate');

export const options = {
  // Ramp up to 50 VUs over 1 minute, hold 2 minutes, ramp down
  stages: [
    { duration: '1m', target: 50 },
    { duration: '2m', target: 50 },
    { duration: '1m', target: 0 },
  ],
  thresholds: {
    http_req_duration: ['p(95)<500', 'p(99)<1000'], // 95th pct < 500ms
    http_req_failed:   ['rate<0.01'],                // Less than 1% errors
    error_rate:        ['rate<0.01'],
  },
};

const BASE_URL = __ENV.BASE_URL || 'http://localhost:3000';

export default function () {
  const res = http.get(`${BASE_URL}/`);

  const ok = check(res, {
    'status is 200':              (r) => r.status === 200,
    'response time < 500ms':     (r) => r.timings.duration < 500,
    'body contains ECOSIRE':     (r) => r.body.includes('ECOSIRE'),
  });

  errorRate.add(!ok);
  sleep(1); // 1 second think time between iterations
}

Execute:

k6 run k6/homepage-load.js
k6 run --env BASE_URL=https://staging.ecosire.com k6/homepage-load.js
k6 run --vus 100 --duration 30s k6/homepage-load.js  # Quick smoke test

Teste de carga de API com autenticação

// k6/api-load.js
import http from 'k6/http';
import { check, group, sleep } from 'k6';
import { Counter, Trend } from 'k6/metrics';

const apiCalls = new Counter('api_calls');
const authDuration = new Trend('auth_duration');

export const options = {
  scenarios: {
    // Constant arrival rate: 100 requests/second regardless of VU count
    constant_load: {
      executor: 'constant-arrival-rate',
      rate: 100,
      timeUnit: '1s',
      duration: '3m',
      preAllocatedVUs: 50,
      maxVUs: 200,
    },
  },
  thresholds: {
    http_req_duration:            ['p(95)<300', 'p(99)<500'],
    'http_req_duration{type:api}': ['p(95)<200'],
    http_req_failed:              ['rate<0.005'], // 0.5% error budget
  },
};

const BASE_URL = __ENV.BASE_URL || 'http://localhost:3001';

// setup() runs once before VUs start
export function setup() {
  // Authenticate and return tokens for VUs to use
  const loginRes = http.post(`${BASE_URL}/auth/login`, JSON.stringify({
    email: __ENV.TEST_EMAIL,
    password: __ENV.TEST_PASSWORD,
  }), {
    headers: { 'Content-Type': 'application/json' },
  });

  check(loginRes, { 'login successful': (r) => r.status === 200 });

  const { accessToken } = loginRes.json();
  return { accessToken };
}

export default function (data) {
  const { accessToken } = data;
  const headers = {
    Authorization: `Bearer ${accessToken}`,
    'Content-Type': 'application/json',
  };

  group('Contacts API', () => {
    // List contacts
    const listRes = http.get(`${BASE_URL}/contacts?page=1&limit=20`, {
      headers,
      tags: { type: 'api', endpoint: 'contacts-list' },
    });

    check(listRes, {
      'list contacts: 200': (r) => r.status === 200,
      'list contacts: has items': (r) => r.json('data') !== null,
    });

    apiCalls.add(1);
    sleep(0.5);

    // Create a contact
    const createRes = http.post(`${BASE_URL}/contacts`, JSON.stringify({
      name: `Load Test User ${Date.now()}`,
      email: `load-test-${Date.now()}@example.com`,
    }), {
      headers,
      tags: { type: 'api', endpoint: 'contacts-create' },
    });

    check(createRes, {
      'create contact: 201': (r) => r.status === 201,
    });

    apiCalls.add(1);
  });

  sleep(1);
}

// teardown() runs once after all VUs finish
export function teardown(data) {
  // Clean up test data if needed
  console.log(`Test complete. Data: ${JSON.stringify(data)}`);
}

Cenários VU de aceleração

Modele padrões de tráfego reais com vários estágios e cenários:

// k6/stress-test.js
export const options = {
  scenarios: {
    // Scenario 1: Normal traffic baseline
    normal_traffic: {
      executor: 'ramping-vus',
      startVUs: 0,
      stages: [
        { duration: '2m', target: 20 },
        { duration: '5m', target: 20 },
      ],
      gracefulRampDown: '30s',
    },

    // Scenario 2: Sudden spike (marketing campaign goes live)
    traffic_spike: {
      executor: 'ramping-vus',
      startVUs: 0,
      startTime: '7m', // Starts after baseline is stable
      stages: [
        { duration: '30s', target: 200 }, // Rapid spike
        { duration: '2m',  target: 200 }, // Hold spike
        { duration: '30s', target: 20  }, // Fall back
      ],
    },

    // Scenario 3: Soak test for memory leaks
    soak_test: {
      executor: 'constant-vus',
      vus: 10,
      duration: '30m', // Run for 30 minutes
      startTime: '10m',
    },
  },
  thresholds: {
    'http_req_duration{scenario:normal_traffic}': ['p(95)<300'],
    'http_req_duration{scenario:traffic_spike}':  ['p(95)<800'],
    http_req_failed:                              ['rate<0.02'],
  },
};

Teste de rastreamento de blog/sitemap

// k6/sitemap-crawl.js
import http from 'k6/http';
import { check, sleep } from 'k6';
import { SharedArray } from 'k6/data';

// Load URLs from sitemap (pre-fetched and saved as JSON)
const urls = new SharedArray('sitemap urls', function () {
  // In real usage, fetch sitemap XML and parse URLs beforehand
  // SharedArray is loaded once and shared across all VUs (memory efficient)
  return JSON.parse(open('./sitemap-urls.json'));
});

export const options = {
  vus: 20,
  duration: '5m',
  thresholds: {
    http_req_duration: ['p(95)<2000'], // Public pages can be slower
    http_req_failed:   ['rate<0.01'],
  },
};

export default function () {
  // Each VU picks a random URL from the sitemap
  const url = urls[Math.floor(Math.random() * urls.length)];

  const res = http.get(url, {
    headers: {
      // Identify as load tester in logs
      'User-Agent': 'k6-load-tester/1.0',
    },
    timeout: '10s',
  });

  check(res, {
    'status is 200':           (r) => r.status === 200,
    'no error page':           (r) => !r.body.includes('Error'),
    'has canonical tag':       (r) => r.body.includes('rel="canonical"'),
    'response time < 2000ms': (r) => r.timings.duration < 2000,
  });

  sleep(Math.random() * 2 + 1); // Random 1-3 second think time
}

Métricas e limites personalizados

// k6/checkout-load.js
import http from 'k6/http';
import { check, sleep } from 'k6';
import { Counter, Rate, Trend, Gauge } from 'k6/metrics';

// Custom metrics for business-specific tracking
const checkoutSuccesses = new Counter('checkout_successes');
const checkoutFailures  = new Counter('checkout_failures');
const checkoutDuration  = new Trend('checkout_duration');
const activeCheckouts   = new Gauge('active_checkouts');

export const options = {
  stages: [
    { duration: '1m', target: 10 },
    { duration: '3m', target: 30 },
    { duration: '1m', target: 0 },
  ],
  thresholds: {
    checkout_duration:   ['p(95)<3000'],   // Checkout < 3s at p95
    checkout_failures:   ['count<10'],     // Max 10 absolute failures
    http_req_duration:   ['p(99)<2000'],
  },
};

export default function (data) {
  activeCheckouts.add(1);
  const start = Date.now();

  // Step 1: Add to cart
  const cartRes = http.post('/api/cart/add', JSON.stringify({
    productId: 'prod_odoo_customization',
    quantity: 1,
  }), { headers: { 'Content-Type': 'application/json' } });

  if (!check(cartRes, { 'add to cart: 200': (r) => r.status === 200 })) {
    checkoutFailures.add(1);
    activeCheckouts.add(-1);
    return;
  }

  sleep(2);

  // Step 2: Create checkout session
  const checkoutRes = http.post('/api/billing/checkout', JSON.stringify({
    cartId: cartRes.json('cartId'),
  }), { headers: { 'Content-Type': 'application/json' } });

  const success = check(checkoutRes, {
    'checkout session created': (r) => r.status === 200,
    'has session URL':          (r) => r.json('url') !== null,
  });

  if (success) {
    checkoutSuccesses.add(1);
    checkoutDuration.add(Date.now() - start);
  } else {
    checkoutFailures.add(1);
  }

  activeCheckouts.add(-1);
  sleep(1);
}

Integração de saída Grafana

Transmita métricas k6 para o InfluxDB e visualize no Grafana durante o teste:

# Run with InfluxDB output
k6 run --out influxdb=http://localhost:8086/k6 k6/api-load.js

# Or use the k6 Grafana integration (k6 v0.45+)
k6 run --out experimental-prometheus-rw k6/api-load.js
# docker-compose.monitoring.yml
services:
  influxdb:
    image: influxdb:2.7
    ports: ["8086:8086"]
    environment:
      INFLUXDB_DB: k6
      INFLUXDB_ADMIN_USER: admin
      INFLUXDB_ADMIN_PASSWORD: password

  grafana:
    image: grafana/grafana:latest
    ports: ["3030:3000"]
    environment:
      GF_AUTH_ANONYMOUS_ENABLED: "true"
    volumes:
      - ./grafana/dashboards:/etc/grafana/provisioning/dashboards
      - ./grafana/datasources:/etc/grafana/provisioning/datasources

CI Integration

# .github/workflows/ci.yml (load test section)
load-tests:
  runs-on: ubuntu-latest
  if: github.ref == 'refs/heads/main' # Only on main branch
  needs: [deploy-staging]
  steps:
    - uses: actions/checkout@v4

    - name: Install k6
      run: |
        sudo gpg --no-default-keyring --keyring /usr/share/keyrings/k6-archive-keyring.gpg \
          --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys C5AD17C747E3415A3642D57D77C6C491D6AC1D69
        echo "deb [signed-by=/usr/share/keyrings/k6-archive-keyring.gpg] https://dl.k6.io/deb stable main" \
          | sudo tee /etc/apt/sources.list.d/k6.list
        sudo apt-get update && sudo apt-get install k6

    - name: Run smoke test (30 VUs, 60s)
      run: |
        k6 run --vus 30 --duration 60s \
          --env BASE_URL=${{ secrets.STAGING_URL }} \
          --env TEST_EMAIL=${{ secrets.TEST_EMAIL }} \
          --env TEST_PASSWORD=${{ secrets.TEST_PASSWORD }} \
          apps/api/k6/api-load.js

    - name: Upload k6 results
      uses: actions/upload-artifact@v4
      if: always()
      with:
        name: k6-results
        path: k6-results/

Linhas de base de desempenho

Estabeleça linhas de base e alerte sobre regressões:

Ponto finalp50p95p99Erro no orçamento
GET / (página inicial)120ms400ms800ms0,1%
GET /api/contacts50ms150ms300ms0,5%
POST /api/contacts80ms200ms400ms0,5%
POST /auth/login200ms500ms1000ms1%
POST /billing/checkout500ms1500ms3000ms1%
GET /blog/[slug]100ms300ms600ms0,1%

Perguntas frequentes

Qual ​​é a diferença entre VUs e solicitações por segundo?

VUs (Usuários Virtuais) são usuários simulados simultâneos. Cada VU executa sua função padrão em um loop. As solicitações por segundo dependem da rapidez com que cada iteração é executada. Se cada iteração da VU levar 2 segundos (incluindo o tempo de suspensão) e você tiver 100 VUs, obterá aproximadamente 50 RPS. Use o executor constant-arrival-rate quando precisar direcionar um RPS específico, independentemente da duração da iteração.

Devo carregar o teste em produção ou teste?

Prefira sempre a encenação. Os testes de carga de produção correm o risco de impactar usuários reais, esgotar os limites de taxa, criar pedidos reais a partir de dados de teste e acionar eventos reais de webhook. Se você precisar testar a produção, faça-o durante janelas de baixo tráfego, use tokens de pagamento de teste e tenha um plano de reversão. O k6 Cloud oferece suporte à geração de carga distribuída geograficamente para que você possa testar o desempenho da borda do CDN sem tocar na origem.

Como lidar com a expiração do token de autenticação durante testes longos?

Para testes curtos (menos de 15 minutos), obtenha um token em setup() e passe-o para todas as VUs via data. Para testes longos (mais de 15 minutos), implemente a atualização do token no script de teste: verifique a idade do token na função padrão e atualize quando ele expirar. Armazene o token em uma variável JavaScript local para cada VU.

Qual é a diferença entre stages e scenarios?

stages é uma abreviação para um único cenário ramping-vus – bom para padrões simples de aceleração/manutenção/desaceleração. scenarios oferece controle total: vários padrões de tráfego simultâneos, diferentes executores por cenário, limites por cenário e marcação de cenário em métricas. Use scenarios para testes realistas de vários padrões (linha de base + pico + absorção simultaneamente).

Quantas VUs um único processo k6 pode manipular?

Um único processo k6 em hardware moderno pode sustentar de 5.000 a 10.000 VUs e gerar de 50.000 a 100.000 RPS, dependendo da complexidade do script e do tamanho da resposta. Para cargas mais altas, use k6 cloud ou execute várias instâncias k6 atrás de um distribuidor de carga. Cada VU é extremamente leve (uma goroutine) — k6 usa muito menos recursos que JMeter ou Gatling para contagens de VU equivalentes.


Próximas etapas

O teste de carga revela o verdadeiro perfil de desempenho do seu aplicativo antes que os usuários o descubram sob estresse. Os padrões neste guia (cenários de rampa, limites personalizados, integração de CI e painéis Grafana) fornecem a infraestrutura para encontrar e corrigir gargalos de desempenho continuamente.

ECOSIRE cria APIs NestJS com desempenho validado com testes de carga k6 cobrindo página inicial, endpoints de API, fluxos de checkout e rastreamentos completos de mapas de sites. Explore nossos serviços de engenharia de back-end para saber como construímos para obter desempenho desde o primeiro dia.

E

Escrito por

ECOSIRE Research and Development Team

Redação técnica

A equipe de redação técnica da ECOSIRE escreve sobre Odoo ERP, e-commerce com Shopify, agentes de IA, análise de dados com Power BI, automação com GoHighLevel e boas práticas de software empresarial. Nossos guias ajudam as empresas a tomar decisões de tecnologia bem fundamentadas.

ECOSIRE

Expanda o seu negócio com ECOSIRE

Soluções empresariais em ERP, comércio eletrônico, IA, análise e automação.

Mais de Desempenho e escalabilidade

Depuração e monitoramento de webhook: o guia completo para solução de problemas

Domine a depuração de webhook com este guia completo que cobre padrões de falha, ferramentas de depuração, estratégias de repetição, painéis de monitoramento e práticas recomendadas de segurança.

Configuração de produção Nginx: SSL, cache e segurança

Guia de configuração de produção Nginx: terminação SSL, HTTP/2, cabeçalhos de cache, cabeçalhos de segurança, limitação de taxa, configuração de proxy reverso e padrões de integração Cloudflare.

Ajuste de desempenho Odoo: PostgreSQL e otimização de servidor

Guia especializado para ajuste de desempenho do Odoo 19. Abrange configuração do PostgreSQL, indexação, otimização de consultas, cache Nginx e dimensionamento de servidores para implantações corporativas.

Teste e monitoramento de agentes de IA em produção

Um guia completo para testar e monitorar agentes de IA em ambientes de produção. Abrange estruturas de avaliação, observabilidade, detecção de desvios e resposta a incidentes para implantações OpenClaw.

Agentes de monitoramento de conformidade com OpenClaw

Implante agentes OpenClaw AI para monitoramento contínuo de conformidade. Automatize verificações regulatórias, aplicação de políticas, geração de trilhas de auditoria e relatórios de conformidade.

Otimizando os custos do agente de IA: uso de token e armazenamento em cache

Estratégias práticas para reduzir os custos operacionais do agente de IA por meio de otimização de tokens, armazenamento em cache, roteamento de modelo e monitoramento de uso. Economias reais com implantações de produção do OpenClaw.