Parte de nuestra serie Performance & Scalability
Leer la guía completaPrueba de carga k6: pruebe sus API antes del lanzamiento
Enviar un producto sin pruebas de carga es una apuesta. No conoce su punto de ruptura hasta que los usuarios lo encuentran por usted, generalmente durante el lanzamiento de un producto, un momento viral o un pico de ventas. k6 es la herramienta de prueba de carga moderna que le permite escribir pruebas en JavaScript, ejecutarlas desde CI y descubrir su límite de rendimiento antes de que lo hagan los usuarios. Es fácil de desarrollar, eficiente en recursos (k6 usa rutinas, no subprocesos) y se integra limpiamente con Grafana y Prometheus para obtener métricas en tiempo real.
Esta guía cubre k6 desde el primer script hasta pruebas de carga complejas de múltiples escenarios, métricas personalizadas, umbrales, integración de CI y patrones de prueba seguros para producción para las API de Node.js/NestJS.
Conclusiones clave
- Los scripts de k6 son JavaScript pero se ejecutan en un tiempo de ejecución de Go, sin API de Node.js (sin
require, sinfs, sinsetTimeout)- Los Usuarios Virtuales (VU) son usuarios simulados concurrentes; Las iteraciones son ejecuciones de scripts individuales.
- Establezca siempre umbrales antes de ejecutar: los umbrales fallidos detienen la prueba y fallan el CI
- Utilice escenarios (vus constante, vus en rampa, tasa de llegada constante) para modelar patrones de tráfico reales
- Nunca cargue la producción de prueba sin coordinarse con operaciones: pruebe contra la puesta en escena o un clon de producción
- La métrica
http_req_durationes su métrica principal de SLA: p95 y p99 importan más que los promedios- Limite la velocidad de sus corredores k6: el tráfico de prueba de carga no debe agotar su presupuesto de límite de velocidad
- Utilice k6 Cloud o Grafana k6 para pruebas de carga distribuida en regiones geográficas
Instalación y primer 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
Tu primer 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
}
Ejecútelo:
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
Prueba de carga de API con autenticación
// 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)}`);
}
Ampliación de escenarios de VU
Modele patrones de tráfico reales con múltiples etapas y escenarios:
// 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'],
},
};
Prueba de rastreo de blog/mapa del sitio
// 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 y umbrales 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);
}
Integración de salida de Grafana
Transmita métricas de k6 a InfluxDB y visualícelas en Grafana durante la prueba:
# 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
Integración de CI
# .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/
Líneas base de desempeño
Establecer líneas de base y alertar sobre regresiones:
| Punto final | p50 | p95 | p99 | Presupuesto erróneo |
|---|---|---|---|---|
GET / (página de inicio) | 120 ms | 400 ms | 800 ms | 0,1% |
| CÓDIGO0 | 50 ms | 150 ms | 300 ms | 0,5% |
| CÓDIGO0 | 80 ms | 200 ms | 400 ms | 0,5% |
| CÓDIGO0 | 200 ms | 500 ms | 1000 ms | 1% |
| CÓDIGO0 | 500 ms | 1500 ms | 3000 ms | 1% |
| CÓDIGO0 | 100 ms | 300 ms | 600 ms | 0,1% |
Preguntas frecuentes
¿Cuál es la diferencia entre VU y solicitudes por segundo?
Los VU (usuarios virtuales) son usuarios simulados concurrentes. Cada VU ejecuta su función predeterminada en un bucle. Las solicitudes por segundo dependen de la velocidad con la que se ejecuta cada iteración. Si cada iteración de VU dura 2 segundos (incluido el tiempo de suspensión) y tienes 100 VU, obtienes aproximadamente 50 RPS. Utilice el ejecutor constant-arrival-rate cuando necesite apuntar a un RPS específico independientemente de la duración de la iteración.
¿Debo cargar la prueba en producción o en preparación?
Prefiere siempre la puesta en escena. Las pruebas de carga de producción corren el riesgo de afectar a usuarios reales, agotar los límites de velocidad, crear pedidos reales a partir de datos de prueba y desencadenar eventos de webhook reales. Si debe probar la producción, hágalo durante períodos de poco tráfico, utilice tokens de pago de prueba y tenga un plan de reversión. k6 Cloud admite la generación de carga distribuida geográficamente para que pueda probar el rendimiento del borde de su CDN sin tocar el origen.
¿Cómo manejo la caducidad del token de autenticación durante pruebas largas?
Para pruebas cortas (menos de 15 minutos), obtenga un token en setup() y páselo a todas las VU a través de data. Para pruebas largas (más de 15 minutos), implemente la actualización del token en el script de prueba: verifique la antigüedad del token en la función predeterminada y actualice cuando se acerque a su vencimiento. Almacene el token en una variable JavaScript local para cada VU.
¿Cuál es la diferencia entre stages y scenarios?
stages es una abreviatura de un único escenario ramping-vus, bueno para patrones simples de aceleración/mantenimiento/desaceleración. scenarios le brinda control total: múltiples patrones de tráfico simultáneos, diferentes ejecutores por escenario, umbrales por escenario y etiquetado de escenarios en métricas. Utilice scenarios para pruebas realistas de múltiples patrones (línea de base + pico + remojo simultáneamente).
¿Cuántas VU puede manejar un solo proceso k6?
Un solo proceso k6 en hardware moderno puede soportar entre 5000 y 10 000 VU y generar entre 50 000 y 100 000 RPS, dependiendo de la complejidad del script y el tamaño de la respuesta. Para cargas más altas, use k6 cloud o ejecute varias instancias k6 detrás de un distribuidor de carga. Cada VU es extremadamente liviana (una gorutina): k6 usa muchos menos recursos que JMeter o Gatling para recuentos de VU equivalentes.
Próximos pasos
Las pruebas de carga revelan el verdadero perfil de rendimiento de su aplicación antes de que los usuarios la descubran bajo estrés. Los patrones de esta guía (escenarios de aceleración, umbrales personalizados, integración de CI y paneles de Grafana) le brindan la infraestructura para encontrar y solucionar cuellos de botella de rendimiento de forma continua.
ECOSIRE crea API NestJS con rendimiento validado con pruebas de carga k6 que cubren la página de inicio, puntos finales de API, flujos de pago y rastreos completos de mapas del sitio. Explore nuestros servicios de ingeniería backend para saber cómo construimos para lograr rendimiento desde el primer día.
Escrito por
ECOSIRE Research and Development Team
Construyendo productos digitales de nivel empresarial en ECOSIRE. Compartiendo perspectivas sobre integraciones Odoo, automatización de eCommerce y soluciones empresariales impulsadas por IA.
Artículos relacionados
API Rate Limiting: Patterns and Best Practices
Master API rate limiting with token bucket, sliding window, and fixed counter patterns. Protect your backend with NestJS throttler, Redis, and real-world configuration examples.
GoHighLevel API and Webhooks: Custom Integrations
Complete developer guide to GoHighLevel API and webhooks. Build custom integrations, sync data with external systems, and extend GHL capabilities with REST API and webhook automation.
NestJS 11 Enterprise API Patterns
Master NestJS 11 enterprise patterns: guards, interceptors, pipes, multi-tenancy, and production-ready API design for scalable backend systems.
Más de Performance & Scalability
Nginx Production Configuration: SSL, Caching, and Security
Nginx production configuration guide: SSL termination, HTTP/2, caching headers, security headers, rate limiting, reverse proxy setup, and Cloudflare integration patterns.
Odoo Performance Tuning: PostgreSQL and Server Optimization
Expert guide to Odoo 19 performance tuning. Covers PostgreSQL configuration, indexing, query optimization, Nginx caching, and server sizing for enterprise deployments.
Odoo vs Acumatica: Cloud ERP for Growing Businesses
Odoo vs Acumatica compared for 2026: unique pricing models, scalability, manufacturing depth, and which cloud ERP fits your growth trajectory.
Testing and Monitoring AI Agents in Production
A complete guide to testing and monitoring AI agents in production environments. Covers evaluation frameworks, observability, drift detection, and incident response for OpenClaw deployments.
Compliance Monitoring Agents with OpenClaw
Deploy OpenClaw AI agents for continuous compliance monitoring. Automate regulatory checks, policy enforcement, audit trail generation, and compliance reporting.
Optimizing AI Agent Costs: Token Usage and Caching
Practical strategies for reducing AI agent operational costs through token optimization, caching, model routing, and usage monitoring. Real savings from production OpenClaw deployments.