Marcio Cunha

Cómo diseñar pruebas de carga realistas con K6 en microservicios

Aprende a simular tráfico real de producción en microservicios usando K6 para descubrir cuellos de botella ocultos de concurrencia antes de que fallen.

Marcio Cunha•5 min
También disponible en:PortuguêsEnglish
Resumen
  • Simular el comportamiento real de los usuarios exige modelar perfiles de tráfico variables en lugar de disparar peticiones en línea recta.
  • La herramienta K6 permite escribir scripts en JavaScript para estresar APIs y medir el impacto exacto de la concurrencia en la infraestructura.
  • El aislamiento de fallos y la resiliencia de red evitan que una caída puntual en cascada paralice toda la arquitectura de microservicios.
  • Métricas detalladas de latencia de percentil ayudan a descubrir cuellos de botella invisibles que los promedios aritméticos suelen enmascarar.
  • Las pruebas de carga continuas integradas en el ciclo de entrega garantizan que las nuevas actualizaciones no degraden el rendimiento bajo presión.

Por qué las pruebas de carga tradicionales fallan en microservicios

Muchos equipos de ingeniería configuran pruebas de rendimiento lanzando una ráfaga constante de peticiones contra una aplicación, creyendo que esto simula el mundo real. En la práctica, el tráfico humano es caótico, marcado por picos repentinos, períodos de calma y comportamientos inesperados. Cuando trabajamos con arquitecturas distribuidas, donde docenas de servicios se comunican entre sí, una carga uniforme oculta graves problemas de concurrencia. Si un solo servicio secundario se traba, el efecto en cascada puede paralizar toda la aplicación, un comportamiento que las herramientas simples de disparo no logran capturar con precisión.

Para entender el impacto real en el sistema, necesitamos herramientas que permitan simular escenarios complejos de uso, variando el número de usuarios activos a lo largo del tiempo. Es exactamente aquí donde entra K6, una herramienta moderna de pruebas de carga desarrollada en Go que utiliza JavaScript para crear escenarios de estrés. En la práctica, esto significa que podemos escribir scripts legibles que describen recorridos completos de navegación, desde el inicio de sesión hasta finalizar una compra, y ejecutarlos simulando a miles de personas accediendo al sistema al mismo tiempo. La claridad en la modelación del tráfico es el primer paso para obtener datos confiables.

Además de simular tráfico, debemos garantizar que la infraestructura subyacente soporte picos sin corromper datos o agotar las conexiones de bases de dados. En escenarios complejos, los problemas de red entre contenedores pueden surgir de repente, exigiendo especial atención a la estabilidad de los nodos, tal como se discute en las estrategias para aislar fallos de red en contenedores con iptables. Sin esta base sólida de red, cualquier prueba de carga medirá únicamente la lentitud de la capa de transporte, distorsionando por completo los resultados operativos del software.

Modelando escenarios de tráfico real con scripts en K6

Escribir una prueba de carga en K6 es sorprendentemente directo porque separa la lógica de ejecución de la configuración de carga mediante objetos llamados options. En lugar de usar solo un número fijo de usuarios virtuales, conocidos como VUs, configuramos etapas que aumentan y disminuyen gradualmente. Esto simula la hora pico de un comercio electrónico, por ejemplo, donde el tráfico crece lentamente por la mañana, explota a la hora del almuerzo y disminuye por la tarde. Modelar esta curva evita falsos positivos generados por sistemas que colapsan simplemente por recibir una avalancha instantánea de conexiones que nunca ocurriría en la vida real.

A continuación tenemos un ejemplo práctico de un script de K6 configurado para simular este comportamiento de tráfico variable, haciendo peticiones HTTP a una API de microservicios y midiendo el tiempo de respuesta. El código utiliza funciones nativas para verificar el código de estado y garantizar que el sistema no solo responde, sino que responde correctamente bajo presión:

import http from 'k6/http';
import { check, sleep } from 'k6';

export const options = {
  stages: [
    { duration: '2m', target: 50 },
    { duration: '5m', target: 200 },
    { duration: '2m', target: 0 },
  ],
  thresholds: {
    http_req_duration: ['p(95)<500'],
  },
};

export default function () {
  const res = http.get('https://api.ejemplo.com/productos');
  check(res, {
    'estado es 200': (r) => r.status === 200,
    'duracion menor a 500ms': (r) => r.timings.duration < 500,
  });
  sleep(1);
}

En este script, definimos umbrales de tolerancia, conocidos como thresholds, que determinan si la prueba pasó o falló basándose en criterios estrictos. El parámetro 'p(95)<500' significa que el 95% de todas las peticiones HTTP deben responder en menos de 500 milisegundos. Si el sistema se calienta y el tiempo de respuesta sube por encima de eso, K6 señala el fallo inmediatamente en la consola. Este enfoque automatizado evita que código lento llegue al entorno de producción.

Identificando cuellos de botella de concurrencia y el efecto en cascada

Cuando la carga aumenta en un sistema de microservicios, el cuello de botella rara vez está donde lo esperamos. Por lo general, el problema no es la CPU del microservicio principal, sino el agotamiento de conexiones en el pool de la base de datos, el bloqueo de hilos en llamadas síncronas o la falta de mecanismos de protección contra fallos en cadena. Para evitar que la lentitud de un servicio arrastre a los demás, es fundamental implementar patrones arquitectónicos defensivos, como explicamos en la guía sobre cómo proteger microservicios del efecto cascada con circuit breaker. Sin este blindaje, cualquier prueba de carga realista revelará un colapso sistémico total.

Durante la ejecución de la prueba con K6, debemos observar atentamente las métricas de percentil en lugar de basarnos únicamente en el promedio aritmético. El promedio es una ilusión peligrosa porque oculta picos absurdos de latencia que afectan directamente a una parte de los usuarios reales. Si el tiempo de respuesta promedio es de 100 milisegundos, pero el percentil 99 alcanza los 8 segundos, significa que el 1% de sus clientes se enfrenta a pantallas congeladas y abandona la compra. Identificar estos puntos ciegos de concurrencia permite ajustar índices en bases de datos, aumentar tiempos de espera de forma estratégica o redimensionar pods en clústeres de Kubernetes.

Validando la resiliencia operativa y el siguiente paso

Ejecutar pruebas de carga realistas con K6 transforma la forma en que la ingeniería aborda la estabilidad de los sistemas. En lugar de confiar en que la aplicación soportará el tráfico del mundo real, el equipo pasa a anticipar fallos, calibrar límites de concurrencia y validar arquitecturas bajo presión controlada. El secreto está en tratar el script de prueba como un artefacto vivo de código, versionándolo junto con la aplicación y ejecutándolo automáticamente en cada cambio importante de infraestructura.

Para consolidar una cultura de alta disponibilidad en entornos distribuidos, el monitoreo continuo debe ir de la mano con las pruebas de estrés. Integrar estas simulaciones en los conductos de CI/CD garantiza que ningún código ineficiente escape a producción, manteniendo la experiencia del usuario rápida, fluida y altamente confiable bajo cualquier volumen de accesos.