Módulo 6: Checks, groups y escenarios realistas
5. Parametrizar datos: no golpear una ruta caliente
Descripción
Fíjate en algo que hemos hecho hasta ahora sin cuestionarlo: cada VU pide el mismo dato. Focus/basic/3h, una y otra vez. O Studio/pro/4h, mil veces. El escenario tiene varios pasos, sí, pero el contenido de cada petición es idéntico en todas las iteraciones. Y eso, aunque genera tráfico, mide una versión falsamente optimista del sistema. Porque cuando pides siempre lo mismo, golpeas una sola ruta caliente: el mismo camino de código, el mismo registro, la misma entrada de caché. Y un sistema respondiendo a la misma pregunta repetida es mucho más rápido —y mucho menos representativo— que uno respondiendo a las preguntas variadas que le hacen los usuarios reales.
Parametrizar es la solución: variar el dato de entrada en cada iteración, tomándolo de una lista, para que la prueba ejercite muchos caminos y no uno solo. En vez de cotizar siempre Focus/basic/3h, cada iteración elige una fila de un conjunto de datos —Focus, Studio o Boardroom; basic o pro; horas distintas— y cotiza esa. El efecto es doble. Primero, realismo: la mezcla de peticiones se parece a la del tráfico real. Segundo, y más sutil, honestidad de la medición: dejas de beneficiarte de cachés y rutas calientes que en producción no estarían tan calientes, y tu p95 refleja el costo real de calcular respuestas variadas. En esta lección aprendes a parametrizar —con una lista en Python (ejecutado) y un SharedArray en k6 (contenido)— y ves medida la diferencia entre golpear una ruta caliente y repartir la carga entre datos variados.
Conexión con el módulo: parametrizar es la tercera pieza del escenario realista. En k6, el conjunto de datos se carga con un SharedArray (contenido); en Python, con una lista que cada VU recorre (ejecutado). El check de valor de la lección 2 ya estaba preparado para esto: calcula el precio esperado con la fórmula de la API para cualquier dato, no un número fijo. Verás la distribución real de una corrida parametrizada contra la API canónica, medida en este entorno con Python 3.14.0. La frontera: aquí varía el dato; encadenar pasos donde el dato fluye de una respuesta a la siguiente es la correlación de la lección 6.
El restaurante que solo sabe cocinar un plato
Imagina que quieres saber si la cocina de un restaurante aguanta una noche llena, y para probarla mandas mil pedidos del mismo plato: mil ensaladas César idénticas. La cocina se organiza: prepara una montaña de lechuga, un cubo de aderezo, y produce ensaladas en cadena a una velocidad impresionante. Concluyes: "la cocina es rapidísima, aguanta perfecto". Pero una noche real no son mil ensaladas iguales: son ensaladas, pastas, carnes, postres, cada uno con su estación, sus ingredientes, sus tiempos. La cocina que vuela haciendo mil ensaladas idénticas puede colapsar cuando le llegan cien platos distintos a la vez, porque ahora tiene que cambiar de estación, buscar ingredientes variados, coordinar. Probar con un solo plato repetido te dio una respuesta falsamente optimista.
Golpear una ruta caliente es mandar mil ensaladas iguales. El sistema se "organiza" alrededor de esa única petición —cachea el resultado, mantiene caliente el camino de código, quizás ni recalcula— y responde volando. Parametrizar es mandar el menú variado: cada iteración pide algo distinto, la cocina no puede optimizar para una sola cosa, y mides cómo aguanta la variedad real. La prueba con datos variados es más dura y más honesta, igual que juzgar una cocina por una noche de menú completo y no por mil ensaladas.
Pedir siempre el mismo dato golpea una sola ruta caliente: el mismo camino de código, el mismo registro, la misma entrada de caché, que el sistema responde con una velocidad falsamente optimista. Parametrizar —variar el dato de entrada desde una lista— ejercita muchos caminos, como el menú variado de una noche real, y mide el costo honesto de responder preguntas distintas. Es la diferencia entre mil ensaladas iguales y el menú completo.
Parametrizar en k6: el SharedArray (contenido)
En k6, el conjunto de datos se carga una vez y se comparte entre todos los VUs con un SharedArray. El nombre es literal: un array compartido en memoria, para que 50 VUs no carguen 50 copias del dataset. Cada VU, en cada iteración, elige una fila:
// quote_parametrized.js - cotizar variando el dato con un SharedArray.
// MOSTRADO COMO CONTENIDO: k6 no esta instalado en este entorno.
import http from 'k6/http';
import { check } from 'k6';
import { SharedArray } from 'k6/data';
// El dataset se carga UNA VEZ y se comparte entre todos los VUs.
const dataset = new SharedArray('reservations', function () {
return [
{ room: 'Focus', tier: 'basic', hours: 3, expected: 7500 },
{ room: 'Focus', tier: 'pro', hours: 3, expected: 6000 },
{ room: 'Studio', tier: 'basic', hours: 2, expected: 8000 },
{ room: 'Studio', tier: 'pro', hours: 4, expected: 12800 },
{ room: 'Boardroom', tier: 'basic', hours: 1, expected: 8000 },
{ room: 'Boardroom', tier: 'pro', hours: 6, expected: 38400 },
];
});
const BASE_URL = 'http://localhost:8000';
const params = { headers: { 'Content-Type': 'application/json' } };
export default function () {
// Cada iteracion elige una fila distinta (aqui, al azar).
const row = dataset[Math.floor(Math.random() * dataset.length)];
const body = JSON.stringify({ room: row.room, tier: row.tier, hours: row.hours });
const res = http.post(`${BASE_URL}/quote`, body, params);
check(res, {
'status is 200': (r) => r.status === 200,
// el esperado viene del dataset: distinto por fila
'price is correct': (r) => r.json('price_cents') === row.expected,
});
}
Desármalo:
new SharedArray('reservations', function () { return [...]; }). Crea un array compartido llamadoreservations. La función de carga se ejecuta una sola vez (en la fase de inicialización); su resultado se comparte entre todos los VUs. Por eso esSharedArrayy no un array normal: con miles de VUs, cargar el dataset una vez en vez de una copia por VU ahorra muchísima memoria. En un caso real, la función de carga suele leer un archivo (JSON.parse(open('./data.json'))) o un CSV.dataset[Math.floor(Math.random() * dataset.length)]. Cada iteración elige una fila al azar. (Otra estrategia común es usar el número de iteración o de VU para recorrer el dataset en orden; al azar es lo más simple para repartir.)'price is correct': (r) => r.json('price_cents') === row.expected. El check de valor usa elexpectedde esa fila, no un número fijo. Esta es la conexión con la lección 2: el check de corrección tiene que calcular (o traer) el esperado del dato actual, porque el precio correcto cambia con cada fila.
El punto clave del SharedArray: carga una vez, comparte entre VUs, varía por iteración. Es la forma canónica de parametrizar datos en k6 sin reventar la memoria.
Parametrizar en Python: una lista (ejecutado)
En Python, el equivalente es una lista que cada VU recorre eligiendo una fila por iteración. No hace falta un "shared array" especial: en el generador, la lista es un objeto global que todos los hilos leen (solo lectura, así que es seguro):
# El dataset parametrizado: cada iteracion elige una fila (equivalente al SharedArray).
DATASET = [
{"room": "Focus", "tier": "basic", "hours": 3},
{"room": "Focus", "tier": "pro", "hours": 3},
{"room": "Studio", "tier": "basic", "hours": 2},
{"room": "Studio", "tier": "pro", "hours": 4},
{"room": "Boardroom", "tier": "basic", "hours": 1},
{"room": "Boardroom", "tier": "pro", "hours": 6},
]
# ... dentro del guion de cada VU:
row = rng.choice(DATASET) # elige una fila distinta por iteracion
room, tier, hours = row["room"], row["tier"], row["hours"]
# cotiza esa fila; el esperado se calcula con expected_price(room, tier, hours)
Para ver la diferencia que hace parametrizar, corramos el mismo número de cotizaciones de dos formas y comparemos la distribución: primero golpeando una ruta caliente (siempre Focus/basic/3h) y luego parametrizado (una fila del dataset por petición). 60 cotizaciones cada una.
Qué esperar. La ruta caliente debe golpear 1 sola fila y ver 1 solo precio; la parametrizada debe repartirse entre las 6 filas y ver varios precios distintos. Salida real en este entorno:
RUTA CALIENTE (siempre la misma fila) (60 peticiones)
filas distintas pedidas: 1 de 6
precios distintos vistos: [7500]
Focus basic 3h .....: 60
PARAMETRIZADO (una fila del dataset por peticion) (60 peticiones)
filas distintas pedidas: 6 de 6
precios distintos vistos: [6000, 7500, 8000, 12800, 38400]
Focus basic 3h .....: 15
Boardroom pro 6h .....: 10
Focus pro 3h .....: 10
Boardroom basic 1h .....: 10
Studio basic 2h .....: 8
Studio pro 4h .....: 7
Léela, porque la diferencia salta a la vista:
- Ruta caliente: 1 de 6 filas, 1 precio (
[7500]). Las 60 peticiones pidieron exactamente Focus/basic/3h. El sistema respondió siempre7500, por el mismo camino de código, con la misma entrada de caché caliente. Mides ese camino, no el sistema. - Parametrizado: 6 de 6 filas, 5 precios (
[6000, 7500, 8000, 12800, 38400]). Las 60 peticiones se repartieron entre las seis filas del dataset, ejercitando salas distintas, tiers distintos y horas distintas. El sistema tuvo que calcular precios variados, no repetir uno solo. - Cinco precios de seis filas — ¿por qué 5 y no 6? Porque dos filas distintas dan el mismo precio: Studio/basic/2h = 4000×2 = 8000, y Boardroom/basic/1h = 8000×1 = 8000. Es un detalle real (dos entradas, un precio) que muestra que los datos se procesaron de verdad —no es un número inventado, es la aritmética de la API sobre datos variados—.
- La distribución no es perfectamente uniforme (15, 10, 10, 10, 8, 7) porque la elección es al azar y 60 peticiones es una muestra chica. Con más peticiones se acercaría a ~10 por fila. Lo importante no es la uniformidad exacta, sino que la carga se repartió entre las seis filas en vez de concentrarse en una.
La lección medida: parametrizar convirtió una prueba que tocaba 1 camino en una que toca 6. La segunda es más dura para el sistema y más parecida al tráfico real. Si tu p95 con datos variados es peor que con la ruta caliente, ese peor número es el honesto —el que verían tus usuarios—.
Estrategias para elegir la fila
Elegir al azar (rng.choice) es lo más simple, pero hay otras formas según lo que busques:
- Al azar (
Math.random()/rng.choice): reparte la carga de forma pareja en promedio. Bueno por defecto, cuando quieres una mezcla representativa. - Por índice de iteración o de VU (
dataset[__ITER % dataset.length]en k6): recorre el dataset en orden, garantizando que cada fila se use el mismo número de veces. Útil cuando necesitas cobertura exacta y no solo estadística. - Datos únicos por VU (que cada usuario virtual use datos que solo él usa —por ejemplo, un usuario distinto para hacer login): evita colisiones cuando cada iteración modifica estado. Es más avanzado; para cotizaciones (que solo leen) el azar basta.
Para el escenario de Reservo, el azar reparte bien y es lo que usamos. La regla general: elige la estrategia por lo que quieras garantizar —mezcla representativa (azar), cobertura exacta (por índice) o unicidad (por VU)—.
Errores comunes
Parametrizar el dato pero dejar el check con un número fijo. Qué pasa: alguien varía sala/tier/horas pero deja 'price is correct': (r) => r.json('price_cents') === 7500. Por qué pasa: el check quedó del tiempo en que solo se cotizaba Focus/basic/3h. Cómo detectarlo: el check de precio falla para todo lo que no sea Focus/basic/3h, porque 7500 ya no es el esperado. Cómo corregirlo: el esperado tiene que venir del dato de esa fila —del expected del dataset, o calculado con expected_price(room, tier, hours)—. Si parametrizas el dato, tienes que parametrizar el esperado.
Cargar el dataset dentro de la función del VU (sin SharedArray). Qué pasa: en k6, alguien pone const dataset = JSON.parse(open('./data.json')) dentro de default, y con miles de VUs se cargan miles de copias del archivo, reventando la memoria. Por qué pasa: no se conoce el SharedArray. Cómo detectarlo: uso de memoria altísimo, la prueba se ralentiza o se cae con muchos VUs. Cómo corregirlo: carga el dataset una vez con new SharedArray('nombre', () => {...}) en el ámbito del script (fuera de default); todos los VUs comparten esa única copia.
Confundir "más peticiones" con "más caminos". Qué pasa: alguien sube los VUs de 10 a 1000 pero sigue pidiendo el mismo dato, y cree que por eso su prueba es más completa. Por qué pasa: se confunde volumen con variedad. Cómo detectarlo: la corrida toca "1 de N filas" sin importar cuántos VUs. Cómo corregirlo: subir VUs añade volumen sobre la misma ruta caliente; parametrizar añade variedad de caminos. Son ejes distintos: quieres los dos —suficiente volumen (M4) y datos variados (esta lección)—.
Ejercicios
Ejercicio 1 — Añade una fila y su esperado. Al dataset de la lección, añade una fila para Boardroom/pro/2h. (a) Calcula el price_cents esperado con la regla de la API. (b) Escribe la fila como la pondrías en el SharedArray de k6.
Ver solución
- (a) Boardroom cuesta 8000 centavos/hora. 2 horas = 8000 × 2 = 16000. Tier pro: 16000 × 80 // 100 = 12800 centavos.
- (b)
{ room: 'Boardroom', tier: 'pro', hours: 2, expected: 12800 },
(Fíjate que este precio, 12800, coincide con el de Studio/pro/4h del dataset original —otra colisión de precio, como Studio/basic/2h y Boardroom/basic/1h con 8000—. Es real: distintas filas pueden dar el mismo precio.)
Ejercicio 2 — Lee la distribución. En la corrida real, la ruta caliente tocó "1 de 6 filas, precios [7500]" y la parametrizada "6 de 6 filas, precios [6000, 7500, 8000, 12800, 38400]". (a) ¿Por qué la ruta caliente vio un solo precio? (b) ¿Por qué la parametrizada vio 5 precios y no 6, si tiene 6 filas? (c) ¿Cuál de las dos corridas es más representativa del tráfico real y por qué?
Ver solución
- (a) Porque las 60 peticiones pidieron la misma fila (Focus/basic/3h), y esa fila siempre da el mismo precio,
7500. Un dato, un precio. - (b) Porque dos filas distintas dan el mismo precio: Studio/basic/2h = 4000×2 = 8000 y Boardroom/basic/1h = 8000×1 = 8000. Seis filas, pero
8000aparece dos veces, así que hay 5 precios distintos. - (c) La parametrizada. Ejercita 6 caminos distintos (salas, tiers y horas variados) en vez de 1, así que se parece a la mezcla de peticiones que hacen los usuarios reales. La ruta caliente mide un solo camino, a menudo cacheado, y da una lectura falsamente optimista.
Ejercicio 3 — La ensalada y el menú. Un compañero prueba la API de Reservo con 500 VUs cotizando todos Focus/basic/3h, obtiene un p95 buenísimo y declara "la API aguanta 500 usuarios". ¿Qué le dirías, usando la analogía de la cocina? ¿Qué cambiaría en su prueba?
Ver solución
Le diría que probó su cocina con 500 pedidos de la misma ensalada: la API se organizó alrededor de esa única petición (mismo camino de código, misma caché caliente) y respondió volando, dándole un p95 falsamente optimista. Una noche real no son 500 ensaladas iguales, sino un menú variado: salas distintas, tiers distintos, horas distintas, cada una con su cálculo.
Lo que cambiaría: parametrizar los datos. En vez de que los 500 VUs pidan Focus/basic/3h, que cada iteración elija una fila de un dataset variado (un SharedArray), y que el check de precio use el esperado de esa fila. Con eso mediría cómo aguanta la API respondiendo preguntas distintas —el menú completo—, que es el número honesto. Volumen (500 VUs) está bien; le falta variedad.
Resumen y siguiente paso
En esta lección quitaste una falta de realismo silenciosa: pedir siempre el mismo dato. Golpear una ruta caliente —el mismo camino de código, el mismo registro, la misma caché— da una lectura falsamente optimista, como juzgar una cocina por mil ensaladas idénticas. Parametrizar —variar sala/tier/horas desde una lista (un SharedArray en k6, una lista en Python)— ejercita muchos caminos, como el menú variado de una noche real, y mide el costo honesto de responder preguntas distintas. Lo viste medido: la misma cantidad de cotizaciones tocó 1 fila y 1 precio en la ruta caliente, contra 6 filas y 5 precios distintos parametrizadas —con dos filas dando el mismo precio (8000), un detalle real que prueba que los datos se procesaron de verdad—. Y quedó clara la conexión con la lección 2: si parametrizas el dato, tienes que parametrizar el esperado del check.
Antes de avanzar deberías poder: parametrizar un escenario con un SharedArray (k6) o una lista (Python); explicar por qué una ruta caliente engaña y datos variados dan el número honesto; hacer que el check de valor use el esperado de la fila actual; y elegir una estrategia de selección (azar, por índice, por VU) según lo que quieras garantizar.
La lección 6 es el corazón del módulo: la correlación. Hasta ahora, aunque el dato varía, cada paso del escenario es independiente —cotizar, reservar y confirmar usan datos que tú fijas—. Un flujo real es una cadena: el price_cents que te cotizaron es el que mandas a reservar, y el booking_id que la reserva te devolvió es el que usas para confirmar. Aprenderás a extraer un valor de una respuesta y reusarlo en la siguiente, y correrás el flujo cotizar → reservar → confirmar completo, con el booking_id correlacionado de verdad.
Recursos
SharedArray—k6/data— la referencia del array compartido: cómo cargar un dataset una vez y compartirlo entre todos los VUs sin duplicar memoria. La fuente exacta de la parametrización en k6.- Parametrización de datos en k6 — ejemplos oficiales de cómo alimentar una prueba con datos variados (arrays, JSON, CSV) y estrategias para elegir la fila. El patrón completo de esta lección.
random.Random.choice— documentación de Python — el método con el que cada VU del generador elige una fila del dataset. Cómo se selecciona un dato al azar en Python.