Performance Testing en Node.js con K6
K6 es una herramienta para realizar pruebas de rendimiento, su uso principal esta en realizar pruebas de carga, estrés y rendimiento. Esto ayuda a identificar cuellos de botella, optimizar la infraestructura y garantizar que un sistema pueda manejar múltiples usuarios simultáneamente sin degradar su rendimiento.
¿Por qué hacemos testing?¶
La razon por la cual se realiza testing, es someter a la app o api a una seria de pruebas ya sean estas de
Testing Funcional¶
- Unit Testing
- Testing de Integración
- End to End Testing
Testing no Funcional¶
¿Cuándo necesitamos performance testing?¶
Tipos de performance testing:¶
Smoke Testing 🔥¶
pruebas rápidas y ligeras que permiten garantizar el correcto funcionamiento del sistema, usado nates de realizar pruebas mas avanzadas
¿Cuándo usarlo?¶
- Antes de ejecutar pruebas de carga o estrés, para detectar problemas básicos.
- Como parte de un pipeline CI/CD, asegurando que la API responde correctamente.
Ejemplo¶
import http from 'k6/http';
export let options = {
vus: 1, // Solo 1 usuario virtual
duration: '10s' // Prueba rápida de 10 segundos
};
export default function () {
let res = http.get('https://test-api.k6.io/public/crocodiles/');
console.log(`Status: ${res.status}`);
}
Stress Testing (Pruebas de Estrés) ⚠️¶
Lleva al limite la capacidad del sistema sometiéndolo a una carga extrema, permite ver cual el limite es decir cuando llega a colapsar
¿Cuándo usarlo?¶
- Para saber cuántos usuarios soporta la aplicación antes de fallar.
- Para identificar cuellos de botella en infraestructura o base de datos.
Ejemplo¶
import http from 'k6/http';
export let options = {
stages: [
{ duration: '30s', target: 50 }, // Aumentar gradualmente a 50 usuarios
{ duration: '1m', target: 200 }, // Llegar a 200 usuarios
{ duration: '30s', target: 300 }, // Poner máxima carga de 300 usuarios
{ duration: '30s', target: 0 }, // Reducir a 0 para ver la recuperación
]
};
export default function () {
http.get('https://test-api.k6.io/public/crocodiles/');
}
Spike Testing¶
Permite evaluar al sistema si este esta listo para manejar picos inesperados de trafico con incrementos repentinos en un periodo de tiempo
¿Cuándo usarlo?¶
- Para probar el efecto de una promoción o evento viral.
- Para simular ataques de tráfico inesperado.
Ejemplo¶
import http from 'k6/http';
export let options = {
stages: [
{ duration: '10s', target: 200 }, // En 10s, subimos de 0 a 200 usuarios
{ duration: '20s', target: 200 }, // Mantenemos la carga por 20s
{ duration: '10s', target: 0 }, // Reducimos repentinamente a 0
]
};
export default function () {
http.get('https://test-api.k6.io/public/crocodiles/');
}
Soak Testing¶
A diferencia de testing anterior que sometía al sistema en una prueba en un periodo corto de tiempo, esta somete al sistema en un periodo de tiempo prolongado.
¿Cuándo usarlo?¶
- Para detectar problemas de fugas de memoria o degradación de rendimiento.
- Para ver si el sistema sigue respondiendo bien después de horas de uso continuo.
Ejemplo¶
import http from 'k6/http';
export let options = {
vus: 50, // Mantener 50 usuarios activos
duration: '2h', // Durante 2 horas
};
export default function () {
http.get('https://test-api.k6.io/public/crocodiles/');
}
Load Testing 🏋️¶
Mide el rendimiento del sistema bajo una carga esperada de usuarios con algunos picos ocasionales.
¿Cuándo usarlo?¶
- Para validar si la aplicación puede manejar la carga esperada sin problemas.
- Para dimensionar los recursos necesarios (CPU, memoria, conexiones de BD).
Ejemplo¶
import http from 'k6/http';
export let options = {
stages: [
{ duration: '5m', target: 100 }, // Subimos a 100 usuarios en 5 minutos
{ duration: '10m', target: 200 }, // Mantenemos 200 usuarios por 10 minutos
{ duration: '2m', target: 50 }, // Bajamos a 50 usuarios
{ duration: '5m', target: 0 }, // Finalizamos la prueba gradualmente
]
};
export default function () {
http.get('https://test-api.k6.io/public/crocodiles/');
}
| Tipo de Test | Objetivo | Usuarios | Duración | Ejemplo de Uso |
|---|---|---|---|---|
| Smoke Testing | Prueba rápida para validar funcionamiento | 🟢 Pocos (1-5) | 🔹 Corta (10-30s) | Pipeline de CI/CD |
| Stress Testing | Evaluar el límite del sistema | 🔴 Máximos posibles | 🔸 Mediana (2-5 min) | Saber cuántos usuarios soporta |
| Spike Testing | Simular picos repentinos de tráfico | ⚡ Incrementos bruscos | 🔹 Corta (1-2 min) | Promociones o tráfico inesperado |
| Soak Testing | Ver estabilidad en largos periodos | 🟠 Media-Alta (50-200) | 🕒 Larga (horas) | Detectar fugas de memoria |
| Load Testing | Validar rendimiento con carga esperada | 🔵 Carga media-alta (100-500) | 🕒 Mediana-Larga (5-15 min) | Saber si la app soporta tráfico regular |
| ## Performance testing en front-end: Lighthouse y WebPageTest | ||||
| Se centra en medir y optimizar la velocidad, interactividad y estabilidad visual de una página web. Dos herramientas clave para esto son Lighthouse y WebPageTest. Vamos a profundizar en cómo funcionan y cómo usarlas. |
| Herramienta | Uso Principal | Datos Clave | Dónde se Ejecuta |
|---|---|---|---|
| Lighthouse | Análisis de rendimiento y optimización | FCP, LCP, CLS, SEO, accesibilidad | DevTools, CLI, PageSpeed Insights |
| WebPageTest | Medición detallada y pruebas en redes/dispositivos reales | TTFB, Speed Index, Waterfall Chart | Web (diferentes ubicaciones) |
| ## Performance testing en back-end: K6 |
Crea tu primer test en K6 e interpreta los resultados¶
-
Creacion
import http from 'k6/http'; import { check, sleep } from 'k6'; export let options = { vus: 10, // 10 usuarios virtuales duration: '30s' // La prueba durará 30 segundos }; export default function () { let res = http.get('https://test-api.k6.io/public/crocodiles/'); check(res, { 'status es 200': (r) => r.status === 200, 'tiempo de respuesta < 200ms': (r) => r.timings.duration < 200 }); sleep(1); // Espera 1 segundo antes de la siguiente iteración } -
Ejecución
k6 run test.js
📊 Interpretación de los Resultados de la Prueba en K6¶
Este es un análisis detallado del test de performance que ejecutaste con K6, desglosando cada métrica clave para entender el rendimiento de tu aplicación.
/\ Grafana /‾‾/
/\ / \ |\ __ / /
/ \/ \ | |/ / / ‾‾\
/ \ | ( | (‾) |
/ __________ \ |_|\_\ \_____/
execution: local
script: .\test\stage.test.js
output: -
scenarios: (100.00%) 1 scenario, 20 max VUs, 2m50s max duration (incl. graceful stop):
* default: Up to 20 looping VUs for 2m20s over 3 stages (gracefulRampDown: 30s, gracefulStop: 30s)
✓ status is 200
✓ response time is less than 200ms
checks.........................: 100.00% 1758 out of 1758
data_received..................: 981 kB 7.0 kB/s
data_sent......................: 45 kB 317 B/s
http_req_blocked...............: avg=51.37ms min=0s med=0s max=11.12s p(90)=0s p(95)=0s
http_req_connecting............: avg=265.37µs min=0s med=0s max=12.4ms p(90)=0s p(95)=0s
http_req_duration..............: avg=22.21ms min=16.34ms med=20.82ms max=94.4ms p(90)=27.36ms p(95)=29.93ms
{ expected_response:true }...: avg=22.21ms min=16.34ms med=20.82ms max=94.4ms p(90)=27.36ms p(95)=29.93ms
http_req_failed................: 0.00% 0 out of 879
http_req_receiving.............: avg=124.29µs min=0s med=0s max=4.25ms p(90)=504.26µs p(95)=1ms
http_req_sending...............: avg=50.03µs min=0s med=0s max=1.06ms p(90)=322.56µs p(95)=432.68µs
http_req_tls_handshaking.......: avg=631.6µs min=0s med=0s max=43.11ms p(90)=0s p(95)=0s
http_req_waiting...............: avg=22.04ms min=16.34ms med=20.71ms max=93.64ms p(90)=27.07ms p(95)=29.43ms
http_reqs......................: 879 6.253511/s
iteration_duration.............: avg=2.07s min=2.01s med=2.02s max=13.16s p(90)=2.02s p(95)=2.03s
iterations.....................: 879 6.253511/s
vus............................: 1 min=1 max=20
vus_max........................: 20 min=20 max=20
- 🛠️ Configuración de la Prueba
- 📌 Modo de ejecución:
local→ Se ejecutó en tu máquina sin distribución en la nube. - 📌 Archivo del script:
stage.test.js→ Prueba basada en etapas. - 📌 Usuarios virtuales (VUs): Hasta 20 concurrentes.
- 📌 Duración: 2 minutos y 20 segundos, con una bajada gradual de 30s al finalizar.
- 📌 Etapas: 3 stages, lo que indica una simulación de carga progresiva y decreciente.
- 📌 Modo de ejecución:
- ✅ Resultados de Validaciones
- ✔️ status is 200 → Todas las respuestas fueron exitosas (HTTP 200 OK).
- ✔️ response time is less than 200ms → Se cumplieron los criterios de rendimiento esperados.
- ✔️ checks: 100% (1758 de 1758) → Todas las verificaciones pasaron sin errores.
- 📈 Métricas Clave
- 1️⃣ Latencias de Petición HTTP
| Métrica | Valor Promedio | Mínimo | Máximo | p(90) | p(95) |
|---|---|---|---|---|---|
⏳ Duración Total (http_req_duration) |
22.21ms | 16.34ms | 94.4ms | 27.36ms | 29.93ms |
⏳ Tiempo Bloqueado (http_req_blocked) |
51.37ms | 0s | 11.12s | 0s | 0s |
🔌 Tiempo de Conexión (http_req_connecting) |
265.37µs | 0s | 12.4ms | 0s | 0s |
⏳ Tiempo de Espera (http_req_waiting) |
22.04ms | 16.34ms | 93.64ms | 27.07ms | 29.43ms |
| - 🧐 Interpretación: | |||||
- http_req_duration (22.21ms promedio) indica que el servidor responde rápido. |
|||||
- http_req_waiting es casi igual a http_req_duration, lo que sugiere que la mayoría del tiempo de espera es en el servidor y no en la red. |
|||||
- http_req_blocked tiene un máximo de 11.12s, lo cual puede indicar problemas intermitentes de DNS o congestionamiento de red. |
|||||
- Baja latencia de conexión (http_req_connecting) indica que el handshake de TCP es eficiente. |
📌 Conclusión: Tu API responde bien en general, pero hay momentos de congestión que deben analizarse.
- 2️⃣ Solicitudes HTTP
| Métrica | Valor |
|---|---|
📌 Solicitudes HTTP (http_reqs) |
879 |
📌 Tasa de solicitudes (http_reqs/s) |
6.25 req/s |
📌 Solicitudes fallidas (http_req_failed) |
0.00% (0 de 879) |
🧐 Interpretación:
- Se enviaron 879 solicitudes en total, a un ritmo estable de 6.25 req/s.
- Cero errores indica que la API manejaba bien la carga sin rechazar solicitudes.
📌 Conclusión: El servidor manejó la carga de forma estable y sin errores.
- 3️⃣ Datos Transferidos
| Métrica | Valor Total | Tasa Promedio |
|---|---|---|
📩 Datos recibidos (data_received) |
981 kB | 7.0 kB/s |
📤 Datos enviados (data_sent) |
45 kB | 317 B/s |
🧐 Interpretación:
- La relación entre datos enviados y recibidos indica que las respuestas del servidor son mucho más pesadas que las solicitudes.
- Si los tiempos de respuesta aumentaran, podrías optimizar el payload de respuestas, como comprimir JSON o usar caching.
📌 Conclusión: No hay problemas de transferencia, pero puedes revisar la optimización de respuestas. - 4️⃣ Uso de Usuarios Virtuales (VUs)
| Métrica | Valor |
|---|---|
👥 Usuarios Virtuales (vus) |
1 - 20 |
🔝 Usuarios Máximos (vus_max) |
20 |
🧐 Interpretación:
- Se alcanzaron los 20 usuarios simultáneos en el punto máximo.
- La carga progresiva funcionó correctamente sin caídas en el servicio.
📌 Conclusión: El servidor manejó bien la concurrencia esperada.
Resultados HTTP:¶
Blocked: Esperando por un espacioConnecting: Estableciendo conexión con el endpointWaiting: Esperando respuesta del endpointReceiving: Tiempo recibiendo la dataSending: Tiempo mandando la data.Handshanking: Tiempo haciendo TLSHandshaking Duration: Sending + waiting + receivingFailed: Request fallidasIterations y virtual users Iterations: Veces que nuestro script se ejecutoInteration duration: Tiempo de cada iteration
Opciones, stages y virtual users¶
En K6, las opciones (options), etapas (stages) y usuarios virtuales (vus) son configuraciones clave para definir cómo se ejecutará una prueba de carga. Vamos a analizarlas en detalle.
- options → Configura la prueba (usuarios, duración, umbrales).
- vus → Define la cantidad de usuarios virtuales concurrentes.
- stages → Permite escalar la carga de forma progresiva.
1️⃣ Opciones (options) en K6¶
Las opciones en K6 permiten configurar el comportamiento del test. Se definen en un objeto llamado options dentro del script:
export let options = {
vus: 10, // 10 usuarios virtuales concurrentes
duration: '30s', // Duración de la prueba
thresholds: { // Límites de éxito/fallo
http_req_duration: ['p(95)<500'], // 95% de las solicitudes deben tardar menos de 500ms
},
};
🔹 Ejemplos de configuraciones dentro de options:
vus: Número de usuarios virtuales concurrentes.duration: Tiempo que se ejecutará la prueba.stages: Para definir cargas escalonadas (explicado más adelante).thresholds: Criterios de éxito o fallo para los resultados.scenarios: Permite definir estrategias avanzadas de prueba.
2️⃣ Usuarios Virtuales (vus)¶
Los Virtual Users (VUs) son usuarios simulados que ejecutan las solicitudes en paralelo.
📌 Ejemplo con 10 VUs durante 30s:
export let options = {
vus: 10, // 10 usuarios virtuales simultáneos
duration: '30s', // Ejecutar la prueba durante 30 segundos
};
🔹 ¿Cómo funcionan los VUs?
Cada VU ejecuta el script en un bucle durante toda la duración de la prueba. No representan usuarios reales, sino hilos de ejecución simulados.
3️⃣ Stages (stages)¶
Los stages permiten definir una carga gradual en lugar de lanzar todos los usuarios virtuales de golpe.
📌 Ejemplo de test con carga escalonada:
export let options = {
stages: [
{ duration: '30s', target: 10 }, // En 30s, subir a 10 VUs
{ duration: '1m', target: 50 }, // En 1m, subir a 50 VUs
{ duration: '30s', target: 0 }, // En 30s, bajar a 0 VUs
],
};
📌 Explicación:
1️⃣ Sube de 0 a 10 VUs en 30s → Simula un aumento gradual de tráfico.
2️⃣ Aumenta hasta 50 VUs en 1 minuto → Representa una fase de máxima carga.
3️⃣ Reduce la carga a 0 VUs en 30s → Simula una desconexión progresiva.
🚀 ¿Cuándo usar vus vs stages?¶
✅ Usar vus + duration → Cuando necesitas un número constante de usuarios virtuales durante un tiempo fijo.
✅ Usar stages → Cuando quieres simular tráfico realista, como picos de carga o crecimiento progresivo.
📌 Ejemplo práctico:
- Test de estrés →
stagescon crecimiento hasta encontrar el punto de fallo. - Carga constante →
vusconduration. - Prueba realista →
stagescon aumento y descenso progresivo.
Exportando los resultados¶
Guardar TODAS las métricas en un archivo¶
# Corriendo un script con K6 y guardarndo TODAS las métricas en un archivo
# k6 run --out [archive-type=archive-name.xxx] script.name
k6 run --out json=load-test.json load.js
Guardar solo el resumen de los resultados¶
# k6 run --summary-export=archive-name.xxx script.js
k6 run --summary-export=results.json script.js
Que son las metrics¶
Las K6 Metrics son valores que K6 registra automáticamente durante una prueba de carga para analizar el rendimiento del sistema. Estas métricas pueden ser predeterminadas (que K6 genera por defecto) o personalizadas (que tú defines según tus necesidades).
En k6 podemos clasificar estas métricas como 2 grupos:
- Métricas Predeterminadas: son aquellas que k6 compila automáticamente
- Métricas Personalizadas:
- Tipos de métricas personalizadas:
1️⃣ Counter → Cuenta eventos específicos.
2️⃣ Gauge → Almacena el último valor medido.
3️⃣ Trend → Registra valores a lo largo del tiempo.
4️⃣ Rate → Calcula la tasa de éxito o error de un evento.
Casos de Uso de Counter Metrics¶
Errores en la API: Contar cuántas solicitudes fallan.
Intentos de autenticación: Seguir cuántos inicios de sesión se realizan.
Peticiones a un endpoint específico: Contar cuántas veces se llama un servicio crítico.
Ejemplo: Contar errores 500 en la API
import { Counter } from 'k6/metrics';
import http from 'k6/http';
let serverErrors = new Counter('server_errors');
export default function () {
let res = http.get('https://api.example.com/data');
if (res.status === 500) {
serverErrors.add(1); // Incrementa si el servidor responde con 500
}
}
- Si al final del test ves
server_errors: 10, significa que la API devolvió 10 errores 500.
Funciones¶
✅ Las Counter Metrics sirven para contar eventos durante la prueba.
✅ Se crean con new Counter('nombre').
✅ Se incrementan con .add(1).
✅ Son útiles para errores, intentos fallidos, número de peticiones, etc.
📏 Gauge Metric en K6¶
La Gauge Metric en K6 es un tipo de métrica personalizada que almacena el último valor registrado en una medición. A diferencia de otras métricas como Counter (que solo suma valores) o Trend (que analiza datos en el tiempo), Gauge solo guarda el último valor medido y lo sobrescribe en cada actualización. Se usa para capturar valores fluctuantes en el sistema y analizar el último dato disponible. Algunos ejemplos de uso incluyen:
✅ Medir el tamaño de respuestas HTTP (para identificar cambios en la carga de datos).
✅ Capturar el número de conexiones activas en un momento específico.
✅ Monitorear el uso de memoria o CPU de un servidor.
** Ejemplo de uso en K6**¶
Caso 1: Medir el tamaño de la respuesta HTTP¶
import { Gauge } from 'k6/metrics';
import http from 'k6/http';
let responseSize = new Gauge('response_size');
export default function () {
let res = http.get('https://api.example.com/data');
responseSize.add(res.body.length); // Guarda el tamaño de la última respuesta
}
🔹 ¿Qué hace este código?
- Realiza una solicitud
GETa la API. - Guarda el tamaño de la respuesta en
responseSize. - Cada vez que se ejecuta, el valor previo se sobrescribe con el nuevo tamaño.
Caso 2: Medir el uso de memoria de un proceso¶
import { Gauge } from 'k6/metrics';
let memoryUsage = new Gauge('memory_usage');
export default function () {
let usedMemory = __VU * 50; // Simulación del uso de memoria por usuario virtual
memoryUsage.add(usedMemory);
}
🔹 ¿Qué hace este código?
- Simula que cada VU usa 50MB de memoria.
- Guarda el último uso de memoria registrado.
📊 Interpretando los Resultados¶
Cuando ejecutas la prueba, verás algo como esto en la consola:
response_size..................: 12.5kB
memory_usage...................: 250MB
🔹 ¿Cómo interpretarlo?
- La última respuesta HTTP recibida tuvo 12.5kB.
- La memoria más reciente usada fue 250MB.
🔍 Diferencia con otras métricas:
| Métrica | Uso |
|---|---|
Counter |
Cuenta eventos acumulativos (número de errores, intentos fallidos). |
Trend |
Analiza datos en el tiempo (promedios, percentiles). |
Rate |
Mide tasas de éxito o fallos (%). |
Gauge |
Guarda el último valor medido. |
Rate Metric en K6¶
Rate Metric mide la proporción de eventos exitosos o fallidos en relación con el total. Se usa para calcular tasas, como la cantidad de respuestas HTTP 200 en comparación con el total de solicitudes.
Ejemplo en K6: Medir tasa de éxito de respuestas HTTP¶
import { Rate } from 'k6/metrics';
import http from 'k6/http';
let successRate = new Rate('success_rate');
export default function () {
let res = http.get('https://api.example.com');
successRate.add(res.status === 200); // Añade "true" si el status es 200, "false" si no
}
Interpretación de Resultados¶
success_rate...................: 95% (950 de 1000)
¿Qué significa?¶
- El 95% de las solicitudes fueron exitosas (
status 200). - 5% de fallos (timeouts, errores 500, etc.).
📌 Útil para medir:
✅ Tasa de éxito/fallo en respuestas HTTP.
✅ Porcentaje de autenticaciones fallidas.
✅ Errores de validación en formularios.
Trend Metric en K6¶
Trend Metric se usa para analizar valores a lo largo del tiempo, proporcionando estadísticas como promedio, mínimo, máximo y percentiles. Es ideal para medir tiempos de respuesta, latencias o cualquier métrica que varíe en cada iteración.
Ejemplo en K6: Medir tiempos de respuesta¶
import { Trend } from 'k6/metrics';
import http from 'k6/http';
let responseTime = new Trend('response_time');
export default function () {
let res = http.get('https://api.example.com');
responseTime.add(res.timings.duration); // Guarda la duración de la solicitud
}
Interpretación de Resultados¶
response_time..................: avg=120ms min=80ms max=300ms p(90)=200ms p(95)=250ms
¿Qué significa?¶
- Promedio: 120ms
- Mínimo: 80ms
- Máximo: 300ms
- Percentil 90: 90% de las solicitudes tardaron menos de 200ms
- Percentil 95: 95% tardaron menos de 250ms
📌 Útil para medir:
✅ Tiempos de respuesta en APIs.
✅ Latencias de base de datos o procesos internos.
✅ Variabilidad del rendimiento a lo largo del tiempo.
Checks Y Thresholds¶
Usados para validar y medir la calidad del rendimiento en las pruebas de carga
Checks: Validaciones en cada iteración¶
Los checks permiten validar condiciones dentro del script, como el código de estado HTTP o el tiempo de respuesta.
Ejemplo:¶
import http from 'k6/http';
import { check } from 'k6';
export default function () {
let res = http.get('https://api.example.com');
check(res, {
'status is 200': (r) => r.status === 200,
'response time < 500ms': (r) => r.timings.duration < 500,
});
}
check falle esta no detendrá la ejecución de los test
Thresholds: Límites de rendimiento¶
Permiten definir un umbral criterios de fallo o éxito para la prueba
Ejemplo¶
import http from 'k6/http';
export let options = {
thresholds: {
http_req_duration: ['p(95)<500'], // 95% de las solicitudes deben tardar menos de 500ms
http_req_failed: ['rate<0.01'], // Menos del 1% de fallos permitidos
},
};
export default function () {
http.get('https://api.example.com');
}