Módulo 6: Checks, groups y escenarios realistas
7. Think time realista con jitter
Descripción
Has usado sleep() desde el módulo 2 como "think time": la pausa que imita a un usuario pensando entre acciones. Sin esa pausa, un VU dispara peticiones tan rápido como el servidor pueda contestar —un martillo automático, no una persona—. Esta lección cierra el módulo afinando ese think time con dos ideas que lo hacen realista de verdad. La primera ya la conoces a medias: el think time gobierna el ritmo, y sin él la prueba mide una tormenta que ningún usuario real produciría. La segunda es nueva y sutil: si todos los VUs pausan exactamente lo mismo, se sincronizan y crean picos artificiales —marchan en formación militar, todos pidiendo en el mismo instante—, lo cual tampoco es realista. La solución a lo segundo es el jitter: hacer la pausa aleatoria dentro de un rango, para que los VUs se desincronicen como se desincronizan los usuarios reales.
El jitter es una idea pequeña con un efecto grande. En vez de sleep(1) exacto, pausas un tiempo aleatorio alrededor de un valor base —por ejemplo, entre 0.5 y 1.5 segundos, o sea 1 segundo ± 50%—. Cada VU, en cada iteración, espera un tiempo distinto. El resultado es que las peticiones se reparten en el tiempo en vez de agolparse en picos. Piénsalo: sin jitter, si 50 VUs arrancan a la vez y todos pausan exactamente 1 segundo, disparan sus peticiones en oleadas sincronizadas —50 de golpe, un segundo de silencio, 50 de golpe—. Con jitter, esas 50 peticiones se esparcen a lo largo del segundo, un goteo continuo, como el tráfico real. En esta lección ves medido cómo el think time gobierna el ritmo (con vs sin) y cómo el jitter reparte las pausas.
Conexión con el módulo: el think time con jitter es la última pieza del escenario realista, el toque que le da ritmo humano. En k6 se escribe con sleep(Math.random() * ...) (contenido); en Python, con time.sleep() sobre un valor aleatorio (ejecutado). Verás dos cosas medidas en este entorno con Python 3.14.0: el mismo escenario cotizar → reservar → confirmar corriendo sin pausa (un martillo) vs con think time realista (un ritmo humano), y la distribución de las pausas jittered. La frontera: aquí el think time modula el ritmo de un VU; cómo sube y baja el número de VUs en el tiempo son los perfiles de carga del módulo 4.
El semáforo mal sincronizado
Piensa en el tráfico de una avenida con varios semáforos. Si todos los semáforos cambian a verde exactamente al mismo tiempo, pasa algo malo: todos los coches arrancan de golpe, avanzan en un pelotón apretado hasta el siguiente semáforo, se detienen todos juntos, y arrancan todos juntos otra vez. El tráfico se mueve en oleadas: momentos de saturación total seguidos de calles vacías. Es el peor de los mundos —ni fluido ni parejo—. Los ingenieros de tráfico lo saben, y por eso desincronizan los semáforos a propósito: los ponen a cambiar con pequeños desfases, para que los coches se repartan a lo largo de la avenida en un flujo continuo en vez de en pelotones.
Los VUs sin jitter son los semáforos sincronizados. Si 50 VUs arrancan juntos y todos pausan sleep(1) exacto, sus peticiones salen en pelotones: 50 de golpe, silencio, 50 de golpe. El servidor ve oleadas artificiales de saturación que ningún tráfico real produce. El jitter es desincronizar los semáforos: cada VU pausa un tiempo ligeramente distinto (aleatorio dentro de un rango), así que sus peticiones se reparten en un goteo continuo, como el tráfico real de usuarios que no están coordinados entre sí. Un usuario no consulta su reloj para pedir en el mismo milisegundo que otro; cada uno va a su ritmo. El jitter reproduce esa falta de coordinación.
El think time gobierna el ritmo de un VU: sin él, dispara peticiones como un martillo, no como una persona. Y si todos los VUs pausan exactamente lo mismo, se sincronizan en picos artificiales, como semáforos que cambian todos a la vez. El jitter —hacer la pausa aleatoria dentro de un rango— los desincroniza, repartiendo las peticiones en un flujo continuo como el tráfico real de usuarios no coordinados.
El think time con jitter en k6 (contenido)
En k6, el think time es sleep(segundos), y el jitter se logra pasándole un valor aleatorio en vez de una constante. Math.random() da un número entre 0 y 1; con un poco de aritmética lo conviertes en un rango:
// think_time_jitter.js - think time con jitter entre pasos.
// MOSTRADO COMO CONTENIDO: k6 no esta instalado en este entorno.
import http from 'k6/http';
import { check, group, sleep } from 'k6';
const BASE_URL = 'http://localhost:8000';
const params = { headers: { 'Content-Type': 'application/json' } };
// Pausa aleatoria en [min, max] segundos: base +/- jitter.
function thinkTime(min, max) {
return Math.random() * (max - min) + min; // p.ej. thinkTime(0.5, 1.5)
}
export default function () {
group('quote', function () {
const body = JSON.stringify({ room: 'Focus', tier: 'basic', hours: 3 });
const res = http.post(`${BASE_URL}/quote`, body, params);
check(res, { 'status is 200': (r) => r.status === 200 });
});
sleep(thinkTime(0.5, 1.5)); // think time JITTERED: 1s +/- 50%, distinto por VU/iteracion
group('book', function () {
const body = JSON.stringify({ room: 'Focus', tier: 'basic', hours: 3 });
const res = http.post(`${BASE_URL}/book`, body, params);
check(res, { 'status is 200': (r) => r.status === 200 });
});
sleep(thinkTime(0.5, 1.5)); // otra pausa jittered antes de la siguiente iteracion
}
Desármalo:
Math.random() * (max - min) + min. La fórmula estándar del jitter.Math.random()da[0, 1); multiplicarlo por(max - min)lo estira al ancho del rango; sumarminlo desplaza al inicio del rango.thinkTime(0.5, 1.5)da un número aleatorio entre 0.5 y 1.5 segundos —un segundo de base con ±50% de jitter—.sleep(thinkTime(0.5, 1.5))entre pasos. Cada vez que se ejecuta, el número es distinto, así que dos VUs (o dos iteraciones del mismo VU) casi nunca pausan lo mismo. Eso es lo que los desincroniza.- La pausa va entre grupos, no dentro (como viste en la lección 4): el think time es tiempo del usuario pensando, no del paso ejecutándose, y no debe inflar el
group_duration.
Una nota sobre magnitudes: 0.5–1.5 s es un ejemplo. El think time real depende de qué hace el usuario —leer una lista de salas puede llevar varios segundos; confirmar un clic, una fracción—. Lo que no cambia es la técnica: una pausa con jitter, centrada en un valor realista para esa acción.
El efecto del think time, medido (ejecutado)
Veamos primero por qué el think time importa tanto, con la medición más contundente del módulo. Corramos el mismo escenario cotizar → reservar → confirmar de dos formas, con la misma carga (3 VUs / 3 s): una sin pausa (think 0, el martillo) y otra con think time realista con jitter (think 200 ± 50%).
Qué esperar. Sin pausa, cada VU dispara iteraciones tan rápido como pueda: muchísimas por segundo. Con think time, cada VU espera ~200 ms entre iteraciones, bajando el ritmo drásticamente. Salida real en este entorno:
--- think 0 (sin pausa: el martillo) ---
vus............: 3
duracion real..: 3.00s
iterations.....: 6477 (2158.6/s)
checks.........: 100.00% (51816 de 51816)
...
por grupo (latencia media, n llamadas):
group 'quote '.....: n=6477 avg=0.47ms
group 'book '.....: n=6477 avg=0.47ms
group 'confirm'.....: n=6477 avg=0.44ms
--- think 200 (con jitter +/-50%: ritmo realista) ---
vus............: 3
duracion real..: 3.25s
iterations.....: 43 (13.2/s)
checks.........: 100.00% (344 de 344)
...
por grupo (latencia media, n llamadas):
group 'quote '.....: n=43 avg=1.52ms
group 'book '.....: n=43 avg=0.65ms
group 'confirm'.....: n=43 avg=0.54ms
La diferencia es enorme y hay que leerla con cuidado:
think 0: 6477 iteraciones, 2158.6/s. Sin pausa, 3 VUs produjeron 6477 recorridos completos en 3 segundos —más de 2000 iteraciones por segundo—. Eso es un martillo: tres "usuarios" disparando el flujo de tres pasos a máxima velocidad, sin parar a pensar. Ningún humano hace eso. Con solo 3 VUs, generaste una carga que en usuarios reales requeriría muchísimos más.think 200: 43 iteraciones, 13.2/s. Con think time de ~200 ms entre pasos, los mismos 3 VUs produjeron 43 iteraciones —13 por segundo—. Cada VU pasa la mayor parte del tiempo esperando (pensando), como un usuario real. El ritmo cayó ~160 veces respecto al martillo.- La lección del think time. El mismo número de VUs produce cargas radicalmente distintas según el think time. Por eso 3 VUs no significan "3 usuarios/segundo": significan 3 usuarios concurrentes, y cuántas peticiones/segundo generan depende de cuánto piensan entre acciones. Un think time realista es lo que convierte "N VUs" en "una tasa de peticiones creíble". Sin él, sobrestimas brutalmente la carga que N usuarios producen (es la ley de Little que viste en el módulo 3, ahora con el flujo completo).
Fíjate en un detalle honesto: sin pausa (think 0), la latencia por grupo bajó (quote 0.47ms vs 1.52ms con think). No es que el martillo sea "más eficiente": es que a 2158 iteraciones/s el servidor está en un régimen distinto, y los promedios de latencia se miden sobre un volumen enorme de peticiones idénticas y calientes. Es otra cara del mismo problema: sin think time realista, todos los números salen distorsionados.
El jitter reparte las pausas, medido (ejecutado)
Ahora veamos qué hace el jitter con las pausas. Miramos la distribución de las pausas jittered de un think time base de 200 ms con ±50%, sobre 1000 muestras:
# La forma del jitter: pausa base +/- 50%, aleatoria cada vez.
def jittered(base_ms, rng):
return base_ms * rng.uniform(0.5, 1.5) # base +/- 50%
Qué esperar. Las pausas deben repartirse entre 100 ms (base × 0.5) y 300 ms (base × 1.5), con una media cerca de 200 ms, y no concentrarse en un solo valor. Salida real en este entorno:
think base=200ms, jitter +/-50%, 1000 muestras:
min=100.0ms avg=196.3ms max=299.8ms
reparto por tercio del rango: [370, 316, 314] (todas distintas, no en fila)
Léela:
min=100.0ms,max=299.8ms— las pausas van de 100 a 300 ms, exactamente el rango200 ± 50%. Ninguna pausa cae fuera; el jitter está acotado.avg=196.3ms— la media está cerca de los 200 ms de base (no exactamente, porque 1000 muestras es una muestra finita). En promedio, el think time sigue siendo ~200 ms; el jitter no cambia el centro, solo lo esparce.reparto por tercio del rango: [370, 316, 314]— de las 1000 pausas, 370 cayeron en el tercio bajo (100–167 ms), 316 en el medio, 314 en el alto. Repartidas por todo el rango, no amontonadas en un valor. Esto es lo que desincroniza a los VUs: como cada uno espera un tiempo distinto (100, 167, 234, 289… ms), sus peticiones no salen en pelotón sino esparcidas.
Contrasta con un think time sin jitter (sleep(0.2) exacto): las 1000 pausas serían todas 200 ms, y VUs que arrancaran juntos seguirían juntos para siempre, disparando en oleadas. El jitter rompe esa formación. La media es la misma (~200 ms), pero el reparto es lo que le da realismo: usuarios no coordinados, un goteo continuo en vez de pelotones.
Errores comunes
Correr la prueba sin think time y creer que N VUs = N usuarios. Qué pasa: alguien corre 10 VUs sin sleep y reporta "probé con 10 usuarios". Por qué pasa: se confunde un VU (que sin pausa martillea) con un usuario (que piensa entre acciones). Cómo detectarlo: una tasa de iteraciones/s absurdamente alta (miles/s con pocos VUs), como el think 0 de 2158/s. Cómo corregirlo: pon un think time realista entre pasos. Sin él, tus N VUs generan la carga de cientos de usuarios reales, y sobrestimas brutalmente lo que tu sistema aguanta "por usuario".
Usar el mismo sleep exacto en todos los VUs. Qué pasa: alguien pone sleep(1) fijo, y bajo muchos VUs el servidor ve oleadas sincronizadas de peticiones (picos artificiales). Por qué pasa: un valor constante es lo primero que uno escribe. Cómo detectarlo: patrones de carga en pelotón —ráfagas periódicas seguidas de silencios— que no se parecen al tráfico real. Cómo corregirlo: añade jitter (sleep(thinkTime(0.5, 1.5))), una pausa aleatoria dentro de un rango, para desincronizar los VUs. Los semáforos van desfasados, no todos a verde a la vez.
Meter el think time dentro del group() (otra vez). Qué pasa: alguien pone el sleep dentro del bloque de un paso, y el group_duration de ese paso sale inflado por la pausa. Por qué pasa: se junta "todo el paso" en un bloque. Cómo detectarlo: un grupo con latencia ≈ el valor del think time. Cómo corregirlo: el think time va entre grupos. Es tiempo del usuario pensando, no del paso ejecutándose; no debe contar en la medición del paso. (Ya lo viste en la lección 4; con jitter aplica igual.)
Ejercicios
Ejercicio 1 — Escribe un think time con jitter. Escribe (en k6, como contenido) una función thinkTime(min, max) que devuelva una pausa aleatoria en segundos, y úsala para un think time de 2 segundos ± 50% (o sea, entre 1 y 3 segundos). ¿Qué llamada a sleep pondrías?
Ver solución
function thinkTime(min, max) {
return Math.random() * (max - min) + min;
}
// 2 segundos +/- 50% = rango [1, 3]:
sleep(thinkTime(1, 3));
Math.random() * (3 - 1) + 1 da un número en [1, 3). La base es 2 s (el centro del rango) y el jitter es ±1 s (±50%).
Ejercicio 2 — Lee el efecto del think time. En las corridas reales, think 0 dio 6477 iteraciones (2158.6/s) y think 200 dio 43 (13.2/s), ambas con 3 VUs. (a) ¿Por qué la misma cantidad de VUs produce tasas tan distintas? (b) Si quisieras que 3 VUs generaran ~30 iteraciones/s, ¿subirías o bajarías el think time respecto a 200 ms? (c) ¿Qué significa esto para interpretar "probé con 3 VUs"?
Ver solución
- (a) Porque el think time gobierna cuánto espera cada VU entre iteraciones. Sin pausa (
think 0), cada VU itera tan rápido como el servidor responde (submilisegundo → miles/s). Conthink 200, cada VU espera ~200 ms por iteración, así que 3 VUs dan ~15/s. La tasa depende del think time, no solo de los VUs. - (b) Bajaría el think time. A menor pausa, cada VU itera más seguido, así que la tasa sube. Con 3 VUs y ~200 ms salen ~13/s; para ~30/s harían falta pausas más cortas (del orden de ~100 ms), o más VUs.
- (c) Que "3 VUs" no dice cuántas peticiones/s se generan: dice cuántos usuarios concurrentes hay. La tasa real depende del think time. Reportar una prueba de carga exige decir VUs y think time (o directamente la tasa de iteraciones/s), nunca solo los VUs.
Ejercicio 3 — Los semáforos. Un compañero corre 100 VUs con sleep(1) exacto y ve que el servidor recibe las peticiones en oleadas: ráfagas cada segundo, con silencios entre medias. (a) Explícale por qué pasa, con la analogía de los semáforos. (b) ¿Qué cambio de una línea lo arreglaría? (c) ¿Cambiaría eso el promedio del think time?
Ver solución
- (a) Sus 100 VUs son semáforos sincronizados: arrancaron juntos y todos pausan exactamente 1 segundo, así que disparan sus peticiones en el mismo instante, esperan 1 segundo todos juntos, y vuelven a disparar juntos. El resultado son oleadas (100 peticiones de golpe, silencio, 100 de golpe), un patrón artificial que el tráfico real no tiene.
- (b) Cambiar
sleep(1)por una pausa con jitter, por ejemplosleep(thinkTime(0.5, 1.5))(1 s ± 50%). Cada VU esperaría un tiempo distinto, desincronizándolos, y las peticiones se repartirían en un goteo continuo. - (c) No (o casi no). El jitter mantiene el think time centrado en ~1 s de promedio; solo lo esparce. Como se vio en la medición, base 200 ms con jitter dio avg=196.3 ms —prácticamente la base—. El jitter cambia el reparto de las pausas, no su centro.
Resumen y siguiente paso
En esta lección pusiste el último toque de realismo: el think time con jitter. El think time gobierna el ritmo de un VU —lo viste medido: el mismo escenario con 3 VUs produjo 2158 iteraciones/s sin pausa (un martillo) contra 13/s con think time realista, una diferencia de ~160×—, lo que prueba que "N VUs" no dice la carga sin decir también cuánto piensan. Y el jitter —hacer la pausa aleatoria dentro de un rango (base ± 50%)— desincroniza los VUs para que no disparen en pelotones, como semáforos desfasados: la distribución real de un think time de 200 ms mostró pausas repartidas de 100 a 300 ms (avg 196.3 ms), esparcidas por todo el rango en vez de amontonadas. La media no cambia; el reparto sí, y eso es lo que da el goteo continuo del tráfico real.
Antes de avanzar deberías poder: escribir un think time con jitter (Math.random() * (max - min) + min); explicar por qué sin think time N VUs sobrestiman la carga; explicar por qué un sleep fijo sincroniza los VUs en picos artificiales y el jitter los reparte; y reportar una prueba con VUs y think time, nunca solo VUs.
Con esto tienes las cuatro piezas del escenario realista —checks de corrección, groups, parametrización, correlación— más el think time con jitter. La lección 8 las junta todas en el mini-proyecto: correr el escenario cotizar → reservar → confirmar parametrizado contra la API canónica, con checks en cada paso y el booking_id correlacionado, reportando la tasa de checks y las métricas por grupo. Es tu graduación del módulo: la pieza de escenario completa, ejecutada, lista para analizar y llevar a CI en el módulo 7.
Recursos
sleepy think time en k6 — la referencia desleep(), la pausa que modela el think time. Cómo se introduce el ritmo humano en un VU.- Escenarios y ritmo de ejecución en k6 — cómo el think time y la concurrencia determinan la tasa de peticiones que produce una prueba. El marco de por qué N VUs no es una tasa sin el think time.
random.Random.uniform— documentación de Python — la función con la que el generador produce la pausa jittered (un flotante aleatorio en un rango). El equivalente ejecutado deMath.random() * (max - min) + min.- Google SRE Book — Addressing Cascading Failures — el marco de por qué las peticiones sincronizadas (thundering herd) son un problema real y por qué el jitter es una defensa estándar. El fondo conceptual de desincronizar los VUs.