Skip to content

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.
  • ✅ 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 espacio
  • Connecting: Estableciendo conexión con el endpoint
  • Waiting: Esperando respuesta del endpoint
  • Receiving: Tiempo recibiendo la data
  • Sending: Tiempo mandando la data.
  • Handshanking: Tiempo haciendo TLS
  • Handshaking Duration: Sending + waiting + receiving
  • Failed: Request fallidas
  • Iterations y virtual users Iterations: Veces que nuestro script se ejecuto
  • Interation 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ésstages con crecimiento hasta encontrar el punto de fallo.
  • Carga constantevus con duration.
  • Prueba realistastages con 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 GET a 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,
  });
}
en caso de que un 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');
}