Pruebas de carga con k6: probando el rendimiento de una web
Cómo hacer pruebas de carga con k6 y Docker: instalación, scripts y un laboratorio con humo, estrés, picos y compras para analizar el rendimiento web.
Fuentes revisadas el 14 de noviembre de 2026
Una página web puede cargar rápido cuando la visitamos nosotros solos y comportarse de otra manera cuando empiezan a entrar muchas visitas a la vez. Lo mismo ocurre con una aplicación web. En mi caso quería comprobar qué pasaba al aumentar el tráfico: si los tiempos de respuesta se mantenían, qué recursos consumía el servidor y cómo respondía cuando, además de consultar páginas, había usuarios haciendo compras.
Para estas pruebas he utilizado Grafana k6. En este artículo voy a mostrar cómo prepararlo con Docker, cómo escribir una primera prueba y cómo he ido aumentando la carga sobre la web. Después veremos los resultados del laboratorio y algunas cosas que encontré al cruzarlos con las métricas del servidor. Si no has utilizado k6, puedes empezar por la primera prueba y seguir después con los escenarios que te interesen.
En este artículo
- Qué es k6 y para qué lo voy a utilizar
- Instalar y ejecutar k6 con Docker
- El escenario sobre el que voy a trabajar
- Comprobando el script con una prueba de humo
- Simulando usuarios que navegan por la web
- Aumentando el ritmo con una prueba de estrés
- Comprobando la respuesta a un pico de visitas
- Probando un recorrido de compra con sesión y CSRF
- Manteniendo la carga durante treinta minutos
- Qué resultados he obtenido
- Ver los resultados en el panel web de k6
- Dejando el entorno como estaba
- Conclusión
Qué es k6 y para qué lo voy a utilizar
k6 es una herramienta de código abierto para realizar pruebas de carga y rendimiento. Nos permite definir un comportamiento mediante un script de JavaScript y ejecutarlo con varios usuarios virtuales, durante el tiempo y con el ritmo que configuremos. Un usuario virtual, o VU, es uno de los usuarios simulados que ejecutan ese script; no es una persona ni una ventana de navegador.
Por ejemplo, podemos pedirle que un usuario consulte una página cada pocos segundos, que ese número de usuarios aumente hasta 200 o que se consulten 100 páginas por segundo. Mientras se ejecuta la prueba, k6 recoge cuánto tardan las peticiones, cuáles fallan y si se cumplen los objetivos que hemos definido.
Una prueba de carga sirve para observar cómo responde la web con un tráfico determinado. Con una prueba de estrés aumentamos ese tráfico para buscar cuándo empieza a empeorar. Y con una prueba de resistencia lo mantenemos durante más tiempo para ver si aparecen problemas que una visita rápida no deja ver.
Los scripts están escritos en JavaScript, pero se ejecutan en el entorno de k6. No es necesario instalar Node.js para utilizarlos y tampoco podemos dar por hecho que cualquier librería de Node vaya a funcionar aquí.
En este laboratorio voy a usar el módulo HTTP de k6. Una petición HTTP es la solicitud que se envía al servidor para consultar una página o enviar datos; el servidor devuelve una respuesta con un estado y su contenido. Esto quiere decir que estoy probando las peticiones y respuestas de la aplicación: no se abre un navegador, no se dibuja la página y no se ejecuta su JavaScript de cliente. Consultar el HTML tampoco descarga automáticamente sus imágenes, hojas de estilo o scripts. Para medir el comportamiento en un navegador existe k6 browser, que tiene otro alcance. Los tiempos que veremos aquí no son una medida de cuánto tarda una persona en ver la página completa.
Dónde se ejecuta k6 y qué estamos midiendo
En este laboratorio hay dos equipos, cada uno con su trabajo:
| Dónde | Qué hace |
|---|---|
| Mi Mac, con k6 dentro de Docker | Genera el tráfico, envía las peticiones HTTP y mide las respuestas que recibe. |
| El servidor donde está alojada la web | Atiende esas peticiones. Allí recojo, por separado, su consumo de CPU, memoria y otros recursos. |
No necesito instalar k6 en el servidor de la web. Lo ejecuto desde el Mac contra la URL que quiero probar. Los tiempos incluyen el recorrido de la red; la CPU y la memoria del servidor no aparecen automáticamente en el resumen de k6.
También podemos probar una web que se esté ejecutando en el propio Mac. En ese caso k6 y la web comparten los recursos del equipo, y el resultado puede verse condicionado por esa competencia. Para este laboratorio los he separado: el Mac genera la carga y el servidor remoto la recibe.
Instalar y ejecutar k6 con Docker
Voy a trabajar desde un Mac con Docker disponible mediante OrbStack. Docker permite ejecutar k6 dentro de un contenedor: un entorno separado con lo necesario para arrancar la herramienta. Puedes utilizar Docker Desktop o un equipo Linux con Docker; los comandos de este artículo están preparados para una terminal de macOS o Linux.
La documentación oficial de instalación incluye también paquetes para Windows y macOS. En mi caso he elegido el contenedor para no instalar k6 directamente en el equipo.
Antes de seguir, Docker debe estar instalado y en ejecución. Puedes comprobarlo con docker version: deben aparecer tanto el cliente como el servidor. Si no tienes Docker, puedes instalar OrbStack para Mac o Docker Desktop. No es necesario tener ambos.
Lo primero es descargar la imagen de k6 y comprobar la versión:
docker pull grafana/k6:2.4.0
docker run --rm grafana/k6:2.4.0 version
La versión utilizada en este laboratorio es k6 v2.4.0. Dejo la etiqueta indicada en los ejemplos para que una actualización de latest no cambie la herramienta entre una prueba y otra.
Creando una primera prueba
Antes de preparar el laboratorio completo, vamos a empezar con un script pequeño. Crea una carpeta de trabajo con mkdir -p k6 && cd k6 y, dentro, un fichero llamado primer-test.js con el siguiente contenido. Este primer ejemplo es independiente: no necesita ningún otro fichero.
import http from 'k6/http';
import { check, sleep } from 'k6';
const BASE_URL = (__ENV.BASE_URL || '').replace(/\/$/, '');
if (!/^https?:\/\//.test(BASE_URL)) {
throw new Error('Indica la URL de tu entorno de pruebas mediante BASE_URL.');
}
export const options = {
vus: 1,
duration: '30s',
thresholds: {
http_req_failed: ['rate==0'],
http_req_duration: ['p(95)<1000'],
checks: ['rate==1'],
},
};
export default function () {
const respuesta = http.get(BASE_URL);
check(respuesta, {
'estado 200': (r) => r.status === 200,
'incluye el cierre del HTML': (r) =>
typeof r.body === 'string' && r.body.includes('</html>'),
});
sleep(1);
}
En options he configurado un usuario virtual durante 30 segundos. La función default contiene lo que va a repetir ese usuario: hacer una petición, comprobar la respuesta y esperar un segundo antes de volver a empezar.
Los check comprueban condiciones sobre cada respuesta. Aquí verifico el estado 200, que indica una respuesta HTTP correcta, y que llegue el cierre del HTML. Es una comprobación básica: una página de error también podría devolver 200 y contener esa etiqueta. En una prueba de tu aplicación conviene comprobar además un título, un texto o algún dato que identifique la respuesta correcta.
Los thresholds son los criterios para decidir si la prueba pasa. En este ejemplo no acepto errores HTTP ni checks fallidos, y pido que el 95 % de las peticiones tarde menos de un segundo. Un check fallido, por sí solo, no hace que k6 termine con un código de error; el umbral sobre checks es lo que incorpora esa condición al resultado de la prueba.
Para ejecutarlo, sitúate en la carpeta donde has guardado el fichero y utiliza:
docker run --rm -v "$PWD:/k6" -w /k6 \
-e BASE_URL=https://tu-web-de-pruebas.example \
grafana/k6:2.4.0 run primer-test.js
Sustituye la URL por la de un entorno tuyo preparado para las pruebas. El parámetro -v monta la carpeta actual dentro del contenedor y -w indica el directorio de trabajo. BASE_URL llega al script a través de __ENV, por lo que no tenemos que editar el fichero cada vez que cambiamos de entorno.
La URL que termina en .example es un marcador, no una web de pruebas real. Empieza contra tu entorno de pruebas; estos ejemplos no están pensados para lanzar tráfico a una web ajena.
Si la web o aplicación se ejecuta en el mismo Mac, recuerda que localhost dentro del contenedor es el propio contenedor. Con Docker Desktop u OrbStack puedes acceder al equipo anfitrión mediante host.docker.internal, seguido del puerto donde escuche tu aplicación. Por ejemplo, si la abres en el Mac en http://localhost:8000, desde k6 utilizarías -e BASE_URL=http://host.docker.internal:8000. Esto cambia el destino de las peticiones; k6 sigue ejecutándose en el contenedor.
Cómo leer el resultado de k6
Al terminar aparecerá el resumen en la terminal. Al principio puede parecer que hay demasiados datos, pero estos son los que voy a utilizar durante el artículo:
| Métrica | Qué nos indica |
|---|---|
http_reqs |
Número de peticiones HTTP realizadas y ritmo medio. |
http_req_failed |
Proporción de peticiones consideradas fallidas por k6. Por defecto, los estados esperados son 200–399; los checks permiten validar también el contenido. |
http_req_duration |
Tiempo de envío, espera y recepción de la respuesta. No incluye el establecimiento de la conexión TCP ni la negociación TLS. |
http_req_waiting |
Tiempo hasta el primer byte de la respuesta, o TTFB, observado desde el cliente. Incluye la latencia de red. |
iterations |
Veces que se ha ejecutado la función del escenario. Una iteración es una repetición de ese recorrido: puede contener una petición o una compra completa con varias llamadas. |
vus |
Usuarios virtuales activos. |
Dentro de los tiempos veremos la media, el mínimo, el máximo y varios percentiles. Si el p95 es de 300 ms, aproximadamente el 95 % de las muestras queda por debajo de ese tiempo; el 5 % restante tarda más. El p50 es la mediana y el p99 nos permite fijarnos en una parte más pequeña de las respuestas lentas.
Voy a mirar esos percentiles durante las pruebas. Una media aceptable puede ocultar que algunas peticiones estén tardando bastante más de lo habitual.
El escenario sobre el que voy a trabajar
La aplicación del laboratorio está desarrollada en PHP y se ejecuta en un VPS, un servidor virtual, con 4 vCPU y unos 8 GB de RAM. Dentro del servidor tengo Caddy con FrankenPHP, PostgreSQL, Redis y varios procesos de cola. También hay tareas programadas y CrowdSec analizando el tráfico.
Las pruebas se lanzan desde un MacBook Pro M1 Pro con 16 GB de RAM. El servidor está lejos del generador de carga. El ping, una referencia del tiempo de ida y vuelta por la red, es de unos 124 ms. Por tanto, incluso cuando el servidor responde rápido, el resultado lleva incorporado el tiempo de la red.
He preparado seis escenarios. En k6, un escenario define qué función se ejecuta y cómo se reparte el trabajo. Estos son los que voy a utilizar:
| Prueba | Qué quiero comprobar |
|---|---|
| Humo | Si el script y las rutas funcionan con un solo usuario. |
| Carga | Cómo responde la web con usuarios navegando y haciendo pausas. |
| Estrés | Qué ocurre al subir el ritmo de peticiones por escalones. |
| Picos | Cómo responde y se recupera después de una subida repentina. |
| Visitas y compras | Cómo conviven las consultas con un recorrido que crea pedidos. |
| Resistencia | Si los tiempos y los recursos se mantienen con carga sostenida. |
Los valores que siguen son los de mi laboratorio. Para probar otra web, empezaría con un usuario y aumentaría la carga poco a poco; no copiaría directamente los escalones más altos. Los umbrales también dependen de lo que se considere aceptable para cada operación.
Preparando el entorno
Antes de lanzar la batería he revisado las protecciones del servidor. En una prueba anterior, CrowdSec bloqueó mi IP durante cuatro horas. El tráfico de una prueba de carga también pasa por el WAF, el cortafuegos de aplicaciones web, y por los límites de peticiones de la aplicación, y puede quedarse detenido ahí antes de llegar a lo que queremos medir.
En este laboratorio utilicé una lista permitida temporal para la IP del generador y una excepción equivalente en la aplicación. Esto cambia el escenario: los resultados corresponden a esa configuración de pruebas, no a todas las restricciones de una visita normal. Al terminar retiré ambas excepciones.
Si necesitas hacer lo mismo, comprueba primero la IP que realmente ve el servidor, especialmente si hay IPv6 o un proxy de por medio. No hace falta desactivar las protecciones para todos los visitantes. En la documentación de CrowdSec puedes consultar cómo gestionar sus listas permitidas.
Las pruebas que crean pedidos las he separado de las que solo consultan páginas. Para repetirlas hay que tener datos de prueba, stock preparado y desactivados los envíos o integraciones que puedan afectar a personas reales. El recorrido que mostraré se detiene antes de realizar ningún pago.
Organizando los scripts
La primera prueba ya funciona por sí sola. Para los siguientes escenarios voy a reutilizar algunas funciones y guardar los resultados en JSON. Todo el código necesario está en los bloques del artículo; puedes copiarlo con el botón de cada bloque.
Desde la carpeta k6 que hemos creado antes, prepara dos subcarpetas y deja configurada la URL de tu entorno en esta terminal:
mkdir -p scripts resultados
export BASE_URL=https://tu-web-de-pruebas.example
Recuerda cambiar esa URL. Los siguientes comandos se ejecutan desde la carpeta k6, no desde scripts. Si abres otra terminal, tendrás que volver a definir BASE_URL.
Voy a guardar las páginas que quiero consultar en scripts/rutas.json:
[
{ "ruta": "/", "nombre": "inicio" },
{ "ruta": "/productos", "nombre": "catalogo" },
{ "ruta": "/condiciones", "nombre": "condiciones" },
{ "ruta": "/tienda", "nombre": "tienda" },
{ "ruta": "/resultados", "nombre": "resultados" },
{ "ruta": "/consulta", "nombre": "consulta" }
]
Son rutas de ejemplo: sustitúyelas por páginas que existan en tu web. ruta es el camino después del dominio y nombre la etiqueta con la que aparecerá en las métricas. Si no tienes una tienda, elimina esa entrada y el umbral específico de tienda del escenario de carga.
El módulo compartido estará en scripts/comun.js. Lee esas rutas, hace las peticiones y guarda el resumen. Lo dejo completo para que no falte ninguna función al copiar los escenarios:
Ver el código completo de scripts/comun.js
import http from 'k6/http';
import { check } from 'k6';
import { Trend } from 'k6/metrics';
import { textSummary } from 'https://jslib.k6.io/k6-summary/0.1.0/index.js';
export const BASE_URL = (__ENV.BASE_URL || '').replace(/\/$/, '');
if (!/^https?:\/\//.test(BASE_URL)) throw new Error('Indica la URL de tu entorno mediante BASE_URL.');
export const RUTAS = JSON.parse(open(__ENV.RUTAS || './rutas.json'));
if (!Array.isArray(RUTAS) || !RUTAS.length || RUTAS.some(r => !r.nombre || typeof r.ruta !== 'string' || !r.ruta.startsWith('/') || r.ruta.startsWith('//'))) {
throw new Error('rutas.json debe contener una lista no vacía de rutas relativas y sus nombres.');
}
export const CABECERAS = {
'Accept': 'text/html,application/xhtml+xml',
'Accept-Encoding': 'gzip, deflate, br, zstd',
'Accept-Language': 'es-ES,es;q=0.9',
'User-Agent': 'k6-laboratorio/1.0',
};
export const ttfb = new Trend('ttfb_pagina', true);
export function rutaAlAzar() {
return RUTAS[Math.floor(Math.random() * RUTAS.length)];
}
export function visitar(ruta) {
const r = http.get(`${BASE_URL}${ruta.ruta}`, {
headers: CABECERAS,
tags: { name: ruta.nombre },
});
ttfb.add(r.timings.waiting, { pagina: ruta.nombre });
check(r, {
'status 200': (x) => x.status === 200,
'incluye el cierre del HTML': (x) => typeof x.body === 'string' && x.body.includes('</html>'),
});
return r;
}
export function resumen(nombre) {
return (data) => ({
stdout: textSummary(data, { indent: ' ', enableColors: true }),
[`resultados/${nombre}-resumen.json`]: JSON.stringify(data, null, 2),
});
}
export function rutasMezcladas() {
const rutas = [...RUTAS];
for (let i = rutas.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[rutas[i], rutas[j]] = [rutas[j], rutas[i]];
}
return rutas;
}
La etiqueta name agrupa las peticiones por página y permite poner objetivos distintos a cada operación. ttfb_pagina es una métrica propia de tipo Trend, que guarda tiempos para calcular estadísticas como la media y los percentiles.
open() lee el JSON antes de empezar a ejecutar las visitas. La función resumen() mantiene el resultado en la consola y escribe una copia en resultados. Para mostrarlo utiliza una pequeña librería oficial, k6-summary, que k6 obtiene de la URL indicada en el import.
Los ficheros van a quedar así; los seis escenarios los iremos creando a continuación:
k6/
├── primer-test.js
├── scripts/
│ ├── comun.js
│ ├── rutas.json
│ ├── 01-humo.js
│ ├── 02-carga.js
│ ├── 03-estres.js
│ ├── 04-picos.js
│ ├── 05-resistencia.js
│ └── 06-compras.js
└── resultados/
Recogiendo las métricas del servidor
Además de los resultados de k6, quiero saber qué ocurre dentro del VPS mientras dura la prueba. Para el laboratorio he utilizado un recolector propio que guarda CPU, memoria y carga del sistema, consumo de los contenedores y conexiones a PostgreSQL. De esos registros salen las gráficas de recursos del artículo.
Para seguir los ejemplos de k6 no hace falta montar ese recolector. Si tu web está en un servidor Linux, puedes abrir una terminal en ese servidor, por ejemplo mediante SSH, y observar su actividad con:
vmstat 1
Es un comando de procps; si tu distribución no lo incluye, tendrás que instalar ese paquete. Muestra una muestra cada segundo. En la CPU, us es el tiempo de usuario, sy el del sistema, id el tiempo inactivo y wa la espera de entrada/salida. La primera fila resume el tiempo desde el arranque; para la prueba nos interesan las siguientes.
Si utilizas contenedores, en otra terminal del servidor puedes ver el consumo de cada uno:
docker stats
Con Ctrl+C sales de cualquiera de los dos comandos sin detener la web ni sus contenedores. Si quieres registrar los datos en un fichero o añadir métricas de la base de datos, necesitarás preparar esa recogida según tu instalación. Anota también cuándo empieza y termina cada prueba para poder compararlos con los resultados de k6.
Recomiendo comprobar la recogida antes de empezar. A mí el recolector detallado se me detuvo durante la prueba de resistencia y perdí parte de las muestras. Más adelante veremos qué datos pude conservar.
Comprobando el script con una prueba de humo
Ahora que tenemos preparada la herramienta, lo primero es comprobar que las rutas funcionan. Esta prueba utiliza un usuario durante un minuto, que recorre todas las páginas del JSON con una pausa de un segundo entre ellas.
El script es parecido al primero, pero utiliza las funciones compartidas:
Fichero: scripts/01-humo.js.
import { sleep } from 'k6';
import { RUTAS, visitar, resumen } from './comun.js';
export const options = {
vus: 1,
duration: '1m',
thresholds: {
http_req_failed: ['rate==0'],
http_req_duration: ['p(95)<1000'],
checks: ['rate==1'],
},
};
export default function () {
for (const ruta of RUTAS) {
visitar(ruta);
sleep(1);
}
}
export const handleSummary = resumen('01-humo');
Para ejecutarlo desde la carpeta k6:
docker run --rm --user "$(id -u):$(id -g)" \
-v "$PWD:/k6" -w /k6 -e BASE_URL \
grafana/k6:2.4.0 run scripts/01-humo.js
El resumen se verá en la terminal y se guardará en resultados/01-humo-resumen.json. En los siguientes ejemplos solo cambia el nombre del script. Para detener una prueba antes de que termine, utiliza Ctrl+C.
En la ejecución del laboratorio se realizaron 48 peticiones, sin errores ni checks fallidos. El p95 fue de 300 ms y la mediana de 257 ms. Es mi referencia para comparar lo que ocurra cuando entren más usuarios.
Hay un detalle importante en estos primeros tiempos: la mediana del TTFB fue de unos 133 ms y la recepción del cuerpo de la respuesta rondó los 123 ms. Con una latencia de red de 124 ms, está claro que la distancia pesa bastante en el resultado.
El TTFB incluye tanto la espera en el servidor como el recorrido de la red. Restarle el ping no nos da el tiempo exacto de ejecución de PHP: son mediciones distintas. Si quiero separar ambas cosas, necesito medir en el servidor o repetir la prueba desde un punto más cercano. Para las siguientes comparaciones voy a mantener el mismo origen y destino.
Simulando usuarios que navegan por la web
Una vez comprobado que el script hace lo que esperamos, voy a aumentar la carga. En este caso quiero llegar a 200 usuarios virtuales. Cada uno consulta cuatro páginas y espera entre dos y seis segundos después de cada respuesta, como una aproximación al tiempo de lectura.
Para ello utilizo ramping-vus. El executor es la forma en que k6 programa el trabajo; este permite subir y bajar el número de usuarios de forma progresiva:
Fichero: scripts/02-carga.js.
import { sleep, group } from 'k6';
import { rutasMezcladas, visitar, resumen } from './comun.js';
export const options = {
scenarios: {
navegacion: {
executor: 'ramping-vus',
startVUs: 0,
stages: [
{ duration: '2m', target: 200 },
{ duration: '8m', target: 200 },
{ duration: '1m', target: 0 },
],
gracefulRampDown: '30s',
},
},
thresholds: {
http_req_failed: ['rate<0.01'],
http_req_duration: ['p(95)<800', 'p(99)<1500'],
'http_req_duration{name:tienda}': ['p(95)<1000'],
checks: ['rate>0.99'],
},
};
export default function () {
const visita = rutasMezcladas().slice(0, 4);
group('visita', () => {
for (const ruta of visita) {
visitar(ruta);
sleep(2 + Math.random() * 4);
}
});
}
export const handleSummary = resumen('02-carga');
docker run --rm --user "$(id -u):$(id -g)" \
-v "$PWD:/k6" -w /k6 -e BASE_URL \
grafana/k6:2.4.0 run scripts/02-carga.js
stages contiene las etapas: dos minutos para llegar a 200 VUs, ocho manteniéndolos y uno para bajar a cero. rutasMezcladas() baraja las páginas y slice(0, 4) selecciona hasta cuatro. Fíjate también en el umbral de tienda: no tengo por qué evaluar todas las operaciones con el mismo objetivo.
Este escenario utiliza un modelo cerrado. Un usuario no empieza su siguiente paso hasta que termina el anterior y se cumple su pausa. Si una respuesta tarda más, ese usuario también tarda más en volver a pedir otra página.
Por eso 200 usuarios virtuales no equivalen a 200 peticiones por segundo. En la parte estable de esta prueba se produjeron unas 47 peticiones por segundo; el promedio de toda la ejecución, incluyendo subida y bajada, fue de unas 40.

En la gráfica podemos ver cómo suben los usuarios y el tráfico mientras los tiempos de respuesta apenas cambian. Se realizaron 27.172 peticiones, todas las comprobaciones pasaron y el p95 quedó en 273 ms, muy cerca de la referencia de la prueba de humo.
La CPU del servidor estuvo en un 21 % de media. Con este recorrido, estas pausas y las páginas seleccionadas, la aplicación atendió la carga sin una degradación apreciable de los tiempos medidos.
También he separado las respuestas por página:

Las distribuciones son parecidas entre sí y ninguna de estas rutas destaca claramente por ser mucho más lenta que las demás. Son páginas públicas, con su caché y sin realizar escrituras. Cuando llegue a la compra estaré probando un trabajo diferente.
Aumentando el ritmo con una prueba de estrés
En la siguiente prueba quiero ver qué ocurre cuando exijo cada vez más trabajo. Para eso cambio a ramping-arrival-rate, un executor de modelo abierto.
Aquí configuro cuántas iteraciones por segundo deben empezar. Como la función de este escenario hace una única petición, el ritmo de iteraciones se corresponde con el de peticiones, salvo redirecciones u otras peticiones añadidas. Si una iteración hiciera cinco llamadas, la interpretación sería distinta.
Los escalones van de 50 a 350, dejando 30 segundos de transición y 90 segundos mantenidos en cada nivel:
Fichero: scripts/03-estres.js.
import { rutaAlAzar, visitar, resumen } from './comun.js';
const ESCALONES = (__ENV.ESCALONES || '50,100,150,200,250,300,350').split(',').map(Number);
export const options = {
scenarios: {
escalones: {
executor: 'ramping-arrival-rate',
startRate: ESCALONES[0],
timeUnit: '1s',
preAllocatedVUs: 200,
maxVUs: 2000,
stages: ESCALONES.flatMap((ritmo) => [
{ duration: '30s', target: ritmo },
{ duration: '90s', target: ritmo },
]),
},
},
thresholds: {
http_req_failed: ['rate<0.05'],
http_req_duration: ['p(95)<2000'],
dropped_iterations: ['count<100'],
},
};
export default function () {
visitar(rutaAlAzar());
}
export const handleSummary = resumen('03-estres');
docker run --rm --user "$(id -u):$(id -g)" \
-v "$PWD:/k6" -w /k6 -e BASE_URL \
grafana/k6:2.4.0 run scripts/03-estres.js
La función del escenario solo llama a visitar(rutaAlAzar()). No añado un sleep final, porque el ritmo ya lo está marcando el executor. timeUnit: '1s' indica que ese ritmo se expresa por segundo.
En este modelo hay que prestar atención a los VUs disponibles. preAllocatedVUs indica los que se preparan al principio; maxVUs, el límite hasta el que k6 puede ampliarlos. Si no hay uno libre cuando debe comenzar una iteración, esa iteración no se inicia y se cuenta en dropped_iterations.

Hasta alrededor de 250 peticiones por segundo, los tiempos se mantienen bastante próximos a los anteriores. En los escalones de 300 y 350 aparecen picos más frecuentes y la CPU se acerca a su capacidad.
El resumen de los 14 minutos muestra:
Peticiones HTTP: 162.991
Peticiones HTTP fallidas: 0 %
p95 de respuesta: 1.279 ms
p99 de respuesta: 1.666 ms
Iteraciones descartadas: 508
Umbral incumplido: dropped_iterations < 100
Son cifras del laboratorio, presentadas de forma abreviada. Aunque las peticiones que llegaron a ejecutarse terminaron sin errores, la prueba no pasó: no se cumplió el umbral de iteraciones descartadas.
Esto no demuestra, por sí solo, que hayamos encontrado la capacidad máxima del servidor. También puede influir una asignación insuficiente de VUs o el propio generador. En esta ejecución k6 llegó a preparar 648 VUs, por debajo del máximo configurado de 2.000. Antes de fijar un límite definitivo repetiría la parte alta con más VUs preasignados y comprobaría cuándo se producen los descartes.
Con los datos que sí tenemos, la zona de unas 200–250 peticiones por segundo es una referencia para este recorrido corto de lectura. A partir de ahí veo una degradación clara. No utilizaría ese número como una garantía para cualquier página, una carga prolongada o el proceso de compra.
Mirando qué consume CPU
Para entender la gráfica anterior, he separado el consumo de cada contenedor:

La aplicación llega a rozar el 290 % de CPU, mientras que CrowdSec alcanza el 92 % en las muestras de mayor consumo. En docker stats, un 100 % equivale aproximadamente a un núcleo ocupado, por lo que una cifra puede superar 100 en un equipo con varias CPU.
Estos datos apuntan a la CPU como el recurso que primero conviene investigar. CrowdSec sigue haciendo trabajo durante la prueba, aunque haya una excepción para la IP del generador. Para saber cuánto afecta al rendimiento tendría que hacer una comparación equivalente cambiando solo ese componente; su consumo aislado no me da esa respuesta.
PostgreSQL tiene bastante menos actividad en este escenario de lectura. Eso tampoco descarta que pueda convertirse en un límite con otras consultas o con más escrituras.
Los picos que se repiten cada minuto
En las gráficas hay otro comportamiento que llama la atención: algunos picos aparecen a intervalos regulares. Al agrupar las muestras por el segundo del minuto, se ve mejor:

Durante los primeros cinco segundos, el p95 ronda los 411 ms, frente a 281 ms en el resto del minuto. Ese momento coincide con las tareas programadas y con sus picos de CPU.
Es una pista para revisar qué trabajos se están ejecutando juntos. Para comprobar cuánto contribuye cada uno, lo siguiente sería repetir el tramo cambiando su planificación. La media de toda la prueba habría hecho mucho más difícil ver esta coincidencia.
Las cookies también cambian la prueba
Otro detalle que encontré fue el crecimiento de Redis. Durante el estrés pasó de unos 18 a 92 MB y la memoria no volvió al valor inicial al terminar.
Por defecto, k6 restablece las cookies de cada VU al empezar una nueva iteración. Dentro de una iteración sí mantiene las cookies entre peticiones. En este escenario, sin embargo, cada iteración contiene una sola visita, y la aplicación puede crear una sesión nueva en cada una.
Si quieres comprobarlo por separado, guarda este ejemplo como cookies.js en la carpeta k6. Muestra las cookies enviadas en cada una de tres iteraciones:
import http from 'k6/http';
export const options = { vus: 1, iterations: 3 };
export default function () {
const r = http.get(__ENV.BASE_URL);
console.log(`Iteración ${__ITER}: ${Object.keys(r.request.cookies).join(',') || 'sin cookies enviadas'}`);
}
Puedes ejecutarlo con el mismo comando de primer-test.js, cambiando el nombre por cookies.js. Que aparezca «sin cookies enviadas» solo demuestra este comportamiento si la web devuelve cookies en sus respuestas.
La opción noCookiesReset: true permite conservarlas entre iteraciones. Elegir una u otra depende de lo que queramos representar: visitas nuevas, personas que vuelven o usuarios que mantienen una sesión.
En esta ejecución mantuve el comportamiento por defecto. La carga de sesiones forma parte del resultado y hay que tenerla en cuenta al compararlo con tráfico de navegadores que reutilizan sus cookies.
Comprobando la respuesta a un pico de visitas
Después del aumento progresivo, voy a probar una subida repentina: pasar de 20 a 600 VUs en diez segundos, mantenerlos un minuto y volver a 20.
Utilizo de nuevo ramping-vus, con estas etapas:
Fichero: scripts/04-picos.js.
import { sleep } from 'k6';
import { rutaAlAzar, visitar, resumen } from './comun.js';
export const options = {
scenarios: {
pico: {
executor: 'ramping-vus',
startVUs: 20,
stages: [
{ duration: '1m', target: 20 },
{ duration: '10s', target: 600 },
{ duration: '1m', target: 600 },
{ duration: '10s', target: 20 },
{ duration: '2m', target: 20 },
],
gracefulRampDown: '10s',
},
},
thresholds: {
http_req_failed: ['rate<0.02'],
http_req_duration: ['p(95)<3000'],
},
};
export default function () {
visitar(rutaAlAzar());
sleep(1 + Math.random() * 2);
}
export const handleSummary = resumen('04-picos');
docker run --rm --user "$(id -u):$(id -g)" \
-v "$PWD:/k6" -w /k6 -e BASE_URL \
grafana/k6:2.4.0 run scripts/04-picos.js
Cada VU consulta una página y espera entre uno y tres segundos. Quiero observar cómo suben los tiempos durante el pico y si vuelven a su nivel anterior cuando baja la carga.

Se realizaron 20.274 peticiones, sin errores HTTP. Durante la parte de mayor carga el p95 fue de unos 472 ms, frente a los 274 ms anteriores al pico. Al bajar el tráfico volvió a rondar los 272 ms.
La CPU llegó al 94 %. Al bajar la carga, las respuestas recuperaron tiempos similares a los iniciales. Como es un modelo cerrado, el ritmo efectivo también depende de cuánto tarde en completarse cada visita.
Probando un recorrido de compra con sesión y CSRF
Hasta ahora solo he consultado páginas. En el siguiente escenario voy a mezclar ese tráfico con usuarios que recorren el proceso de compra hasta llegar a la página de pago.
Hay dos escenarios ejecutándose a la vez: 60 iteraciones por segundo de visitas y hasta 30 compradores virtuales. En las visitas utilizo constant-arrival-rate; en las compras, ramping-vus.
El recorrido de un comprador tiene cinco peticiones: abrir la tienda, elegir un producto, obtener el formulario, enviar los datos y consultar la página de pago. Aquí ya tenemos que mantener una sesión, leer tokens CSRF y comprobar redirecciones.
Esta parte requiere más adaptación que una visita a una página. Es un ejemplo para un formulario de tienda como el del laboratorio, no un script que funcione sin cambios en cualquier tienda o en WooCommerce. Si estás empezando, puedes seguir con las pruebas de lectura y volver a este recorrido después.
Leer los datos del formulario
Un token CSRF es un valor que la web incluye en el formulario y valida al enviarlo, junto con la sesión, para proteger esa operación. El token debe obtenerse de la respuesta del servidor. No lo copio del navegador ni lo dejo fijo en el script. Para leerlo utilizo k6/html. Este fragmento es una parte del script completo que dejo más abajo; tienda es la respuesta de la petición a esa página:
import { parseHTML } from 'k6/html';
function campo(respuesta, selector) {
return parseHTML(respuesta.body).find(selector).first().attr('value');
}
const token = campo(tienda, 'input[name="_token"]');
También se leen los campos ocultos que la aplicación necesita para continuar. Los nombres publicados son genéricos: hay que adaptar esta parte a los formularios y las rutas de cada aplicación.
Mantener la sesión y comprobar las redirecciones
Al comenzar cada compra vacío las cookies del comprador anterior:
http.cookieJar().clear(BASE_URL);
Después dejo que el mismo VU mantenga su sesión durante todo el recorrido. Si se pierde esa cookie entre el GET del formulario y el POST, es posible que el token deje de ser válido.
Por defecto, k6 sigue las redirecciones. Para comprobar la respuesta original de un POST utilizo redirects: 0:
const enviar = http.post(`${BASE_URL}/checkout`, datos, {
headers: CABECERAS,
redirects: 0,
tags: { name: 'compra_datos' },
});
const destino = enviar.headers.Location || '';
check(enviar, {
'pedido creado': (r) =>
r.status === 302 && /\/pedido\/[0-9a-f-]{36}\/pago/.test(destino),
});
El script completo comprueba además que el destino permanece en el entorno configurado antes de seguirlo. Los emails son datos de prueba con dominio .invalid; el teléfono y las demás validaciones deben ajustarse al entorno de pruebas.
Counter sirve para contar eventos, Rate para calcular una proporción de éxitos y Trend para guardar tiempos y obtener sus percentiles. Aquí los utilizo para seguir los pedidos creados, los recorridos completados y su duración.
Ver el código completo de scripts/06-compras.js
Fichero: scripts/06-compras.js.
import http from 'k6/http';
import { check, group, sleep } from 'k6';
import { parseHTML } from 'k6/html';
import { Counter, Rate, Trend } from 'k6/metrics';
import { BASE_URL, CABECERAS, rutaAlAzar, visitar, resumen } from './comun.js';
const compraEspera = new Trend('compra_espera', true);
const pedidosCreados = new Counter('pedidos_creados');
const compraOk = new Rate('compra_ok');
export const options = {
scenarios: {
visitas: {
executor: 'constant-arrival-rate',
rate: 60,
timeUnit: '1s',
duration: '10m',
preAllocatedVUs: 60,
maxVUs: 300,
exec: 'visitas',
},
compras: {
executor: 'ramping-vus',
startVUs: 0,
stages: [
{ duration: '2m', target: 10 },
{ duration: '3m', target: 20 },
{ duration: '3m', target: 30 },
{ duration: '2m', target: 0 },
],
exec: 'compra',
},
},
thresholds: {
'http_req_duration{scenario:visitas}': ['p(95)<800'],
'http_req_duration{scenario:compras}': ['p(95)<1500'],
'http_req_failed{scenario:compras}': ['rate<0.01'],
compra_ok: ['rate>0.98'],
compra_espera: ['p(95)<3000'],
},
};
if (__ENV.SOLO_COMPROBAR === 'true') {
options.scenarios = {
comprobar_compra: {
executor: 'shared-iterations',
vus: 1,
iterations: 1,
maxDuration: '1m',
exec: 'compra',
},
};
options.thresholds = {
http_req_failed: ['rate==0'],
checks: ['rate==1'],
compra_ok: ['rate==1'],
};
}
export function visitas() {
visitar(rutaAlAzar());
}
function campo(respuesta, selector) {
return parseHTML(respuesta.body).find(selector).first().attr('value');
}
export function compra() {
let espera = 0;
const medir = (r) => { espera += r.timings.duration; return r; };
http.cookieJar().clear(BASE_URL);
const sinSeguir = { headers: CABECERAS, redirects: 0 };
let ok = false;
try {
group('compra', () => {
const tienda = medir(http.get(`${BASE_URL}/tienda`, { headers: CABECERAS, tags: { name: 'compra_tienda' } }));
const productos = parseHTML(tienda.body).find('input[name="producto"]').toArray().map((x) => x.attr('value'));
if (!check(tienda, { 'tienda 200': (r) => r.status === 200 && productos.length > 0 })) return;
const producto = productos[Math.floor(Math.random() * productos.length)];
sleep(2 + Math.random() * 3);
const elegir = medir(http.post(`${BASE_URL}/tienda`, { _token: campo(tienda, 'input[name="_token"]'), producto },
{ ...sinSeguir, tags: { name: 'compra_elegir' } }));
if (!check(elegir, { 'elegir 302': (r) => r.status === 302 })) return;
const checkout = medir(http.get(`${BASE_URL}/checkout`, { headers: CABECERAS, tags: { name: 'compra_checkout' } }));
if (!check(checkout, { 'checkout 200': (r) => r.status === 200 })) return;
const doc = parseHTML(checkout.body);
const datos = {
_token: campo(checkout, 'input[name="_token"]'),
intento: campo(checkout, 'input[name="intento"]'),
[`items[${producto}]`]: '1',
nombre: 'Prueba',
apellidos: `Carga ${__VU}-${__ITER}`,
telefono: __ENV.TELEFONO_PRUEBA || '000000000',
acepto: '1',
comunicaciones: '0',
};
doc.find('input[type="hidden"][name^="precios["]').each((_, el) => { datos[el.getAttribute('name')] = el.getAttribute('value'); });
datos.email = `k6-${__VU}-${__ITER}-${Date.now()}@ejemplo.invalid`;
datos.email_confirmation = datos.email;
sleep(5 + Math.random() * 5);
const enviar = medir(http.post(`${BASE_URL}/checkout`, datos, { ...sinSeguir, tags: { name: 'compra_datos' } }));
const destino = enviar.headers.Location || '';
const paginaPago = destino.startsWith('/') && !destino.startsWith('//') ? `${BASE_URL}${destino}` : destino;
const mismoOrigen = paginaPago.startsWith(`${BASE_URL}/`);
if (!check(enviar, { 'pedido creado': (r) => r.status === 302 && mismoOrigen && /\/pedido\/[0-9a-f-]{36}\/pago/.test(destino) })) return;
pedidosCreados.add(1);
const pago = medir(http.get(paginaPago, { headers: CABECERAS, tags: { name: 'compra_pago' } }));
ok = check(pago, { 'pago 200': (r) => r.status === 200 });
});
} finally {
compraOk.add(ok);
if (ok) compraEspera.add(espera);
sleep(3);
}
}
export const handleSummary = resumen('06-compras');
Antes de ejecutarlo adapta /tienda, /checkout, los selectores, los campos y la comprobación de la página de pago a tu aplicación. La opción exec selecciona la función de cada escenario: visitas o compra.
Primero revisa el recorrido con un comprador y una sola iteración. SOLO_COMPROBAR=true activa ese modo, sin las visitas de fondo:
docker run --rm --user "$(id -u):$(id -g)" \
-v "$PWD:/k6" -w /k6 -e BASE_URL -e SOLO_COMPROBAR=true \
grafana/k6:2.4.0 run scripts/06-compras.js
Cuando esa comprobación pase y el entorno esté preparado para los pedidos de prueba, puedes ejecutar el escenario mixto de diez minutos sin esa variable:
docker run --rm --user "$(id -u):$(id -g)" \
-v "$PWD:/k6" -w /k6 -e BASE_URL \
grafana/k6:2.4.0 run scripts/06-compras.js
Medir si el recorrido termina correctamente
Además de los tiempos HTTP, he añadido tres métricas:
pedidos_creados: cuenta las respuestas que cumplen la comprobación de creación del pedido.compra_ok: registra qué proporción de recorridos llega correctamente a la página de pago.compra_espera: sumahttp_req_durationde las peticiones de las compras completadas. No incluye las pausas, la apertura de conexiones ni el tiempo de renderizado de un navegador.
En la ejecución del laboratorio se completaron 640 recorridos hasta la página de pago, sin checks fallidos. No son 640 pagos realizados. Las visitas siguieron entrando durante la prueba y su p95 quedó en unos 270 ms.

La mediana del TTFB fue de unos 131 ms en la tienda, 152 ms al obtener el formulario y 174 ms al enviar los datos. El POST que valida y crea el pedido es el paso con más espera en esta medición. Sería uno de los primeros que revisaría si el recorrido comenzara a degradarse.
La suma de duraciones de una compra completada tuvo una media de 1,13 segundos y un p95 de unos 1,21 segundos. Es una forma de seguir el comportamiento del recorrido sin confundirlo con los segundos que hemos añadido para simular la lectura y el rellenado del formulario.
Manteniendo la carga durante treinta minutos
Para terminar, voy a mantener 80 iteraciones por segundo durante 30 minutos. Como en el estrés, cada iteración realiza una petición, pero aquí el ritmo se mantiene constante.
Me interesa ver si los tiempos cambian con el paso de los minutos y si se acumulan memoria, sesiones o conexiones. Utilizo constant-arrival-rate para mantener el ritmo sin aumentar ni reducir usuarios manualmente:
Fichero: scripts/05-resistencia.js.
import { rutaAlAzar, visitar, resumen } from './comun.js';
export const options = {
scenarios: {
constante: {
executor: 'constant-arrival-rate',
rate: Number(__ENV.RITMO || 80),
timeUnit: '1s',
duration: __ENV.DURACION || '30m',
preAllocatedVUs: 100,
maxVUs: 400,
},
},
thresholds: {
http_req_failed: ['rate<0.001'],
http_req_duration: ['p(95)<600'],
dropped_iterations: ['count==0'],
},
};
export default function () {
visitar(rutaAlAzar());
}
export const handleSummary = resumen('05-resistencia');
docker run --rm --user "$(id -u):$(id -g)" \
-v "$PWD:/k6" -w /k6 -e BASE_URL \
grafana/k6:2.4.0 run scripts/05-resistencia.js
Se realizaron 144.000 peticiones, sin errores HTTP ni iteraciones descartadas. El p95 quedó en 270 ms, próximo al de las pruebas anteriores de lectura.

La memoria del contenedor de la aplicación se mantuvo entre unos 157 y 171 MB en las muestras disponibles. La del servidor terminó en torno a 1,8 GB. En este intervalo no aparece un crecimiento sostenido de la memoria de la aplicación.
Redis sí siguió creciendo, de unos 119 a 150 MB en el tramo detallado, de aproximadamente trece minutos. El patrón encaja con las sesiones nuevas que provocan las iteraciones sin cookies. Prolongar esa pendiente hasta el límite de memoria sería una estimación, no un resultado que haya medido en esta ejecución.
También hubo un episodio de respuestas lentas alrededor del minuto 14. En esas muestras aumentó sobre todo el tiempo de recepción, mientras que el TTFB cambió menos. El siguiente paso sería revisar qué ocurrió durante la transferencia y repetir el tramo para ver si se reproduce.
Antes comentaba que el recolector me dio un problema. Dejó de escribir aproximadamente a los doce minutos, de modo que no tengo el mismo detalle de recursos para los treinta minutos completos. Para el resto utilicé las muestras que el propio servidor guardaba cada minuto. Los resultados de k6 sí cubren toda la prueba. Esta diferencia de resolución hay que recordarla al comparar los máximos de CPU con los de los escenarios anteriores.
Qué resultados he obtenido
En esta tabla he reunido los tiempos de las seis ejecuciones. Los percentiles corresponden a las peticiones HTTP del conjunto de cada prueba; no son el tiempo total de un recorrido de compra.
| Prueba | Peticiones HTTP | Fallos HTTP | p50 | p95 | p99 |
|---|---|---|---|---|---|
| Humo: 1 VU, 1 min | 48 | 0 % | 257 ms | 300 ms | 350 ms |
| Carga: hasta 200 VUs, 11 min | 27.172 | 0 % | 256 ms | 273 ms | 282 ms |
| Estrés: 50–350 iteraciones/s, 14 min | 162.991 | 0 % | 259 ms | 1.279 ms | 1.666 ms |
| Pico: 20–600 VUs, 4 min 20 s | 20.274 | 0 % | 262 ms | 456 ms | 571 ms |
| Visitas y compras: 10 min | 39.201 | 0 % | 253 ms | 272 ms | 302 ms |
| Resistencia: 80 iteraciones/s, 30 min | 144.000 | 0 % | 253 ms | 270 ms | 303 ms |
En total son 393.686 peticiones HTTP y 640 recorridos de compra hasta la página de pago. Todas las comprobaciones registradas pasaron, aunque el estrés incumplió el umbral de iteraciones descartadas. Por tanto, tener una columna de errores HTTP a cero no significa que todas las pruebas hayan pasado.
También he revisado el generador. En el estrés, el contenedor de k6 llegó aproximadamente al consumo de un núcleo y a unos 792 MB de memoria. No se observa una saturación global de CPU en las muestras del Mac. En la siguiente ejecución vigilaría también los momentos de ampliación de VUs y su coincidencia con las iteraciones descartadas.
La conclusión práctica que saco es que el recorrido de lectura se comporta bien en los escalones intermedios y empeora en los altos. Para dar un número de capacidad con más confianza, repetiría esos tramos durante más tiempo, ajustaría los VUs preasignados y probaría desde un punto con menor latencia. Las compras necesitarían su propia prueba de capacidad, porque hacen un trabajo diferente.
Ver los resultados en el panel web de k6
Además de la consola, k6 incluye un panel web. No hace falta instalar Grafana ni crear una cuenta para utilizarlo. Se activa con variables de entorno y permite exportar un informe HTML al terminar. Este comando vuelve a ejecutar la prueba de estrés; no abre los resultados de una ejecución anterior.
Con Docker, para verlo en directo desde el equipo anfitrión hay que publicar el puerto e indicar que el panel escuche dentro del contenedor en 0.0.0.0:
mkdir -p resultados
docker run --rm --user "$(id -u):$(id -g)" \
-v "$PWD:/k6" -w /k6 -p 127.0.0.1:5665:5665 \
-e BASE_URL=https://tu-web-de-pruebas.example \
-e K6_WEB_DASHBOARD=true -e K6_WEB_DASHBOARD_HOST=0.0.0.0 \
-e K6_WEB_DASHBOARD_EXPORT=resultados/03-estres-informe.html \
grafana/k6:2.4.0 run scripts/03-estres.js
Durante la prueba puedes abrir el panel en http://127.0.0.1:5665. El puerto se publica únicamente en la interfaz local del equipo. Al finalizar se guarda resultados/03-estres-informe.html; ese es el fichero que puedes abrir después para revisar la ejecución.
Este es el informe de estrés del laboratorio:

Y este, el de la prueba de carga:

Las pruebas muy cortas pueden terminar sin suficientes muestras para generar el informe; hay que tenerlo en cuenta si se reduce su duración.
El panel facilita consultar las métricas de k6. Para compararlas con las del VPS sigo utilizando los CSV y las marcas de inicio y fin de cada prueba. No incorpora automáticamente la CPU o la memoria del servidor remoto.
Si un navegador no muestra el HTML al abrirlo desde disco, se puede servir desde la carpeta de resultados con python3 -m http.server 8080 --bind 127.0.0.1 y consultarlo por HTTP.
Dejando el entorno como estaba
Una vez terminadas las pruebas, he parado el recolector y retirado las excepciones temporales de la aplicación y de CrowdSec. Después he comprobado que la limitación de peticiones volviera a estar activa.
Si has seguido la actividad con vmstat y docker stats, basta con salir de esos comandos con Ctrl+C. No hay que apagar el servidor ni detener los contenedores de la aplicación.
Los pedidos del entorno de pruebas quedaban pendientes y caducaban a los quince minutos, liberando las reservas. Comprobé esa caducidad y que el stock volviera al valor anterior. Este comportamiento pertenece a la aplicación del laboratorio: en otro sistema puede ser necesario limpiar esos datos de otra forma.
Los CSV y los informes también pueden contener URLs y datos del entorno. Por eso he conservado aquí las gráficas y los resultados agregados, y los ejemplos del artículo utilizan rutas y nombres genéricos.
Conclusión
Hasta aquí las pruebas con k6. Empezar con un usuario y una petición es sencillo; lo que requiere más atención es decidir qué queremos representar y cómo vamos a interpretar lo que salga.
En este laboratorio, ir aumentando la carga me ha permitido encontrar diferencias que no veía al navegar a mano: los picos que coinciden con las tareas programadas, el consumo de los distintos contenedores y las sesiones que crea una prueba sin cookies. También me he encontrado con una limitación de mi propia medición al perder parte del recolector.
Me quedo con esa forma de trabajar: comprobar primero el script, añadir carga de manera progresiva y mirar el servidor mientras se ejecuta. Después, repetir la parte que deja dudas antes de utilizar una cifra como referencia de capacidad.
¿Has utilizado k6 para probar alguna de tus webs o aplicaciones? Si tienes dudas sobre los ejemplos o quieres compartir cómo planteas tus pruebas de carga, déjame un comentario.
Logotipo de k6 obtenido del repositorio oficial de Grafana. Las marcas pertenecen a Grafana Labs y se utilizan para explicar su herramienta de código abierto. Este artículo es independiente y no está afiliado, patrocinado ni avalado por Grafana Labs.
Conversamos
Los comentarios se cargan desde Disqus cuando los abres.