Módulo 2: El script de k6 y los usuarios virtuales

5. `sleep()` y el *think time*

Descripción

Hay una línea que ha aparecido en casi todos los scripts de este módulo y que hemos usado sin detenernos: sleep(1). Parece trivial —"espera un segundo"— pero es una de las decisiones más importantes de una prueba de carga. Esa pausa es el think time: el tiempo que un humano tarda entre una acción y la siguiente —leer la pantalla, decidir, mover el ratón—. Sin ella, tu usuario virtual deja de parecerse a una persona y se convierte en un martillo neumático que golpea la API miles de veces por segundo. Y una prueba que mide un martillo neumático no te dice cómo se comportará tu sistema con usuarios reales.

La diferencia es enorme y medible. Un mismo VU, sin sleep, puede hacer miles de iteraciones en unos segundos; con un sleep(1), hace una por segundo. No es un ajuste cosmético: cambia por completo la carga que tu prueba impone y, por tanto, las conclusiones que sacas. En esta lección entendemos qué es el think time, por qué casi siempre lo quieres, cuándo no lo quieres, y lo comprobamos con el mismo VU corriendo con y sin pausa —viendo cómo sleep gobierna el ritmo del bucle—.

Conexión con el módulo: en la lección 2 viste que el bucle del VU es abierto —va tan rápido como dure cada vuelta— y que quitar el sleep disparaba las iteraciones. Esta lección explica por qué eso importa y cómo usar sleep para modelar carga realista. El sleep() de k6 se muestra como contenido; el time.sleep() de Python, su equivalente exacto, se ejecuta de verdad en el generador, y verás el mismo VU dar 13 598 vueltas sin pausa y 6 con sleep(0.5). Todo lo rotulado como salida de Python fue medido en este entorno con Python 3.14.0. El ritmo de la carga se afina aquí; los perfiles de carga (subir y bajar VUs en el tiempo) son el módulo 4.

El cliente que lee el menú vs. el que grita pedidos

Piénsalo así. Vuelve al restaurante de la lección 3. Un cliente real llama, pregunta por el menú, se toma unos segundos para pensar, dice su pedido, cuelga; un rato después vuelve a llamar para pedir el postre. Entre cada acción hay pausas humanas: lee, decide, habla. La cocina, con cien clientes así, tiene un flujo manejable —las llamadas llegan espaciadas—.

Ahora imagina un cliente que, en cuanto cuelga, vuelve a marcar al instante, sin pausa, gritando pedidos tan rápido como el teléfono lo permita: mil pedidos por minuto, uno solo de él. Ese cliente no existe en la vida real, pero es exactamente lo que hace un VU sin think time: repite el guion a máxima velocidad, sin las pausas que un humano tendría. Si pruebas tu cocina con clientes así, mides algo que nunca ocurrirá —una avalancha irreal— y sacas conclusiones equivocadas: creerás que tu sistema aguanta muchos menos "usuarios" de los que en realidad soportaría, porque cada VU-martillo pesa como decenas de personas reales.

El think time (sleep) es la pausa humana entre acciones. Sin él, un VU repite el guion a máxima velocidad —un martillo neumático, no una persona— y tu prueba mide una avalancha irreal. Con él, cada VU se parece a un usuario de verdad, y la carga que impones refleja lo que pasará en producción.

sleep() en k6 (contenido)

sleep se importa de 'k6' y recibe un número de segundos (puede ser decimal). Se pone donde un humano haría una pausa: normalmente al final del guion, antes de que la siguiente iteración empiece, y a veces entre pasos de un flujo:

// quote_sleep.js — cotizar con think time realista.
// MOSTRADO COMO CONTENIDO: k6 no esta instalado en este entorno.
import http from 'k6/http';
import { check, sleep } from 'k6';

const BASE_URL = 'http://localhost:8000';

export default function () {
  const payload = JSON.stringify({ room: 'Focus', tier: 'basic', hours: 3 });
  const params = { headers: { 'Content-Type': 'application/json' } };

  const res = http.post(`${BASE_URL}/quote`, payload, params);
  check(res, { 'status is 200': (r) => r.status === 200 });

  sleep(1); // think time: 1 segundo antes de la siguiente iteracion.
}

Tres detalles:

  • sleep(segundos). El argumento son segundos; sleep(1) es un segundo, sleep(0.5) medio segundo, sleep(3) tres. Es tiempo de espera pura: el VU no hace nada, no consume la API, solo deja pasar el reloj.
  • Dónde va. Lo típico es un sleep al final del guion, que separa una iteración de la siguiente (como el cliente que espera antes de volver a llamar). En flujos de varios pasos, puedes poner pausas entre pasos —cotizar, sleep, reservar— para imitar a un usuario que piensa entre clics.
  • No cuenta como tiempo de petición. El sleep alarga la iteración, pero no la petición. En el módulo 3 verás que k6 reporta http_req_duration (el tiempo de las peticiones) aparte de iteration_duration (la vuelta completa, que sí incluye el sleep). El think time no ensucia tus métricas de latencia; solo espacia la carga.

Cuánto think time: valores realistas

¿Un segundo, dos, medio? Depende de qué modeles. La idea es que el think time refleje el ritmo real de tus usuarios:

  • Un usuario navegando (leyendo, decidiendo entre pantallas) tiene pausas de varios segundossleep(3) a sleep(10) no es raro—.
  • Un usuario en un flujo rápido (cotizar y reservar seguido) tiene pausas cortas, de uno o dos segundos.
  • Un cliente automático o una integración máquina-a-máquina (otro servicio llamando tu API) puede tener poco o ningún think time —ahí sí quieres modelar algo cercano a un martillo, porque eso es lo real—.

Una práctica común es variar el think time un poco (por ejemplo, un valor aleatorio entre 1 y 3 segundos) para que los VUs no golpeen todos al mismo tiempo, como usuarios reales que no están sincronizados. En este módulo usamos valores fijos para que los números sean claros; la aleatoriedad realista es un refinamiento que verás en los escenarios del módulo 6. Lo esencial ahora: el think time es una decisión de modelado, no un detalle técnico. Elígelo pensando "¿cada cuánto actúa de verdad un usuario mío?".

El mismo VU, con y sin think time (ejecutado)

Aquí está la demostración que hace tangible todo lo anterior. Tomamos el mismo VU —un hilo de Python— corriendo la misma duración (3 segundos) contra la misma API, y solo cambiamos una cosa: si hay o no sleep.

Primero, sin think time. El VU golpea /quote tan rápido como pueda:

Qué esperar. Sin pausa, cada vuelta dura solo lo que tarda la petición (~0.2 ms en localhost), así que el VU hará miles de iteraciones. Salida real (1 VU, 3 s, sin think time):

----------------------------------------------------------
  Generador de carga Python  ->  1 VUs / 3s / sin think time
----------------------------------------------------------
  vus............: 1
  duracion real..: 3.00s
  iterations.....: 13598   (4532.5/s)
  checks.........: 100.00%   (27196 de 27196)
  http_errors....: 0
  req_duration...: avg=0.20ms  min=0.14ms  max=6.27ms
----------------------------------------------------------

Ahora el mismo VU, la misma duración, pero con sleep(0.5) —medio segundo de think time— tras cada cotización:

Qué esperar. Con medio segundo de pausa por vuelta, cada iteración dura ~0.5 s, así que en 3 segundos caben unas 6. Salida real (1 VU, 3 s, think time de 500 ms):

----------------------------------------------------------
  Generador de carga Python  ->  1 VUs / 3s / think 500ms
----------------------------------------------------------
  vus............: 1
  duracion real..: 3.06s
  iterations.....: 6   (2.0/s)
  checks.........: 100.00%   (12 de 12)
  http_errors....: 0
  req_duration...: avg=2.20ms  min=0.73ms  max=6.27ms
----------------------------------------------------------

Compara las dos, porque el contraste es la lección entera:

  • Mismo VU (1), misma duración (3 s), resultados opuestos: 13 598 iteraciones vs 6. Lo único que cambió fue el sleep(0.5). El think time gobierna el ritmo del bucle: sin él, el VU va al límite de la máquina; con él, va al ritmo que le impones.
  • iterations/s: 4532.5/s sin pausa, 2.0/s con sleep(0.5). Medio segundo de pausa por vuelta da ~2 vueltas por segundo —justo lo que dicta la aritmética 1 / 0.5 = 2—. El ritmo es predecible y tuyo.
  • La carga real que impones es abismalmente distinta. Un solo VU sin think time pesa, sobre el servidor, como miles de peticiones por segundo —el equivalente a cientos de usuarios reales apretujados en uno—. Con sleep(0.5), ese mismo VU pesa como... una persona que actúa dos veces por segundo. Si dimensionas tu prueba con VUs-martillo, creerás que tu sistema colapsa con "10 usuarios", cuando en realidad esos 10 VUs sin pausa equivalían a miles de personas.
  • req_duration casi no cambió (0.20 ms vs 2.20 ms, ambas rapidísimas): el sleep no infla la latencia de la petición. Alarga la iteración, no la petición. La ligera diferencia aquí es ruido de medición, no efecto del think time.

Esa es la razón por la que el think time casi siempre va en tus scripts: sin él, no estás midiendo a tus usuarios, estás midiendo cuán rápido tu máquina puede disparar un bucle.

Cuándo no quieres think time

El think time modela usuarios humanos, pero no todo lo que golpea tu API es humano. Hay casos legítimos donde lo quitas o lo reduces:

  • Pruebas de stress al límite (módulo 1): a veces quieres, a propósito, ver cuántas peticiones por segundo aguanta la API antes de quebrarse, sin la cortesía del think time. Ahí el objetivo no es realismo de usuario, sino encontrar el techo.
  • Integraciones máquina-a-máquina: si tu API la consume otro servicio en un bucle cerrado (sin un humano de por medio), lo realista es poco o ningún think time —eso es lo que de verdad pasará—.
  • Modelos de carga por tasa de llegada: hay una forma de generar carga donde no controlas el think time de cada VU sino la tasa de peticiones por segundo directamente (los executors constant-arrival-rate). Eso cambia el papel del sleep y es tema del módulo 4.

La regla no es "siempre pon sleep", sino "modela el ritmo real de quien usa tu API". Para usuarios humanos, eso casi siempre significa think time; para máquinas o pruebas de límite, quizás no. Lo que nunca debes hacer es poner VUs-martillo sin darte cuenta y luego interpretar los resultados como si fueran usuarios reales.

Errores comunes

Olvidar el sleep y medir una avalancha irreal. Qué pasa: alguien escribe un guion sin sleep, corre "10 VUs", ve que la API se satura y concluye "mi sistema no aguanta ni 10 usuarios". Por qué pasa: no puso think time, así que cada VU golpeó a máxima velocidad —miles de peticiones por segundo—. Cómo detectarlo: el iterations/s es enorme (miles) para pocos VUs, y la carga no se parece a nada humano. Cómo corregirlo: añade sleep(1) (o el think time realista de tus usuarios) al final del guion. Diez VUs con pausa se parecen a diez personas; diez VUs sin pausa se parecen a miles.

Pasar milisegundos en vez de segundos a sleep. Qué pasa: alguien viene de APIs donde los tiempos van en milisegundos y escribe sleep(500) queriendo medio segundo. Por qué pasa: confunde la unidad. Cómo detectarlo: cada iteración tarda 500 segundos (más de 8 minutos), la prueba parece colgada y hace casi ninguna iteración. Cómo corregirlo: sleep recibe segundos; medio segundo es sleep(0.5), no sleep(500). (En el time.sleep de Python es igual: segundos.)

Creer que el sleep infla la latencia reportada. Qué pasa: alguien teme poner think time porque "va a hacer que mis tiempos de respuesta se vean altísimos". Por qué pasa: confunde el tiempo de la iteración con el tiempo de la petición. Cómo detectarlo: revisa que http_req_duration (petición) y iteration_duration (vuelta completa) son métricas distintas; el sleep solo afecta la segunda. Cómo corregirlo: pon el think time sin miedo: no ensucia http_req_duration. La latencia de la petición se mide aparte del tiempo que el VU pasa esperando.

Ejercicios

Ejercicio 1 — Estima las iteraciones. Un VU corre un guion con una petición de ~2 ms y un sleep(0.25) al final (un cuarto de segundo). Si la corrida dura 4 segundos con 1 VU, ¿cuántas iteraciones aproximadas esperas? ¿Y con 2 VUs?

Ver solución

Con 1 VU: cada vuelta dura ~0.25 s (la petición de 2 ms es despreciable). En 4 segundos caben 4 / 0.25 = ~16 iteraciones. El ritmo es ~4 iteraciones por segundo (1 / 0.25).

Con 2 VUs: cada VU hace sus ~16, así que en total ~32 iteraciones (los VUs corren en paralelo, así que se suman). La duración por vuelta no cambia; lo que se duplica es el número de actores.

Ejercicio 2 — Diagnostica la conclusión errónea. Un compañero corrió "5 VUs sin sleep" contra Reservo, vio 20 000 iteraciones en 4 segundos y la API empezó a dar errores. Concluyó: "Reservo no aguanta ni 5 usuarios". ¿Por qué su conclusión es engañosa y qué debería cambiar?

Ver solución

Su conclusión es engañosa porque 5 VUs sin think time no son 5 usuarios: son 5 martillos golpeando a máxima velocidad, unas 5000 iteraciones por segundo en total —el equivalente a miles de personas reales, no a 5—. La API no falló "con 5 usuarios"; falló con una avalancha de miles de peticiones por segundo que ningún grupo de 5 humanos generaría jamás.

Qué cambiar: añadir think time realista (sleep(1) o similar). Con sleep(1), 5 VUs harían ~5 iteraciones por segundo —eso se parece a 5 usuarios—, y recién ahí podría concluir algo sobre cuántos usuarios reales aguanta Reservo. Sin think time, solo midió el techo de peticiones por segundo, que es otra pregunta (una prueba de stress, no de carga realista).

Ejercicio 3 — Elige el think time. Para cada escenario, propón un think time razonable y justifícalo: (a) usuarios navegando el catálogo de salas, leyendo descripciones antes de cotizar; (b) un servicio de reservas automático que sincroniza disponibilidad llamando tu API en bucle; (c) una prueba de stress para encontrar el máximo de peticiones por segundo que aguanta /quote.

Ver solución
  • (a) Un think time largo, sleep(3) a sleep(8) (o aleatorio en ese rango): un humano que lee descripciones se toma varios segundos entre acciones. Modelar pausas cortas exageraría la carga.
  • (b) Poco o ningún think time (sleep(0) o muy corto): es una máquina, no un humano, y lo realista es que llame en bucle cerrado. Aquí el "martillo" es el comportamiento verdadero.
  • (c) Sin think time, a propósito: el objetivo de un stress test es encontrar el techo de peticiones por segundo, así que quieres que los VUs golpeen al límite. (En el módulo 4 verás que hay executors pensados justo para controlar la tasa de llegada en estos casos.)

Resumen y siguiente paso

En esta lección entendiste la línea que habíamos usado sin explicar: sleep() es el think time, la pausa humana entre acciones. Sin él, un VU repite el guion a máxima velocidad —un martillo neumático que pesa como miles de usuarios— y tu prueba mide una avalancha irreal; con él, cada VU se parece a una persona y la carga refleja lo que pasará en producción. Lo mediste con el mismo VU: 13 598 iteraciones en 3 s sin pausa vs 6 con sleep(0.5) —la prueba de que el think time gobierna el ritmo del bucle—, y viste que sleep alarga la iteración pero no la latencia de la petición. También viste cuándo no quieres think time: pruebas de stress al límite e integraciones máquina-a-máquina.

Antes de avanzar deberías poder: poner sleep con el valor correcto (segundos) en el lugar correcto de un guion; estimar cómo el think time cambia el número de iteraciones; elegir un think time realista según quién usa tu API; y explicar por qué un VU sin pausa no representa a un usuario.

Ya tienes las cuatro piezas del guion: la petición, el check, el think time y el bucle. Falta la hoja de reparto. La lección 6 abre options: vus y duration —cuántos usuarios virtuales corren y por cuánto tiempo—, la configuración que, separada del guion, decide la escala de tu prueba.

Recursos