Módulo 2: El script de k6 y los usuarios virtuales
7. VUs vs iteraciones y leer el resumen de k6
Descripción
Llegamos al malentendido que planteamos en la lección 1 y hemos ido rodeando: un VU no es una iteración, y ninguno de los dos es una petición. Son tres conceptos que suenan parecidos, se cuentan distinto, y confundirlos hace que interpretes mal toda una prueba de carga. Un VU es un usuario concurrente (un actor); una iteración es una vuelta completa al guion (una función terminada); una petición es una llamada HTTP (una acción dentro de la vuelta). Diez VUs, en treinta segundos, con sleep(1), producen unas trescientas iteraciones —no diez, no treinta—. Contar bien esa cadena es saber leer una prueba de carga.
La otra mitad de la lección es práctica: leer el resumen de k6. Cuando corres k6 run, al final imprime un bloque de texto lleno de métricas, y ahí es donde estos tres números —VUs, iteraciones, peticiones— aparecen juntos, junto a los checks. Aprenderás a ubicar cada uno en el resumen y a leer lo que dicen. Nos concentramos en los bloques que este módulo cubre —checks, iterations, http_reqs, vus, vus_max—; el bloque http_req_duration (las latencias y sus percentiles) lo verás en el resumen y sabrás que existe, pero su disección es el módulo 3.
Conexión con el módulo: esta lección junta todo. La aritmética VUs-vs-iteraciones usa lo de las lecciones 2 (el bucle), 5 (el think time) y 6 (vus/duration); el resumen reúne los checks de la lección 4 y todo lo demás. El resumen de k6 se muestra como contenido rotulado —real y correcto, verificado contra la documentación de k6, pero no ejecutado en esta máquina—; la aritmética la comprobamos con corridas reales del generador Python (1, 5 y 10 VUs). Todo lo rotulado como salida de Python fue medido en este entorno con Python 3.14.0. Con esta lección cierras la anatomía del script y el modelo del VU; la lección 8 lo ensambla en un mini-proyecto.
El gimnasio: máquinas, series y repeticiones
Piénsalo así. En un gimnasio hay 10 máquinas de un mismo ejercicio. Cada persona que llega usa una máquina, hace su serie, descansa un momento, y repite —una serie tras otra— durante la hora que dura su rutina. Al final del día, el gerente cuenta tres cosas distintas: cuántas máquinas hubo ocupadas a la vez (10), cuántas series se completaron en total (cientos, sumando las de todas las personas a lo largo de la hora), y cuántas repeticiones individuales se hicieron (miles, si cada serie son varias repeticiones). Nadie confundiría "10 máquinas" con "300 series" con "3000 repeticiones": son tres medidas de tres cosas.
En k6 es idéntico. Los VUs son las máquinas: cuántos usuarios concurrentes hay a la vez (10). Las iteraciones son las series: cuántas vueltas completas al guion se hicieron en total, sumando las de todos los VUs durante toda la duración (cientos). Las peticiones (http_reqs) son las repeticiones: cuántas llamadas HTTP se dispararon en total (si cada iteración hace una petición, iteraciones y peticiones coinciden; si hace tres, hay el triple de peticiones). Tres números, tres cosas. El VU es la concurrencia; la iteración, el trabajo completado; la petición, la acción individual.
VUs, iteraciones y peticiones son tres cosas distintas. VUs = usuarios concurrentes (las máquinas del gimnasio). Iteraciones = vueltas completas al guion, sumando todos los VUs (las series). Peticiones = llamadas HTTP individuales (las repeticiones). 10 VUs no son 10 iteraciones ni 10 peticiones: producen cientos de cada una a lo largo del tiempo.
La aritmética, comprobada con corridas reales
La relación entre los tres números no es misteriosa; es una fórmula que puedes estimar de antemano. Con un guion que hace una petición por vuelta y un sleep(t), cada VU completa aproximadamente 1 / (t + tiempo_de_petición) iteraciones por segundo. Como el tiempo de petición es diminuto frente al sleep, con sleep(1) cada VU hace ~1 iteración por segundo, y el total es:
iteraciones ≈ vus × duración / (think_time + tiempo_de_petición)
≈ vus × duración (cuando think_time domina, p. ej. sleep(1))
Comprobémoslo con tres corridas reales del generador, todas con sleep(1), subiendo la escala. Primero 1 VU / 5 s:
Qué esperar. 1 × 5 = ~5 iteraciones. Salida real:
vus............: 1
iterations.....: 5 (1.0/s)
checks.........: 100.00% (10 de 10)
Ahora 5 VUs / 4 s:
Qué esperar. 5 × 4 = ~20 iteraciones. Salida real:
vus............: 5
iterations.....: 20 (4.9/s)
checks.........: 100.00% (40 de 40)
Y 10 VUs / 10 s:
Qué esperar. 10 × 10 = ~100 iteraciones. Salida real:
vus............: 10
iterations.....: 100 (9.8/s)
checks.........: 100.00% (200 de 200)
Léelas juntas, porque desarman el malentendido de un golpe:
- VUs ≠ iteraciones. En ningún caso el número de iteraciones es igual al de VUs. 1 VU → 5 iteraciones; 5 VUs → 20; 10 VUs → 100. El VU es quién; la iteración es cuántas vueltas dio.
- La fórmula se cumple. 1×5=5, 5×4=20, 10×10=100. Con
sleep(1), iteraciones ≈vus × duración. Predecible. - Los checks son el doble de las iteraciones. 5 iteraciones → 10 checks; 20 → 40; 100 → 200. Porque cada iteración hace 2 checks (status y precio, lección 4). Peticiones, iteraciones y checks son tres conteos distintos ligados por el guion: 1 petición por iteración, 2 checks por iteración.
iterations/sescala con los VUs. 1.0/s → 4.9/s → 9.8/s. Cada VU aporta ~1 iteración/s (por elsleep(1)), así que el ritmo total es ~vus/s. Duplica los VUs y duplicas el ritmo.
Con esto, nunca más deberías leer "vus: 10" y anotar "10 peticiones". Diez VUs son diez actores que, a lo largo de la duración, completan cientos de vueltas y disparan cientos de peticiones.
El resumen de k6, leído línea por línea (contenido)
Ahora la otra mitad: cómo se ve todo esto en el resumen de k6. Cuando corres k6 run quote_test.js con el script de 10 VUs / 30 s y sleep(1) que armamos en el módulo, k6 imprime al final un bloque como este. Se muestra como contenido de referencia —correcto y verificado contra la documentación de k6, pero k6 no está instalado en este entorno, así que no lo ejecutamos aquí—:
/\ Grafana /‾‾/
/\ / \ |\ __ / /
/ \/ \ | |/ / / ‾‾\
/ \ | ( | (‾) |
/ __________ \ |_|\_\ \_____/
execution: local
script: quote_test.js
output: -
scenarios: (100.00%) 1 scenario, 10 max VUs, 30s max duration (incl. graceful stop):
* default: 10 looping VUs for 30s (gracefulStop: 30s)
✓ status is 200
✓ price is 7500
checks.........................: 100.00% ✓ 600 ✗ 0
data_received..................: 61 kB 2.0 kB/s
data_sent......................: 59 kB 2.0 kB/s
http_req_blocked...............: avg=9µs min=1µs med=3µs max=1.2ms p(90)=6µs p(95)=10µs
http_req_connecting............: avg=2µs min=0s med=0s max=0.5ms p(90)=0s p(95)=0s
http_req_duration..............: avg=2.1ms min=0.5ms med=1.7ms max=110ms p(90)=3.2ms p(95)=4.6ms
{ expected_response:true }...: avg=2.1ms min=0.5ms med=1.7ms max=110ms p(90)=3.2ms p(95)=4.6ms
http_req_failed................: 0.00% ✓ 0 ✗ 300
http_req_receiving.............: avg=40µs min=10µs med=30µs max=0.9ms p(90)=70µs p(95)=95µs
http_req_sending...............: avg=20µs min=5µs med=14µs max=0.5ms p(90)=35µs p(95)=50µs
http_req_waiting...............: avg=2.0ms min=0.5ms med=1.6ms max=109ms p(90)=3.1ms p(95)=4.4ms
http_reqs......................: 300 9.97/s
iteration_duration.............: avg=1.0s min=1.0s med=1.0s max=1.11s p(90)=1.0s p(95)=1.0s
iterations.....................: 300 9.97/s
vus............................: 10 min=10 max=10
vus_max........................: 10 min=10 max=10
running (0m30.1s), 00/10 VUs, 300 complete and 0 interrupted iterations
default ✓ [======================================] 10 VUs 30s
Es mucho texto, pero solo unas pocas líneas nos importan en este módulo. Vamos por ellas:
El encabezado (qué corrió).
scenarios: ... 1 scenario, 10 max VUs, 30s max durationy* default: 10 looping VUs for 30s— k6 te dice cómo montó la corrida a partir de tuoptions: un escenario que corre la funcióndefaultcon 10 VUs "en bucle" (looping VUs) durante 30 s. Ahí está el modelo del VU descrito por el propio k6: VUs que hacen loop sobredefault.
Los checks (lección 4).
✓ status is 200y✓ price is 7500— los nombres que pusiste en tucheck, cada uno con su marca. La✓significa que ese criterio pasó siempre.checks.........: 100.00% ✓ 600 ✗ 0— el conteo total. 600 checks, todos pasaron, ninguno falló. ¿Por qué 600? Porque hubo 300 iteraciones × 2 checks por iteración = 600. Reconoces el patrón de las corridas de Python (checks = 2 × iteraciones).
Las tres cifras del malentendido.
http_reqs......: 300 9.97/s— las peticiones: 300 llamadas HTTP en total, a ~10 por segundo. Como el guion hace una petición por iteración, coincide con las iteraciones.iterations.....: 300 9.97/s— las iteraciones: 300 vueltas completas adefault. Fíjate: no son 10 (los VUs) ni 30 (los segundos). Son ~vus × duración= 10 × 30 = 300, la misma fórmula que comprobaste en Python.vus............: 10 min=10 max=10yvus_max...: 10— los VUs: 10 usuarios concurrentes, y se mantuvieron en 10 todo el tiempo (carga plana,min=max=10). Aquívuses la concurrencia, distinta de las 300 iteraciones. Las tres cifras —10, 300, 300— son tres cosas.
Lo que existe pero es del módulo 3.
http_req_duration..: avg=2.1ms ... p(90)=3.2ms p(95)=4.6ms— la latencia de las peticiones, con su promedio, mínimo, mediana, máximo y percentiles. Este es el bloque que estudia el módulo 3: por qué elavg(2.1 ms) puede engañarte mientras elmax(110 ms) y elp(95)cuentan otra historia. Aquí solo ubícalo: es el tiempo de las peticiones, y los percentiles son M3.http_req_failed..: 0.00% ✓ 0 ✗ 300— la tasa de error de las peticiones (0 % fallaron). También M3.iteration_duration..: avg=1.0s— cuánto duró cada vuelta completa: ~1 segundo, casi todo por elsleep(1). Contrasta conhttp_req_duration(2.1 ms): la iteración dura mil veces más que la petición porque incluye el think time. Esa diferencia —petición vs iteración— es la de la lección 5, ahora visible en el resumen.
El pie (el progreso).
running (0m30.1s), 00/10 VUs, 300 complete and 0 interrupted iterations— el resumen del progreso: corrió 30.1 s, terminó con 0 VUs activos, y completó 300 iteraciones sin interrumpir ninguna. Eldefault ✓ [===] 10 VUs 30ses la barra de progreso al final.
Si de todo este bloque te llevas cinco líneas, que sean estas: checks (¿respondió bien?), http_reqs (¿cuántas llamadas?), iterations (¿cuántas vueltas?), vus (¿cuántos concurrentes?) y —guardado para M3— http_req_duration (¿qué tan rápido?). Con esas cinco lees el 90 % de cualquier prueba de carga.
Por qué el resumen de Python se parece al de k6 (a propósito)
Habrás notado que el resumen del generador Python imita la forma del de k6: un bloque de vus, iterations, checks. Es deliberado. El generador modela el mismo mundo —VUs en bucle, iteraciones, checks— y reporta las mismas cifras, para que el puente entre "lo que k6 haría" y "lo que medimos" sea directo. Las diferencias son honestas: el resumen de Python no calcula percentiles ni separa http_req_waiting de http_req_sending (eso es maquinaria de k6, y las métricas a fondo son M3); solo reporta lo que este módulo necesita —cuántos VUs, cuántas iteraciones, cuántos checks pasaron—. Cuando en el módulo 3 el generador empiece a calcular p95 y RPS con statistics, su resumen se acercará aún más al de k6. Por ahora, las columnas que comparten —vus, iterations, checks— ya te dejan leer ambos con los mismos ojos.
Errores comunes
Leer vus: 10 como "10 peticiones" o "10 iteraciones". Qué pasa: alguien reporta "corrí 10 usuarios" y asume que hubo 10 llamadas a la API. Por qué pasa: confunde la concurrencia con el volumen. Cómo detectarlo: sus cuentas no cuadran con http_reqs ni iterations del resumen, que son mucho mayores. Cómo corregirlo: vus es cuántos corren a la vez; iterations y http_reqs son cuánto trabajo hicieron en total a lo largo de la duración. Diez VUs / 30 s con sleep(1) ≈ 300 iteraciones, no 10.
Confundir iterations con http_reqs. Qué pasa: alguien ve que ambos dicen 300 y cree que siempre son iguales. Por qué pasa: en el ejemplo el guion hace una petición por iteración, así que coinciden. Cómo detectarlo: en un guion que hace varias peticiones por vuelta (cotizar y reservar, por ejemplo), http_reqs es mayor que iterations. Cómo corregirlo: recuerda que iterations cuenta vueltas al guion y http_reqs cuenta llamadas HTTP; su relación es "peticiones por iteración". Coinciden solo cuando hay exactamente una petición por vuelta.
Fijarse en el avg de http_req_duration e ignorar el max y el p(95). Qué pasa: alguien lee avg=2.1ms, concluye "rapidísimo", y no ve el max=110ms. Por qué pasa: el promedio es lo más visible y lo más tranquilizador. Cómo detectarlo: el avg se ve bien pero el max o el p(95) son mucho mayores —señal de que algunas peticiones fueron lentas—. Cómo corregirlo: en este módulo basta con ubicar el bloque http_req_duration; su lectura a fondo —por qué el promedio miente y el p95 manda— es el módulo 3. Pero ya nota el patrón: un avg bajo con un max alto esconde una cola de peticiones lentas.
Ejercicios
Ejercicio 1 — Los tres números. En el resumen de k6 de la lección aparecen vus: 10, iterations: 300 y http_reqs: 300. (a) ¿Qué mide cada uno, en una frase? (b) ¿Por qué iterations es 300 y no 10? (c) ¿Por qué http_reqs coincide con iterations en este caso, y cuándo no coincidiría?
Ver solución
- (a)
vus= usuarios virtuales concurrentes (cuántos corren a la vez);iterations= vueltas completas a la funcióndefault(cuánto trabajo se completó en total);http_reqs= llamadas HTTP disparadas en total. - (b) Porque los 10 VUs corren 30 segundos con
sleep(1), y cada VU hace ~1 iteración por segundo: 10 × 30 ≈ 300 iteraciones. Los VUs son 10; las vueltas que dieron entre todos son 300. - (c) Coincide porque el guion hace una petición (
http.posta/quote) por iteración: 300 iteraciones × 1 petición = 300 peticiones. No coincidiría si cada iteración hiciera varias peticiones —por ejemplo, cotizar y reservar serían 2 peticiones por vuelta, yhttp_reqssería ~600 coniterationsen 300—.
Ejercicio 2 — Predice y lee. Vas a correr un guion con sleep(1) y options = { vus: 20, duration: '10s' }, con 1 petición y 2 checks por iteración. (a) ¿Cuántas iteraciones esperas? (b) ¿Cuántas peticiones (http_reqs)? (c) ¿Cuántos checks totales?
Ver solución
- (a) ~
vus × duración= 20 × 10 = ~200 iteraciones. - (b) Una petición por iteración → ~200 peticiones (
http_reqs). - (c) Dos checks por iteración → 200 × 2 = ~400 checks totales. Si todos pasan, el resumen diría
checks: 100.00% ✓ 400 ✗ 0.
(La corrida real de 20 VUs / 10 s la verás en el mini-proyecto de la lección 8, con think time de 500 ms —así que las iteraciones serán más que 200, porque el ritmo es más rápido con menos pausa—.)
Ejercicio 3 — Ubica en el resumen. Un compañero corrió una prueba y quiere responder cuatro preguntas mirando el resumen de k6. ¿En qué línea encuentra cada respuesta? (a) "¿Cuántos usuarios concurrentes hubo?" (b) "¿Qué porcentaje de las respuestas fue correcto?" (c) "¿Cuántas llamadas a la API se hicieron en total?" (d) "¿Cuál fue la petición más lenta?"
Ver solución
- (a) En la línea
vus(yvus_max):vus: 10 min=10 max=10→ 10 usuarios concurrentes. - (b) En la línea
checks:checks: 100.00% ✓ 600 ✗ 0→ el 100 % de los criterios pasó. - (c) En la línea
http_reqs:http_reqs: 300 9.97/s→ 300 llamadas en total. - (d) En el
maxde la líneahttp_req_duration:max=110ms→ la petición más lenta tardó 110 ms. (Leer bien esta línea —promedio vs máximo vs percentiles— es el módulo 3; aquí solo la ubicas.)
Resumen y siguiente paso
En esta lección desarmaste el malentendido central de k6: VUs, iteraciones y peticiones son tres cosas distintas. VUs son usuarios concurrentes (las máquinas del gimnasio), iteraciones son vueltas completas al guion sumando todos los VUs (las series), y peticiones son llamadas HTTP individuales (las repeticiones). Lo comprobaste con corridas reales —1 VU → 5 iteraciones, 5 → 20, 10 → 100— que confirman la fórmula iteraciones ≈ vus × duración (con sleep(1)) y que los checks son 2 × iteraciones. Y aprendiste a leer el resumen de k6 (como contenido): a ubicar checks, http_reqs, iterations, vus y vus_max, y a reconocer que el bloque http_req_duration —las latencias y sus percentiles— existe pero es materia del módulo 3.
Antes de avanzar deberías poder: explicar la diferencia entre un VU, una iteración y una petición sin dudar; estimar las tres cifras de una carga plana a partir de vus, duration y el think time; y encontrar en un resumen de k6 las líneas de checks, iteraciones, peticiones y VUs.
Con esto cierras la anatomía del script y el modelo del VU: sabes qué hace cada pieza y cómo leer lo que producen. La lección 8 es el mini-proyecto: escribirás un script k6 completo para /quote (contenido, con su resumen) y su equivalente ejecutable en Python —N VUs con ThreadPoolExecutor, check de status y precio, conteo de iteraciones y errores— y lo correrás de verdad contra la API canónica, cerrando el módulo con las dos caras en tus manos.
Recursos
- El modelo de ejecución de k6 — VUs e iteraciones — la referencia que distingue usuarios virtuales de iteraciones. El fundamento de la aritmética de esta lección.
- Métricas integradas de k6 — la lista de todas las métricas del resumen (
checks,http_reqs,iterations,vus,http_req_duration...) y qué mide cada una. La leyenda del bloque que leíste. - El resumen de fin de test (end-of-test summary) — cómo se compone el bloque que k6 imprime al terminar. La fuente del resumen mostrado como contenido.
statistics.quantiles— Documentación de Python — los percentiles que el generador aún no calcula y que el módulo 3 usará para acercar su resumen al de k6. El puente hacia las métricas a fondo.