Por Lucas Yoris · Performate
Think time y concurrencia: escenarios k6 que se sienten humanos (sin matemática de fantasía)
Think time y concurrencia en k6: pacing realista, distribuciones de sleep e interacción con escenarios arrival-rate—alineado con analytics de producto.
Tu script ejecuta búsqueda → detalle → checkout en 200 ms sin pausa. Analytics de producto dice que el usuario mediano tarda 8 segundos entre pasos. La prueba reporta 500 req/s sostenidos; producción nunca supera 40—y el capacity plan compra infraestructura para un fantasía de bots.
El think time (sleep) en k6 no es decoración: define cuánta presión concurrente ejerce cada VU y si tu modelo closed vs open tiene sentido. En esta guía verás por qué sleeps uniformes de 1 s suelen mentir, cómo alinear distribuciones a telemetría de sesión, y qué cambia cuando combinas think time con ejecutores arrival-rate.
Por qué el pacing cambia la concurrencia—no solo el RPS
A nivel funcional, omitir sleep «acelera» la prueba. Bajo modelos closed, produce efectos distintos:
- VUs como bots — cada usuario virtual dispara requests al ritmo máximo; la concurrencia efectiva en backends y pools de DB no refleja sesiones humanas.
- RPS inflado en closed model — más VUs × menos sleep = más req/s que analytics de producto jamás verá.
- Interacción con arrival rate — en ejecutores open, el think time no reduce la tasa objetivo; k6 lanza más VUs para mantener iteraciones/s (ejecutores).
- Colas engañosas — sin pausa, las colas de middleware se llenan distinto; los percentiles no son comparables a prod.
Piensa en medir tráfico peatonal contando pasos por minuto mientras todos corren en lugar de caminar.
Cuando copiar sleeps de otro producto falla
Los artículos genéricos sugieren sleep(1) o sleep(Math.random() * 3). Valida contra trazas de sesión reales—o contra cuántos usuarios virtuales necesitas dado tu mix de closed/open. Combina pacing con tipos de escenario cuando el riesgo es flujo multipaso, no un endpoint aislado.
Implementación práctica con k6: distribuciones de sleep en flujo multipaso
Modela un journey de e-commerce con pausas log-normales entre pasos—más realistas que uniforme—y compara RPS resultante con analytics.
Script de ejemplo (ilustrativo—no es una prueba lista para producción). El snippet usa URLs y timings ficticios. Adapta base URL, auth y distribuciones a tu telemetría.
Qué demuestra este ejemplo:
- Think time por paso: sleeps distintos tras búsqueda, detalle y checkout reflejan lectura vs decisión de compra.
- Distribución sesgada:
humanPause()usa log-normal (media ~3 s) en lugar de uniforme 0–1 s. - Closed model explícito:
ramping-vuscon 30→80 VUs modela sesiones concurrentes, no req/s fijos. - Checks por paso: validación funcional en cada transición antes de dormir—evita inflar sleeps sobre requests fallidos.
import http from 'k6/http';
import { check, sleep } from 'k6';
const BASE = __ENV.API_BASE || 'https://staging.example.com';
// Pausa log-normal aproximada — calibra media/mu contra analytics
function humanPause(meanSeconds = 3) {
const u = Math.random();
const v = Math.random();
const z = Math.sqrt(-2 * Math.log(u)) * Math.cos(2 * Math.PI * v);
const seconds = Math.max(0.5, meanSeconds * Math.exp(0.5 * z));
sleep(seconds);
}
export const options = {
scenarios: {
browse_buy: {
executor: 'ramping-vus',
startVUs: 0,
stages: [
{ duration: '2m', target: 30 },
{ duration: '5m', target: 80 },
{ duration: '2m', target: 80 },
{ duration: '2m', target: 0 },
],
exec: 'journey',
tags: { flow: 'checkout' },
},
},
thresholds: {
'http_req_duration{step:search}': ['p(95)<300'],
'http_req_duration{step:checkout}': ['p(95)<800', 'p(99)<1500'],
http_req_failed: ['rate<0.01'],
},
};
export function journey() {
const headers = {
Authorization: `Bearer ${__ENV.TOKEN}`,
'Content-Type': 'application/json',
};
const search = http.get(`${BASE}/v1/search?q=headphones`, {
headers,
tags: { step: 'search', flow: 'checkout' },
});
check(search, { 'search ok': (r) => r.status === 200 });
humanPause(4);
const detail = http.get(`${BASE}/v1/products/SKU-42`, {
headers,
tags: { step: 'detail', flow: 'checkout' },
});
check(detail, { 'detail ok': (r) => r.status === 200 });
humanPause(6);
const checkout = http.post(
`${BASE}/v1/checkout`,
JSON.stringify({ sku: 'SKU-42', qty: 1 }),
{ headers, tags: { step: 'checkout', flow: 'checkout' } }
);
check(checkout, { 'checkout ok': (r) => r.status >= 200 && r.status < 300 });
humanPause(2);
}
Patrones que funcionan
- Calibra contra analytics — exporta histogramas de tiempo entre eventos de producto; ajusta media de
humanPause. - Sleep solo tras éxito — no pauses sobre 4xx/5xx si el flujo real aborta; modela abandono con
if (!check(...)) return. - Arrival rate para APIs abiertas — si prod mide req/s independiente de pacing, usa ramping arrival rate y think time mínimo.
- Tags por paso — umbrales segmentados (
step:checkout) detectan cuellos en el paso lento del journey.
Anti-patrones a evitar
- Copiar sleeps de tutoriales ajenos sin validar contra tus trazas.
sleep(0)en closed model y extrapolar «usuarios concurrentes» a stakeholders.- Mezclar closed model con objetivo de req/s de marketing—son preguntas distintas.
Tip pro (comando de ejemplo):
k6 run journey.js --summary-trend-stats="avg,min,max" --tag flow=checkout
Qué demuestra este comando: el summary expone tendencias por tag de paso—útil para comparar si el pacing produce RPS alineado a analytics (~40 vs ~500).
Marco de decisión: cuánto sleep y qué executor
| Situación | Acción recomendada |
|---|---|
| App con login, carrito, wizard—«usuarios activos» | ramping-vus + think time calibrado a sesiones |
| API pública, métrica principal = req/s | ramping-arrival-rate; think time mínimo o cero |
| Flujo con paso lento (pago, KYC) | Sleep largo solo en ese paso; umbrales por step |
| CI smoke—salud rápida | Sleep corto fijo; no confundir con capacity test |
| Comparar builds | Mismo executor, mismos sleeps, mismos stages |
Usa think time largo si analytics muestra minutos entre acciones y modelas closed concurrency.
Usa think time mínimo si el sistema es API-first y prod reporta arrival rate desacoplado del response time.
Usa distribución log-normal si la mediana y la cola larga importan—uniforme subestima ráfagas de usuarios rápidos.
Observabilidad, documentación y siguientes pasos
El pacing solo sirve si documentas de dónde salieron los números. Antes de presentar capacity:
- Registra fuente de think times (Mixpanel, Amplitude, traces APM) en el README del escenario.
- Compara RPS observado en k6 vs dashboard de prod para el mismo flujo.
- Documenta executor (closed vs open) junto a los sleeps—evita interpretaciones cruzadas.
- Revalida sleeps tras rediseños de UX que cambian tiempo entre pasos.
- Archiva git SHA y parámetros de
humanPausepor corrida de regresión.
Cómo Performate simplifica pacing sin reescribir sleeps en cada script
Ajustar sleep(3) en cuatro archivos cada vez que producto cambia el funnel no escala. Abajo hay un ejemplo concreto de flujo para el journey browse→buy de arriba.
Ejemplo: flujo multipaso con pacing visual y comparación de RPS
- Importa los tres requests (search, detail, checkout) desde Postman en orden de flujo. Problema resuelto: un journey en un workspace, no tres scripts desconectados.
- Define pausas entre pasos en el editor visual—4 s, 6 s, 2 s alineados a analytics. Problema resuelto: tuning sin buscar líneas
sleepen JS. - Configura escenario ramping-vus 30→80 para modelar sesiones concurrentes. Problema resuelto: closed model explícito para stakeholders de producto.
- Corre y compara RPS resultante con el dashboard de prod en la vista de reporte. Problema resuelto: detectas desalineación de pacing antes del capacity review.
- Duplica escenario con arrival rate para el mismo flujo si también necesitas modelo open—compara side by side. Problema resuelto: dos preguntas, una fuente de requests.
- Exporta script k6 con sleeps y executor para CI (pruebas de carga en CI/CD).
Ese flujo conecta directo con el cta de este post: escenarios que se sienten humanos sin matemática de fantasía ni días de pegamento.
Cierre
El riesgo de concurrencia mal modelada es un problema de pacing, no solo de VU count. Calibra think time contra telemetría real, elige closed vs open según la pregunta, y valida que el RPS de la corrida esté en el mismo orden de magnitud que producción.
Corre el journey con pacing de analytics esta semana—y anota si los req/s observados están dentro de 2× del dashboard de prod o si estás stressando con bots.
¿Listo para optimizar el rendimiento de tu API?
Usa Performate para convertir este playbook en escenarios k6 ejecutables, umbrales y reportes compartibles sin perder días en código pegamento.