4 · La red y la webLección 6 de 6
Qué tan rápido y cuánto: latencia y throughput
How fast and how much: latency and throughput
Por qué la misma foto pasa 8 ms en un pod del gateway un martes en la tarde y 60 en un pico: dónde se va el tiempo de una request, cómo crece la fila cuando un servidor se llena, cuánta capacidad se compra, cómo se le pone tope a una fila y cómo se mide todo sin engañarte.
Secciones14
- 01Dónde se va el tiempo: la latencia de punta a punta
- 02Esperar turno: tiempo de servicio y tiempo en cola
- 03Utilización: la curva que explota cerca del 100 %
- 04Saturación, cuello de botella y headroom
- 05Batching dinámico: más throughput a cambio de latencia
- 06Backpressure: una fila con tope
- 07Medir de verdad: pruebas de carga y goodput
- 08Colas a fondo: varios servidores, menos variabilidad y pruebas que mienten extra
- 09Errores comunes
- 10En la vida real extra
- 11Ejercicios
- 12Lo que sigue extra
- 13Palabras que vas a oír
- 14Chequeo
¿Por qué el gateway de C01 contesta rápido con poco tráfico y lento cerca del pico, si cada request hace el mismo trabajo?
Cada foto que llega al gateway de C01 pide el mismo trabajo: ≈ 6 milisegundo (ms) de CPU para decodificarla y prepararla, y unos milisegundos de GPU para el modelo. Un martes a las 4 de la tarde, la foto pasa ≈ 8 ms en el pod (cada copia del gateway en Kubernetes). En un pico que llega antes de que haya más pods, pasa 60, y su p99 supera los 250. Nadie cambió el código, la foto ni la máquina.
Ya tienes las piezas sueltas. La latencia es cuánto tarda una request; el throughput, cuántas termina el sistema por segundo, y los QPS, cuántas llegan. La Ley de Little dice cuántas hay adentro a la vez; el p99 y el SLO, cómo se escribe la promesa, y los timeouts, cuándo rendirse.
Esta lección las junta. Vas a desarmar la latencia en sus partes, ver por qué la espera explota cuando un servidor se llena, decidir cuántas máquinas comprar, ponerle tope a una fila y medir todo sin que la prueba te mienta.
Dónde se va el tiempo: la latencia de punta a punta
EsencialWhere the time goes: end-to-end latency
La latencia de punta a punta (end-to-end latency) es el tiempo total de una request entre dos puntos que tú nombras, sumando todo lo que pasa en medio. «Punta a punta» no dice nada si no dices cuáles puntas.
En C01 están escritas en el SLO, como viste en la lección de percentiles: el reloj arranca cuando el balanceador de carga de carga (la puerta que reparte las requests entre los pods) tiene la request completa y para cuando sale la respuesta. Es una medida del lado del servidor (server-side). La subida de la foto desde el celular de Ana, por 4G, queda fuera: depende de su señal, no del servicio.
Tres maneras de gastar el tiempo
Entre esas dos puntas, la request da varios saltos: del balanceador al gateway, del gateway a Triton (el servidor que corre el modelo en la GPU) y de vuelta. En cada salto, el tiempo se va en una de tres cosas:
- Viajar. Los bytes cruzan la red. Cada mensaje viaja en paquetes, y una ida y vuelta, el RTT (round-trip time), cuesta poco dentro de la misma zona y mucho más a otra ciudad. Una zona es un centro de datos de la nube, o un grupo de ellos muy cercanos, con su propia luz y su propia red; lo verás a fondo en la lección de regiones. Abrir una conexión nueva cuesta un handshake, un saludo de idas y vueltas antes del primer byte útil; por eso C01 reusa conexiones abiertas (keep-alive). Lo viste en las lecciones de red y de HTTP.
- Esperar. La request ya llegó, pero el recurso que necesita está ocupado con otras. Espera su turno en una fila.
- Trabajar. Un recurso le dedica su tiempo: la CPU decodifica la foto, la GPU corre la ResNet-50.
La Ronda (M03) estima el camino típico de C01 tramo por tramo. Esta es una request ilustrativa, dentro de sus rangos, a una hora tranquila:
- Backend · pide la categoría
El backend del marketplace manda la foto de Ana y espera como máximo 300 ms. Reusa conexiones abiertas (keep-alive), así que no paga un handshake en cada foto. - Balanceador · viajar: ≈ 3 ms
Viajar. El balanceador recibe la request y la pasa a un pod; el gateway lee los ≈ 200 KB de la foto. En una request típica, ≈ 2 a 6 ms (M03). Aquí arranca el reloj del SLO: cuando la request completa ya llegó. - Fila del pod · esperar un lugar de CPU
Esperar. Si el núcleo del pod está decodificando otras fotos, esta espera. Con poco tráfico, ≈ 0 ms. Con el núcleo al 90 %, ≈ 54 ms en promedio: es la espera que más crece cerca del pico. - Reloj del SLO · p99 ≤ 200 ms
El SLO de C01 mide la latencia de punta a punta del lado del servidor: desde que el balanceador tiene la request completa hasta que sale la respuesta. La subida desde el celular de Ana queda fuera. - Preproceso · trabajar: ≈ 6 ms de CPU
Trabajar. Decode del JPEG, resize, crop y normalización: el tiempo de servicio de la CPU, ≈ 6 ms por foto en promedio. Una foto web de 12 MP cuesta mucho más. - Salida · trabajar: ≈ 1 ms
Viajar y trabajar. Los logits regresan de Triton (≈ 1 ms) y el gateway hace softmax, top-5 y el JSON de la respuesta: ≈ 1 ms de CPU (lección de async). Con el decode, la CPU trabaja ≈ 7 ms por request; las cuentas de capacidad redondean a 6. - ResNet-50 · trabajar: 3–6 ms
Trabajar. El modelo en la GPU, con TensorRT en FP16. Es una parte chica del total: 4 de 20 ms en la request típica. - Fila de Triton · esperar: ≤ 2 ms de ventana
Viajar y esperar. El tensor de 602 KB viaja a la T4 de su misma zona (≈ 2 a 4 ms) y espera compañeros para un batch, como máximo 2 ms: con poco tráfico casi nunca llega nadie y espera la ventana completa. Si la GPU está ocupada, espera además al batch anterior.
Para ir más hondoLa red también tiene cola+
Casi siempre, la red de adentro del centro de datos aporta poco y parejo. Pero tiene sus sorpresas. Si un paquete se pierde y los acuses que sí llegan no delatan el hueco, TCP espera su tiempo de reenvío antes de volver a mandarlo, y en Linux ese tiempo es de 200 ms como mínimo (M03, lección de red): una request de 30 ms se vuelve de 230 sin que nadie haya hecho nada mal. Una conexión nueva paga su handshake. Y un salto a otra zona suma ida y vuelta. Son pocas requests, y justo por eso viven en la cola de la distribución, donde mira el p99.
Medir cada tramo
La regla práctica: mide cada tramo por separado. El gateway de C01 lleva un histograma para el preproceso y otro para la llamada a Triton. Si la latencia sube y solo crece uno, ya sabes dónde buscar. La suma de los p99 de los tramos sirve para repartir el presupuesto, pero no da el p99 total: eso se mide de punta a punta, como viste en la lección de percentiles.
Fíjate en la columna que casi no aparece. Con poco tráfico, «esperar» vale solo la ventana de Triton. Cerca del pico es la única que crece. La siguiente sección la abre.
Esperar turno: tiempo de servicio y tiempo en cola
EsencialWaiting your turn: service time and queueing time
El tiempo que un recurso trabaja de verdad en una request es su tiempo de servicio (service time), que en las fórmulas se escribe S. En el gateway de C01, ≈ 6 ms de CPU por foto (con el ≈ 1 ms de armar la respuesta son 7, pero la Ronda y la lección de async redondean a 6 en las cuentas de capacidad, y aquí también); en la T4, de 3 a 6 ms por batch chico. Se baja con mejor hardware o con menos trabajo: el draft mode de JPEG (decodificar la foto ya achicada), un modelo en FP16, mandar menos bytes.
El tiempo que la request pasa esperando a que ese recurso se desocupe es su tiempo en cola (queueing time o queueing delay). No se baja optimizando el código: se baja con más capacidad o con llegadas más parejas.
Su suma es lo que la request vive en ese tramo: W = espera + S. Es la misma W de la Ley de Little: el tiempo que pasa adentro.
Una fila, a mano
Un pod con un solo núcleo y 6 ms de CPU por foto. Le llegan 5 fotos en 30 ms. Primero, llegan parejas, una cada 7.5 ms: cuando llega cada una, la anterior ya terminó. Nadie espera, y las cinco tardan 6 ms.
Ahora llegan las mismas cinco, pero tres casi juntas. La regla de una fila es simple: cada request empieza cuando llega o cuando el núcleo se desocupa, lo que pase después.
En la vida real nadie se pone de acuerdo para llegar parejo. Las requests llegan al azar, como viste con Poisson en la lección de probabilidad: en ventanas de 10 ms con 5 llegadas en promedio, ≈ 3 % de las ventanas trae 10 o más (M03). Siempre hay ráfagas, y cada ráfaga deja una fila. Por eso aparece la espera aunque el servidor no esté lleno.
La Ronda (M03) lo resume así: el mismo modelo, en el mismo hardware, puede responder en 10 ms o en 200 ms según qué tan cargado esté. El servicio no cambió. Cambió la fila. ¿Cuánto crece? Depende de una sola cantidad.
Utilización: la curva que explota cerca del 100 %
EsencialUtilization: the curve that explodes near 100 %
La utilización (utilization) es la fracción del tiempo en que un servidor está ocupado. Se escribe ρ (la letra griega rho) y se calcula con lo que llega y lo que tarda cada una:
ρ = λ × S ÷ c
λ (la letra griega lambda) es la tasa de llegada, en requests por segundo; S, el tiempo de servicio; c, cuántos servidores atienden la misma fila (núcleos, GPUs, réplicas). Es una cuenta de horas de trabajo: cada segundo llegan λ × S segundos de trabajo, para c servidores.
- Un pod del gateway en el pico de diseño (500 requests por segundo entre 5 pods): 100 requests por segundo × 0.006 s ÷ 1 vCPU (el que tiene reservado cada pod) = 0.6. Ese vCPU trabaja 60 % del tiempo.
- Los 5 pods juntos: 500 × 0.006 ÷ 5 = 0.6 también. Son 3 vCPU ocupados de 5.
- Una T4 (500 fotos por segundo entre 2 GPUs): una prueba de carga midió que procesa hasta ≈ 1,200 fotos por segundo cumpliendo su presupuesto (la sección de capacidad lo explica). Es como si cada foto le costara S = 1 ÷ 1,200 de segundo, así que ρ = 250 × (1 ÷ 1,200) = 250 ÷ 1,200 ≈ 21 %.
Si ρ llega a 1, llega tanto trabajo como el servidor puede hacer. Si lo pasa, la fila crece sin fin: lo viste en la lección de async, con los pedazos de audio de C13, los subtítulos en vivo, que se atrasaban dos minutos en diez. Pero la sorpresa no está en el 100 %: está antes.
La teoría de colas, en una fórmula
La teoría de colas (queueing theory) es la parte de las matemáticas que calcula cuánto se espera en una fila. Su modelo más simple se llama M/M/1: llegadas al azar (Poisson), tiempos de servicio al azar (la mayoría cortos y unos pocos largos), un servidor y una fila en orden de llegada. El nombre se lee por partes: la primera letra dice cómo llegan las requests, la segunda cómo es el servicio y el número cuántos servidores hay. La M (de Markov, un matemático ruso) quiere decir «al azar y sin memoria»: lo que tardó la anterior no dice nada de la siguiente. Con esos supuestos, el tiempo promedio que pasa cada request en el sistema es:
W = S ÷ (1 − ρ)
Y la espera en la fila es lo que sobra: W − S = ρ × S ÷ (1 − ρ). El divisor es la clave: 1 − ρ es el tiempo libre del servidor. Cuando se acerca a cero, la espera se dispara.
Con los 6 ms del gateway:
| Utilización ρ | Tiempo libre | W promedio | Espera en la fila | p99 (≈ 4.6 × W) |
|---|---|---|---|---|
| 50 % | 50 % | 12 ms | 6 ms | ≈ 55 ms |
| 80 % | 20 % | 30 ms | 24 ms | ≈ 138 ms |
| 90 % | 10 % | 60 ms | 54 ms | ≈ 276 ms |
| 95 % | 5 % | 120 ms | 114 ms | ≈ 553 ms |
| 99 % | 1 % | 600 ms | 594 ms | ≈ 2,763 ms |
De 50 a 90 % la espera se multiplica por 9 (de 6 a 54 ms), aunque la carga solo subió 80 %. De 90 a 99 %, por 11 más. La intuición dice «al 90 % todavía queda 10 % libre»; la fórmula dice que ese 10 % no alcanza para deshacer las ráfagas.
¿Y el p99? En M/M/1, el tiempo en el sistema sigue una distribución exponencial, y su p99 es W × ln(100) ≈ 4.6 × W (ln es el logaritmo natural). No hace falta recordar por qué: basta saber que la cola de la distribución crece igual que el promedio, multiplicada por casi 5.
Por qué se dobla la curva
Con ρ = 0.5, el servidor pasa la mitad del tiempo libre. Cuando llega una ráfaga, tiene tiempo de sobra para vaciar la fila antes de la siguiente. Con ρ = 0.9 solo tiene 10 %: cada ráfaga deja una fila que tarda en vaciarse, y la siguiente llega antes de que termine. La Ronda lo dice así: la espera no la causa la carga promedio, sino la variabilidad combinada con poco tiempo libre para absorberla.
Ahora sí, la respuesta de la lección. El HPA, la regla de Kubernetes que agrega o quita pods, mantiene al gateway cerca de 60 % de CPU. Pero el HPA reacciona en decenas de segundos o minutos: por omisión revisa las métricas cada 15 s, y después hay que decidir y arrancar los pods nuevos. Un pico llega en segundos:
Son números del modelo, no mediciones, y solo de la parte del pod. Pero cuentan la historia completa: la misma request, el mismo código, otra fila.
Un servidor con una fila: llegan λ requests por segundo y cada una le cuesta S de tiempo de servicio. Arranca en un pod del gateway de C01 en el pico de diseño.
Prueba S = 3 con 150 llegadas: con la mitad de trabajo por foto, el núcleo baja a 45 % y el p99 a ≈ 25 ms. Bajar el tiempo de servicio también baja la utilización.
Empieza en el pico de diseño de un pod. Sube las llegadas de 100 a 150: el núcleo pasa de 60 a 90 % y el p99 se cuadruplica. Sube a 167: ρ pasa del 100 %, la fila ya no tiene fondo y W, la espera y el p99 dejan de existir (salen como «—»).
Compruébalo con una simulación
La fórmula sale de supuestos. Este programa no supone nada: genera 200,000 requests al azar (con semilla, para que salga igual siempre) y aplica la regla de la fila a mano de la sección anterior, una por una. La espera de cada request es la de la anterior, más lo que trabajó la anterior, menos el hueco entre las dos llegadas, y nunca menos de cero.
import math, random
def simular(rho, S=6.0, n=200_000, semilla=3):
azar = random.Random(semilla)
espera, servicio_anterior, tiempos = 0.0, 0.0, []
for _ in range(n):
hueco = azar.expovariate(rho / S) # ms desde la llegada anterior
espera = max(0.0, espera + servicio_anterior - hueco)
servicio = azar.expovariate(1 / S) # ms de CPU de esta request
tiempos.append(espera + servicio)
servicio_anterior = servicio
tiempos.sort()
return sum(tiempos) / n, tiempos[math.ceil(0.99 * n) - 1]
print(" ρ W simulado W fórmula p99 simulado p99 fórmula")
for rho in (0.5, 0.8, 0.9, 0.95):
w, p99 = simular(rho)
formula = 6.0 / (1 - rho)
print(f"{rho:>4} {w:7.1f} ms {formula:7.1f} ms {p99:8.0f} ms {formula * math.log(100):8.0f} ms") ρ W simulado W fórmula p99 simulado p99 fórmula
0.5 12.0 ms 12.0 ms 56 ms 55 ms
0.8 30.4 ms 30.0 ms 140 ms 138 ms
0.9 61.4 ms 60.0 ms 275 ms 276 ms
0.95 118.9 ms 120.0 ms 512 ms 553 msexpovariate(t) genera tiempos al azar con promedio 1 ÷ t: huecos de S ÷ ρ entre llegadas (12 ms a 50 %) y servicios de 6 ms en promedio. Es la «M» de M/M/1. La simulación y la fórmula coinciden de cerca: los promedios, por menos de 3 %; el p99 a 95 %, por ≈ 7 % (512 contra 553). Cambia la semilla y verás otra cosa que importa: a 50 % el p99 sale casi igual con cualquiera; a 95 %, entre semillas brinca de ≈ 460 a ≈ 930 ms. Cerca del 100 %, hasta medir se vuelve ruidoso: unas cuantas ráfagas largas deciden el resultado.
Un modelo, no una ley
Tus llegadas no son exactamente Poisson. El servicio de una GPU que corre siempre el mismo engine de TensorRT es mucho más parejo que una exponencial, y Triton con batching no es una fila simple. Lo que se conserva en la práctica es la forma: plana hasta cierto punto y después, el palo de hockey. La fórmula sirve para argumentar y para estimar; para dimensionar, se mide el codo con una prueba de carga.
Para ir más hondoDe dónde sale W = S ÷ (1 − ρ)+
En M/M/1 se puede calcular la probabilidad de encontrar n requests en el sistema: (1 − ρ) × ρⁿ. Con ρ = 0.6, el sistema está vacío el 40 % del tiempo, con una el 24 %, con dos el 14.4 %, y así, cada vez 0.6 veces menos. El promedio de esa lista es L = ρ ÷ (1 − ρ): con ρ = 0.6, 1.5 requests.
Ahora entra la Ley de Little: L = λ × W, así que W = L ÷ λ = ρ ÷ (λ × (1 − ρ)). Como ρ = λ × S, eso es S ÷ (1 − ρ). En el pod: 1.5 ÷ 100 por segundo = 15 ms, lo mismo que la tabla.
Y el p99: en M/M/1, la probabilidad de que una request tarde más de t es e^(−t ÷ W). Para que sea 1 %, e^(−t ÷ W) = 0.01, y t = W × ln(100) ≈ 4.6 × W.
Saturación, cuello de botella y headroom
EsencialSaturation, bottlenecks and headroom
Un recurso está en saturación (saturation) cuando ya no tiene tiempo libre: su utilización llega cerca del 100 % y lo que sigue llegando se va a la fila. Se reconoce por dos síntomas juntos: la fila crece y el throughput deja de subir aunque llegue más. La saturación es una de las cuatro señales doradas (golden signals) que C01 vigila por cada parte del sistema, junto con la latencia, el tráfico y los errores. El nombre y la lista vienen del libro de SRE de Google (Site Reliability Engineering, 2016); SRE es el oficio de mantener servicios funcionando.
Manda el recurso más lleno
Una request de C01 pasa por varios recursos en fila: la red del gateway, su CPU, la red hacia Triton, la GPU. Cada uno tiene su capacidad y su utilización. El que se satura primero es el cuello de botella (bottleneck): fija el throughput máximo de toda la cadena, y su fila es la que domina la latencia.
Es como una cadena de tubos: el agua que sale por segundo la decide el tubo más angosto. Ensanchar los otros no cambia nada.
En C01, en el pico de diseño, cada recurso va a su propio porcentaje:
| Recurso | Cuenta | Utilización |
|---|---|---|
| Red de cada nodo (máquina) del gateway | 1.2 Gb/s de tensores ÷ 1.56 Gb/s sostenidos | ≈ 77 % |
| CPU de cada pod | 100 × 6 ms ÷ 1 núcleo | 60 % |
| Cada T4 | 250 ÷ 1,200 fotos por segundo | ≈ 21 % |
Las cuentas de red son las de la lección de unidades: 500 tensores de 602 KB por segundo son ≈ 2.4 Gb/s, repartidos entre los 2 nodos del gateway. La pieza más cara, la GPU, es la más desocupada. El cuello de botella está en la red del gateway, y luego en su CPU. Por eso la Ronda anota como mejora mandar el tensor en uint8, 4 veces menos bytes, y no comprar otra GPU.
¿Cómo se encuentra en producción? Mirando qué fila crece. Si sube el tiempo en la fila de Triton, falta GPU. Si sube el histograma del preproceso, falta CPU en el gateway. Si las dos están planas y la latencia sube, busca en la red o en las conexiones.
Capacidad: lo que aguanta cumpliendo la promesa
La capacidad (capacity) de un servidor es el throughput más alto que sostiene cumpliendo su presupuesto de latencia. No es el máximo que alcanza a procesar: pasado cierto punto todavía procesa más, pero tarde. La capacidad de una T4 de C01, ≈ 1,200 fotos por segundo, es el punto donde su p99 todavía cabe en los ≈ 30 ms que le tocan a la GPU: la parte del presupuesto de 200 ms que la Ronda (M03) le reparte. Los 150 ms del timeout del gateway son otra cosa: cuándo rendirse, no cuánto debería tardar. Se mide con una prueba de carga, como verás más adelante.
Headroom: el margen que se compra a propósito
El headroom es la distancia entre la capacidad y la carga que planeas: la parte que dejas libre a propósito. No hay un número oficial; la regla práctica de la Ronda es que un servicio online se planea a ≈ 50–70 % de la capacidad medida; un job offline, que nadie espera, puede trabajar cerca del 100 %. La Ronda insiste: el headroom es protección, no desperdicio. Paga cuatro cosas a la vez:
- La p99. Lejos del codo, una ráfaga no se convierte en fila.
- La caída de una réplica o de una zona. Su carga se reparte en las demás.
- El tiempo de reacción del autoscaler. Un nodo con GPU tarda minutos en estar listo; un pico dura segundos.
- El error de tu medición. El máximo de la prueba no es el de producción: otra mezcla de fotos, relojes más bajos con calor.
Máquinas = ⌈pico ÷ (capacidad medida × utilización objetivo)⌉ + repuesto. Arranca con las T4 de C01: 500 req/s de pico de diseño, ≈ 1,200 medidas por T4, planeadas al 50 % y una de repuesto.
Pon el repuesto en 0: la única T4 va al ≈ 42 % en el pico, pero si cae, no queda nada. En C01, el headroom lo compra sobre todo el N+1.
Arranca con C01. Sube la utilización objetivo a 90 %: siguen siendo 2, porque la segunda la compra el repuesto. Ahora pon el pico en 1,670, el minuto de Hot Sale: al 50 % pide 4 T4; al 90 %, 3, y si cae una, las otras dos trabajan a ≈ 70 % de su máximo.
Para ir más hondoEl minuto de Hot Sale, recurso por recurso+
En la lección de unidades viste un minuto con ≈ 1,670 requests por segundo, 4.8 veces el pico de un día normal. Recorre los recursos con esa tasa:
- GPUs: 1,670 ÷ 2,400 ≈ 70 % del máximo de las dos. Aguantan, en la parte que ya empieza a doblarse.
- CPU del gateway: 1,670 × 6 ms ≈ 10 núcleos, contra los 8 de sus dos nodos: 125 %. Saturada.
- Red del gateway: ≈ 8 Gb/s contra ≈ 3.1 sostenidos: más de 250 %. Saturada, y primero.
Con dos recursos arriba de 100 %, la fila del gateway crece sin fondo. La GPU no puede ayudar: ni le llegan las fotos. Las salidas son preparar el evento (más nodos antes de que empiece, fotos en uint8) y, para lo que sobre, una fila con tope, que es la sección que sigue a la de batching.
Batching dinámico: más throughput a cambio de latencia
EsencialDynamic batching: throughput in exchange for latency
Una GPU corre un batch de 8 fotos en mucho menos que 8 veces lo que tarda una. Parte del costo es fijo por cada ejecución: lanzar los kernels, leer los pesos de la memoria, sincronizar. Con una sola foto, además, casi todos sus núcleos se quedan sin trabajo. Juntar fotos reparte ese costo fijo.
El batching dinámico (dynamic batching) hace eso en el servidor, con las requests que van llegando: las junta en una fila y manda el batch a la GPU cuando se llena (el tamaño máximo) o cuando vence una espera máxima, lo que pase primero. En C01, Triton espera como máximo 2 ms (max_queue_delay_microseconds: 2000) y junta hasta 32 fotos.
La Ronda (M03) trae una tabla ilustrativa, con forma realista, para una T4:
| Batch | La GPU tarda | Fotos por segundo, como máximo | Latencia extra contra batch 1 | Throughput contra batch 1 |
|---|---|---|---|---|
| 1 | 4 ms | 250 | — | 1× |
| 4 | 6 ms | ≈ 670 | +2 ms | ≈ 2.7× |
| 8 | 9 ms | ≈ 890 | +5 ms | ≈ 3.6× |
| 16 | 15 ms | ≈ 1,070 | +11 ms | ≈ 4.3× |
| 32 | 27 ms | ≈ 1,190 | +23 ms | ≈ 4.7× |
La cuenta de cada fila es tamaño ÷ tiempo: 8 fotos en 9 ms son 8 ÷ 0.009 ≈ 890 por segundo. El throughput sube mucho al principio y luego se aplana: de 1 a 8 se multiplica por 3.6, y de 8 a 32 solo por 1.3, mientras el batch tarda el triple. Rendimientos decrecientes.
El batch que se forma depende de lo que llega
32 es un techo, no lo que pasa. El tamaño real depende de cuántas requests llegan mientras el batcher espera y mientras la GPU está ocupada con el batch anterior:
¿Entonces para qué se activa? Para los malos ratos. Si cae una GPU, la otra recibe 500 por segundo y los batches crecen solos a ≈ 2–4: la GPU absorbe la carga extra sin escalar. En la lección de probabilidad viste la misma idea con Poisson: a 250 por segundo, ≈ 61 % de las ventanas no recibe a nadie.
Throughput contra latencia es la perilla de todo serving. Un job offline, como el backfill (clasificar de una vez todo lo que ya existía) de las ≈ 200 millones de fotos viejas, usa batches llenos de 32 todo el tiempo: nadie espera cada foto. En los LLMs la versión es el batching continuo de la lección de LLMs: las secuencias entran y salen del batch en cada token, y la perilla mueve el TTFT y el TPOT.
Backpressure: una fila con tope
EsencialBackpressure: a queue with a limit
Una fila sin límite nunca falla. Ese es el problema.
Imagina que a una T4 de C01 le ofrecen 1,400 fotos por segundo y solo procesa ≈ 1,210, su máximo absoluto (un poco más que los 1,200 que cumplen el presupuesto). Sobran 190 cada segundo, y se van a la fila. A los 10 segundos esperan 1,900, y la última tardará 1,900 ÷ 1,210 ≈ 1.6 s solo en esperar. El gateway le da a Triton 150 ms antes de rendirse. Todas las requests llegan tarde: la GPU trabaja al máximo, el throughput es 1,210 por segundo, y ninguna respuesta le sirve a nadie. Y no hay un solo error en los logs hasta que empiezan los timeouts.
Una fila con tope
Una cola acotada (bounded queue) es una fila con un límite: por cuántas caben o por cuánto pueden esperar. Si está llena, la request nueva se rechaza en el momento. C01 pone topes en cada salto:
- En el pod del gateway:
MAX_INFLIGHT = 16requests en vuelo (ya aceptadas y todavía sin respuesta), y solo 2 decodes a la vez, con 50 ms como máximo de espera por un lugar (lección de async). - En Triton: la fila del batcher rechaza lo que ya esperó 100 ms (en su configuración,
default_queue_policycon REJECT), el último eslabón de la cadena 300 → 250 → 150 → 100 ms de la lección anterior: el backend espera 300, el gateway se da 250, a Triton le da 150 y su fila rechaza a los 100.
Un tope por número también acota el tiempo, por Little: con 16 lugares en un pod que termina 100 por segundo, el tiempo promedio adentro no pasa de ≈ 16 ÷ 100 = 0.16 s, aunque se llene.
Que la presión llegue hasta atrás
Backpressure (contrapresión) es la señal que viaja hacia atrás y dice «no me mandes más, no puedo». Puede ser un 503, el código de estado de «no disponible», con un Retry-After (un header de la respuesta que dice «vuelve en tantos segundos»), una fila que no deja meter nada hasta que haya lugar, o una conexión que no acepta más bytes. Con ella, el que produce se frena al ritmo del que consume, en vez de llenarle una fila sin fondo.
En C01 la cadena es corta: Triton rechaza, el gateway contesta un 503 en microsegundos y el backend del marketplace le muestra a Ana el selector de categorías: su fallback. Rechazar a propósito lo que no cabe, para salvar lo demás, es load shedding, de la lección anterior. Una cola acotada es su forma más simple.
- Backend · manda la foto
El backend del marketplace manda la foto y espera como máximo 300 ms. Si recibe un error, no reintenta a ciegas: muestra el selector. - Pod del gateway · MAX_INFLIGHT = 16
Una cola acotada por número: como mucho 16 requests en vuelo por pod, y solo 2 decodes a la vez, con 50 ms de espera máxima por un lugar. La 17.ª se rechaza en microsegundos. - Fila de Triton · tope: 100 ms
Una cola acotada por tiempo: lo que ya esperó 100 ms se rechaza (default_queue_policycon REJECT). Es el último eslabón de 300 → 250 → 150 → 100 ms. Detrás está la T4, con ≈ 1,210 fotos por segundo como máximo. - Selector manual · el fallback
El fallback: Ana elige la categoría a mano. Peor que una sugerencia, mucho mejor que una pantalla esperando. - 503 rápido · «no puedo: no esperes»
El backpressure de C01: un 503, el código de «servicio no disponible», que cuesta microsegundos y le dice a quien llama que no puede más. Rechazar a propósito lo que no cabe es load shedding.
¿Dónde va cada tope? Donde se rompe la promesa, no donde se acaba la memoria. Una fila que deja esperar más de lo que queda del presupuesto solo guarda requests que ya perdieron. La regla de la Ronda: si esperó tanto que ya no va a llegar a tiempo, es mejor rechazarla que gastar GPU en ella.
Medir de verdad: pruebas de carga y goodput
EsencialMeasuring for real: load tests and goodput
Todos los números de esta lección que dicen «medido» salen de una prueba de carga (load test): un programa que le manda tráfico sintético al servicio, cada vez más, y anota el throughput y los percentiles de latencia en cada escalón. En una entrevista, decir cómo lo medirías vale tanto como el número.
La rampa y su codo
La prueba básica es una rampa: subir por escalones la carga ofrecida, lo que el generador manda. Mientras hay capacidad, el throughput sigue a la carga y la latencia casi no cambia. Pasado el codo (knee), el throughput se aplana y la latencia se dispara, porque todo lo que sobra se va a la fila. Una rampa ilustrativa contra una T4 de C01 (M03):
| Carga ofrecida | Throughput logrado | p50 | p99 | Lectura |
|---|---|---|---|---|
| 300 fotos/s | 300 | 6 ms | 11 ms | Plano |
| 600 fotos/s | 600 | 7 ms | 13 ms | Plano: aquí planea C01 |
| 900 fotos/s | 900 | 9 ms | 17 ms | Empieza a doblarse |
| 1,200 fotos/s | ≈ 1,190 | 14 ms | 28 ms | El codo: el último escalón dentro del presupuesto |
| 1,300 fotos/s | ≈ 1,210 | ≈ 110 ms | ≈ 130 ms | Saturada: la fila llega a su tope y ≈ 7 % se rechaza |
| 1,400 fotos/s | ≈ 1,210 | ≈ 110 ms | ≈ 130 ms | ≈ 14 % de rechazos |
La capacidad es el codo, 1,200, no el 1,210 del final. Y fíjate en las dos últimas filas: con la fila acotada de Triton, la latencia se queda cerca del tope y lo que sube son los rechazos.
Para ir más hondoCuatro pruebas, y cómo hacer que predigan producción+
La rampa es una de cuatro pruebas. El soak sostiene el pico de diseño por horas (500 por segundo durante 4 a 8 h en C01) y encuentra lo que solo sale con el tiempo: fugas de memoria, GPUs que bajan su reloj por calor. El spike salta de la carga normal a 3–5 veces en segundos, para ver si los topes y el autoscaler responden. El failover apaga un nodo con GPU en medio del pico, para ver si N+1 aguanta de verdad.
Para que la prueba prediga producción: descarta los primeros minutos (el arranque calienta cachés y conexiones), usa fotos reales (en C01, ≈ 85 % de celular de ≈ 200 KB y ≈ 15 % de la web, de varios MB), el mismo hardware y la misma configuración, y vigila que el generador no sea el que se satura.
Carga abierta y carga cerrada
Hay dos maneras de generar el tráfico, y no miden lo mismo:
- Carga cerrada (closed-loop): N usuarios virtuales; cada uno manda su siguiente request cuando le llega la respuesta de la anterior.
- Carga abierta (open-loop): las requests llegan a una tasa fija, pase lo que pase con las respuestas.
Tus usuarios reales se parecen a la abierta: un vendedor en Monterrey no espera a que termine la foto de otro para subir la suya. La cerrada tiene una trampa: cuando el servidor se frena, el generador también se frena, y manda menos requests justo cuando el servidor está lento. Los peores momentos casi no se miden, y el p99 sale mejor que la realidad. La regla de la Ronda: para medir latencia a una tasa dada, carga abierta; para encontrar el máximo, sirve la cerrada. Los números y las herramientas, en la sección extra.
Goodput: el throughput que sí cuenta
El throughput cuenta todo lo que el servidor termina. Pero una respuesta que llega después del timeout, o un error, no le sirve a nadie. El goodput es el throughput útil: las requests por segundo que terminan bien y dentro de su SLO. En una prueba de carga, la carga máxima a la que todavía se cumple el SLO.
En la cuenta de la sección anterior, la T4 sin tope tenía un throughput de 1,210 por segundo y un goodput de casi cero. Con tope, los dos valían ≈ 1,210. El goodput es el número que separa un servidor ocupado de un servidor que sirve.
En los LLMs el goodput manda todavía más, porque cada respuesta trae dos promesas: el bloque que sigue lo cuenta.
Para ir más hondoGoodput en los LLMs: el barrido de C10+
En los LLMs, el goodput es el número con el que se compra la flota (M14). El artículo de DistServe (OSDI 2024, un congreso de sistemas) lo definió así para el serving de LLMs: la tasa más alta de requests por segundo, por GPU, a la que todavía se cumple el SLO en la fracción que pediste, por ejemplo el 90 % de las requests. Cada respuesta tiene dos promesas, el TTFT (cuánto tarda el primer token) y el TPOT (cuánto tarda cada token después). Al subir la concurrencia, los tokens por segundo siguen subiendo un rato, pero el TTFT se rompe primero, porque la fila crece antes de que cada paso se vuelva lento. En C10, el caso de la Ronda que sirve un LLM a varios equipos, el goodput de una réplica es ≈ 6 requests por segundo, y se planea al 70 %: ≈ 4.2.
La Ronda (M14) muestra un barrido ilustrativo de una réplica de C10. Las promesas: TTFT p95 ≤ 1 s y TPOT p95 ≤ 50 ms.
| Requests a la vez | TTFT p95 | TPOT p95 | Requests/s | ¿Cumple? |
|---|---|---|---|---|
| 16 | 0.31 s | 27 ms | ≈ 2.6 | Sí, con GPU de sobra |
| 48 | 0.66 s | 42 ms | ≈ 4.9 | Sí |
| 72 | 0.97 s | 49.6 ms | ≈ 6.06 | Sí, en el límite: el goodput |
| 80 | 1.34 s | 53.8 ms | ≈ 6.19 | No: rompe las dos |
| 96 | 2.4 s | 64 ms | ≈ 6.38 | No |
De 72 a 96 a la vez, el throughput sube 5 % y el TTFT se multiplica por ≈ 2.5. Quien vende «nuestra réplica hace 1,600 tokens por segundo» habla de la última fila, donde nadie recibe su primer token a tiempo. Las requests por segundo salen de Little al revés: concurrencia ÷ latencia promedio de cada respuesta completa (no la p95 de la tabla, que daría menos).
Colas a fondo: varios servidores, menos variabilidad y pruebas que mienten
ExtraQueues in depth: many servers, less variability and lying load tests
M/M/1 tiene un servidor. Los sistemas reales tienen varios, sus tiempos de servicio son más parejos que una exponencial, y se miden con generadores que a veces mienten. Tres ideas para la segunda pasada.
Una fila para varios servidores
¿Qué es mejor, dos cajas con una fila cada una o dos cajas con una sola fila? Con una sola fila, nadie espera detrás de una caja ocupada mientras la otra está libre. El modelo es M/M/c: c servidores y una fila compartida. Su fórmula se llama Erlang C, por Agner Krarup Erlang, un matemático e ingeniero danés de la compañía de teléfonos de Copenhague, que la publicó en 1917 para dimensionar las centrales telefónicas. No hace falta memorizarla; sí saber lo que dice.
import math
def espera_mmc(c, rho, S): # Erlang C: espera promedio en la fila, en ms
a = c * rho # servidores ocupados, en promedio
cola = a**c / math.factorial(c) / (1 - rho)
p_esperar = cola / (sum(a**k / math.factorial(k) for k in range(c)) + cola)
return p_esperar * S / (c * (1 - rho)), p_esperar
S, rho = 10, 0.8 # 10 ms de servicio y 80 % de utilización por servidor
print(f"cada servidor con su fila (M/M/1): {rho * S / (1 - rho):.1f} ms de espera")
for c in (2, 4, 8):
espera, p = espera_mmc(c, rho, S)
print(f"una fila para {c} (M/M/{c}): {espera:.1f} ms de espera; esperan {p:.0%}")
print(f"servicio constante (M/D/1) al 90 %: {0.9 * S / (2 * 0.1):.0f} ms, contra {0.9 * S / 0.1:.0f} en M/M/1")cada servidor con su fila (M/M/1): 40.0 ms de espera
una fila para 2 (M/M/2): 17.8 ms de espera; esperan 71%
una fila para 4 (M/M/4): 7.5 ms de espera; esperan 60%
una fila para 8 (M/M/8): 2.9 ms de espera; esperan 46%
servicio constante (M/D/1) al 90 %: 45 ms, contra 90 en M/M/1A la misma utilización de 80 %, compartir la fila entre dos baja la espera de 40 a ≈ 18 ms, y entre cuatro a ≈ 7.5 ms (son los números de M03). Por eso un grupo grande de réplicas aguanta mejor la misma utilización que uno chico.
En la práctica, «una fila compartida» es un balanceador de carga que manda cada request a la réplica con menos requests en vuelo (el algoritmo least outstanding requests, que el ALB de AWS, el balanceador de C01, ofrece como opción; el que usa por omisión es round robin). El reparto al azar de un Service de Kubernetes, en el modo iptables de kube-proxy, el más común, se parece más a filas separadas: un pod puede tener fila mientras otro descansa. Lo ves en la lección de balanceadores, con los algoritmos de balanceo.
Menos variabilidad, menos espera
La última línea del programa es la otra palanca. Si el tiempo de servicio es constante (el modelo M/D/1, de determinístico), la espera promedio es la mitad que en M/M/1: ρ × S ÷ (2 × (1 − ρ)). A 90 % y 10 ms, 45 ms en vez de 90.
La regla general, la aproximación de Kingman (John Kingman, 1961), dice que la espera promedio es ρ ÷ (1 − ρ) por el promedio de qué tan variables son las llegadas y el servicio, por S. «Qué tan variable» se mide como (desviación estándar ÷ promedio)²: vale 1 para una exponencial y 0 para algo constante. Con todo exponencial, ese promedio vale 1 y sale M/M/1 exacto; con llegadas de Poisson y servicio constante, (1 + 0) ÷ 2, la mitad. Es una aproximación, y es mejor cuanto más cerca está ρ del 100 %, justo donde importa. Una GPU que corre el mismo engine con el mismo shape se parece al caso constante. Lo que agrega variabilidad (fotos de 200 KB mezcladas con fotos de 12 MP, batches de tamaño variable, pausas del programa) te empuja hacia el peor caso. Por eso C01 limita el tamaño de las fotos y usa draft mode: no solo bajan el promedio, emparejan el servicio.
Coordinated omission: cuando la prueba miente
Gil Tene, ingeniero de Azul Systems (una empresa que hace los programas que corren Java) y autor de wrk2, una herramienta de pruebas de carga, le puso nombre a la trampa de la carga cerrada: coordinated omission. El generador se «coordina» con el servidor y omite justo las mediciones que más importan. La Ronda (M03) lo muestra con un experimento mental: un servidor que responde en 10 ms, salvo una pausa de 1 s a la mitad de una prueba de 10 s.
Para simplificar, se supone que al terminar la pausa el servidor despacha de golpe lo acumulado. La cuenta se puede comprobar con las dos listas de latencias:
import math
def p99(latencias): # el p99 de la lección de percentiles: contar desde abajo
orden = sorted(latencias)
return orden[math.ceil(0.99 * len(orden)) - 1]
cerrada = [10] * 9_000 + [1_010] * 10 # 10 usuarios atorados 1 s
abierta = [10] * 9_000 + [1_000 - k + 10 for k in range(1_000)] # llega una por ms en la pausa
print("cerrada:", len(cerrada), "mediciones, p99 =", p99(cerrada), "ms")
print("abierta:", len(abierta), "mediciones, p99 =", p99(abierta), "ms")cerrada: 9010 mediciones, p99 = 10 ms
abierta: 10000 mediciones, p99 = 910 msLas herramientas, y cuál es abierta
Las que vas a oír en la Ronda (M03 y M07):
| Herramienta | Qué carga | Modelo de carga | |
|---|---|---|---|
perf_analyzer, de NVIDIA | Triton directo | Por concurrencia (cerrado) o por tasa (abierto) | |
| Locust | El servicio completo, con usuarios escritos en Python | Cerrado por omisión | |
| k6 | El servicio completo | Tiene un modo de tasa fija: abierto | |
| wrk2 y vegeta | Uno o pocos [[endpoint | endpoints]] HTTP | Tasa constante: abierto |
Las salidas: un generador abierto cuando lo que mides es la latencia, o, con uno cerrado, un timeout más corto que el intervalo entre las requests de cada usuario, para que un servidor lento no frene el ritmo. Y una costumbre que delata el problema: anota la tasa que de verdad mandó el generador. Si pediste 1,000 por segundo y salieron 900, la prueba se coordinó con el servidor.
Hay una prueba más de que importa la forma de generar. Con carga cerrada, Little dice que la tasa que manda el generador es usuarios ÷ latencia: si la latencia se multiplica por 10, la tasa se divide entre 10. El servidor «mejora» solo, porque le llega menos. Con carga abierta no hay ese alivio, y por eso es la que se parece a producción: el día del Hot Sale nadie deja de subir su foto porque el servicio esté lento.
Errores comunes
EsencialCommon mistakes
1. Planear al 90 % porque «queda 10 % libre»
En una fila, ese 10 % libre vale poco: la espera ya es 9 veces la del 50 %, y cualquier ráfaga saca al p99 del SLO.
Cómo evitarlo: planea a 50–70 % de la capacidad medida en lo online (más cerca de 50 % con tráfico en ráfagas y un SLO apretado), y deja el 100 % para los jobs offline, donde nadie espera.
2. Escalar con la señal equivocada
La utilización de nvidia-smi marca 30 % con batches de 1 y se queda en 100 % igual con 2 que con 200 en la fila. Y la CPU del pod de Triton sigue al throughput terminado: cuando la GPU se satura, se congela justo cuando más la necesitas (M06).
Cómo evitarlo: escala con una señal del recurso que se satura primero. En el gateway, su CPU; en Triton, el tiempo en la fila.
3. Una fila sin tope
asyncio.Queue() sin maxsize, un pool sin límite, un servidor que acepta todo. Nunca falla: solo se atrasa, en silencio, hasta que todas las respuestas llegan tarde.
Cómo evitarlo: un tope en cada fila, puesto donde se rompe el presupuesto de latencia, y un rechazo rápido con su fallback del otro lado.
4. Creerle al p99 de una prueba cerrada
Locust con 50 usuarios reporta un p99 precioso, porque cuando el servidor se frenó, los usuarios también.
Cómo evitarlo: para medir latencia a una tasa, un generador abierto a la tasa de diseño. Y compara siempre la tasa que pediste con la que de verdad se mandó.
5. Dimensionar con el throughput máximo o con el promedio
El máximo de una rampa no es la capacidad: pasado el codo, el servidor procesa más, pero tarde. Y el promedio no es la carga: la Ronda (M03) cuenta un gateway con 1 vCPU (un núcleo rentado en la nube) para 116 requests por segundo (116 × 6 ms ≈ 0.7 vCPU de trabajo) que a las 8 de la noche, con 350, necesitaba 350 × 6 ms ≈ 2.1.
Cómo evitarlo: capacidad = el codo que cumple el presupuesto, o el goodput; carga = el pico de diseño. Después, el headroom.
6. Sumar o promediar lo que no se suma
Sumar los p99 de los tramos, promediar los p99 de los pods o meter un p99 en la Ley de Little («500 × 200 ms = 100 en vuelo en el peor caso»). Little habla de promedios, y de un sistema estable, donde a la larga sale lo mismo que entra: con una fila que crece sin fondo, no hay un W que meter.
Cómo evitarlo: reparte el presupuesto por tramo, pero mide el p99 de punta a punta, con histogramas que sí se suman. Para Little, el W promedio.
En la vida real
ExtraIn real life
C01: escalar por la fila, no por la GPU
Triton escala con KEDA, una pieza que conecta el HPA con métricas de Prometheus (el sistema que guarda las métricas), mirando el tiempo promedio en la fila de Triton, con un objetivo de 10 ms (M06). Es el síntoma que siente el usuario. Con la misma cuenta del HPA (réplicas × métrica ÷ objetivo, hacia arriba): normalmente la fila marca ≈ 2 ms y KEDA pediría ⌈2 × 2 ÷ 10⌉ = 1 réplica, pero el mínimo es 2, por N+1. Si la GPU se satura y la fila sube a 25 ms, pide ⌈2 × 25 ÷ 10⌉ = 5, y su techo de costo lo deja en 4, que agrega de una por minuto. Además, una alerta avisa si la fila pasa de 20 ms durante 10 minutos.
La señal no es lineal, como predice la curva: casi 0 hasta cerca del codo y después salta. Por eso C01 baja réplicas despacio. Y no confía en el autoscaler para lo rápido: con nodos de GPU que tardan de 5 a 13 minutos en estar listos, la Ronda lo dice claro: «un spike de 30 segundos lo absorbe el margen, no el autoscaler». El headroom de 50 % es su seguro.
El gateway, en cambio, escala por CPU al 60 %, porque ahí la CPU es el recurso que se satura: ρ = 0.6 en cada pod, la fila de la sección de utilización.
C01: «el p99 pasó de 80 a 400 ms»
Es una pregunta de entrevista de la Ronda, y la respuesta es esta lección hecha procedimiento. Primero, ¿desde cuándo? Se cruza con los últimos cambios: del modelo, del gateway, de quien llama. Después, ¿en qué tramo? Se separa el histograma del preproceso, el tiempo en la fila de Triton y el cómputo en la GPU.
- Si creció la fila: ¿subió el tráfico, o lo subió un solo cliente? ¿KEDA llegó a su máximo? ¿Se cayó un pod y los demás quedaron más llenos?
- Si creció el cómputo: ¿un engine nuevo, otro tamaño de batch promedio, una GPU que bajó su reloj por calor?
- Si creció el preproceso: ¿llegan fotos más grandes? ¿El pod se topa con su límite de CPU? ¿Alguien metió una cuenta pesada en el event loop?
Primero se mitiga (regresar a la versión anterior, escalar, limitar al cliente ruidoso) y después se busca la causa. La regla de fondo es la de la primera sección: la latencia se desarma en viajar, esperar y trabajar, tramo por tramo.
C01: el job que llenó la fila
Las ≈ 200 millones de fotos viejas del catálogo se clasifican con un job offline: throughput, no latencia, con batches llenos de 32. Si ese job usara el mismo Triton que la app, sería el clásico vecino ruidoso (noisy neighbor): sus batches llenan la fila y el p99 de Ana explota, aunque el tráfico de la app no cambió. Por eso corre aparte, en sus propias máquinas. Y en el Triton de la app, la fila tiene dos prioridades (priority_levels: 2): la app va primero y cualquier otro cliente detrás. Cuando hay que rechazar, se rechaza primero lo que no tiene a una persona esperando.
C10: el tope de admisión va donde se rompe la promesa
C10 sirve un LLM a varios equipos con 16 réplicas de vLLM, el programa que corre el modelo. Su gateway pone un tope de requests en vuelo hacia toda la flota: una cola acotada por número. ¿Dónde va el tope? La Ronda (C10) responde: donde se rompe el SLO, no en el plan. El barrido de la sección de goodput dice que cada réplica cumple hasta 72 a la vez, así que el tope es 16 × 72 = 1,152 en vuelo. En el pico de diseño hay ≈ 630: se usa ≈ 55 %, y el resto queda para los picos. Si el tope fuera la carga planeada (≈ 40 por réplica), el gateway contestaría 503 desde ≈ 61 requests por segundo, con GPUs de sobra.
Cuando no hay lugar, lo interactivo espera en una fila acotada hasta 0.5 s, porque esperar más ya rompe la promesa de un TTFT de 1 s. De noche, los trabajos batch esperan a propósito detrás de lo interactivo: su TTFT puede ser de minutos, y es correcto. Y el plan al 70 % tampoco es capricho: si se cae un nodo con 4 de las 16 réplicas, quedan 12 × 6 = 72 requests por segundo contra un pico de 60. Es headroom con nombre y apellido.
C13: tiempo real y una fila que solo atrasa
En los subtítulos en vivo de C13, una fila sin tope convierte cada milisegundo de más por pedazo de audio en atraso acumulado (lección de async). Ahí el tope no es por cantidad sino por edad: si el atraso pasa de ≈ 2 s, la sesión se cierra y se abre en una GPU con lugar. En tiempo real, una respuesta vieja es una respuesta equivocada.
Fuera de ML: carreteras, bancos y urgencias
Una autopista al 90 % de su capacidad fluye hasta que alguien frena: la ráfaga de frenazos no se deshace, porque no hay tiempo libre. Es el palo de hockey con coches. Muchos bancos y aeropuertos usan una sola fila para varias cajas: es M/M/c, y la espera baja sin contratar a nadie. Y las urgencias de un hospital no atienden en orden de llegada: clasifican por gravedad (triage), como las colas con prioridad de C01 y C10.
Ejercicios
EsencialExercises
Resuélvelos con lápiz antes de abrir la solución. Los de utilización y capacidad se comprueban con las calculadoras.
- 1Fácil
A un servicio le llegan 40 requests por segundo y cada una ocupa 20 ms de CPU. ¿Qué utilización tiene con 1 núcleo? ¿Y con 2? ¿Cuántas por segundo aguanta como máximo con 1?
Ver la solución paso a paso
- Con 1 núcleo: ρ = 40 × 0.020 ÷ 1 = 0.8, 80 %.
- Con 2: 40 × 0.020 ÷ 2 = 0.4, 40 %.
- Máximo con 1: el núcleo termina 1 ÷ 0.020 = 50 por segundo. Con 40 está a 80 % de ese máximo: es la misma cuenta.
- 2Fácil
La Ronda arma su tabla de M/M/1 con S = 10 ms. Calcula W a 50, 70, 80, 90 y 95 %.
Ver la solución paso a paso
- W = 10 ÷ (1 − ρ): 10 ÷ 0.5 = 20 ms; 10 ÷ 0.3 ≈ 33 ms; 10 ÷ 0.2 = 50 ms; 10 ÷ 0.1 = 100 ms; 10 ÷ 0.05 = 200 ms.
- De 50 a 95 %, la carga no llega al doble y el tiempo se multiplica por 10.
- 3Fácil
Una cadena tiene tres tramos, con números parecidos a los de C01: la CPU del gateway aguanta 800 por segundo, la red 650 y las GPUs 2,400. ¿Cuál es el throughput máximo de la cadena? ¿Ayuda comprar otra GPU?
Ver la solución paso a paso
- El máximo es el del tramo más angosto: 650 por segundo, la red. Es el cuello de botella.
- Otra GPU sube su tramo a 3,600 y la cadena sigue en 650: no ayuda. Ayuda mandar menos bytes (uint8) o más red.
- 4Fácil
Con la ventana de 2 ms de Triton, ¿cuántas requests llegan en la ventana a una T4 que recibe 500 por segundo? ¿Cuánto tardaría en llenarse un batch de 32?
Ver la solución paso a paso
- En la ventana: 500 × 0.002 = 1 request en promedio.
- Para llenar 32: 32 ÷ 500 = 0.064 s = 64 ms. Por eso la ventana corta a los 2 ms: esperar a llenarlo se comería un tercio de los 200 ms de la promesa.
- 5Medio
El pico de diseño de C01 sube a 650 por segundo. Con la T4 a 1,200 medidas y planeada al 50 %, ¿cuántas compras con N+1? ¿A qué utilización trabaja cada una en el pico? ¿Y si cae una?
Ver la solución paso a paso
- Por throughput: ⌈650 ÷ 600⌉ = ⌈1.08⌉ = 2. Con repuesto: 3 T4.
- Las tres vivas: 650 ÷ (3 × 1,200) ≈ 18 %.
- Si cae una: 650 ÷ (2 × 1,200) ≈ 27 %. La fracción que pasa de 600 obliga a la segunda GPU: el redondeo hacia arriba compra mucho headroom.
- 6Medio
Una prueba cerrada usa 100 usuarios virtuales. Con el servidor respondiendo en 50 ms, ¿qué tasa manda? Si el servidor se frena a 500 ms, ¿qué tasa manda ahora? ¿Qué le pasa a lo que mide?
Ver la solución paso a paso
- Cada usuario manda 1 ÷ 0.05 = 20 por segundo: 100 × 20 = 2,000 por segundo. Es Little al revés: λ = L ÷ W.
- A 500 ms: 100 ÷ 0.5 = 200 por segundo, diez veces menos.
- Justo cuando el servidor está lento, la prueba manda menos y mide menos requests lentas. Los usuarios reales seguirían llegando a 2,000: el p99 real sería peor que el medido.
- 7Medio
En una rampa, el servicio cumple su p99 ≤ 200 ms hasta 900 requests por segundo; a 1,000, el p99 es 260 ms y el throughput 990. ¿Cuál es su capacidad? ¿Su goodput a 1,000? Si planeas al 60 %, ¿cuánto le asignas?
Ver la solución paso a paso
- Capacidad: 900 por segundo, el último escalón que cumple. No 990: ahí ya no cumple.
- A 1,000 ofrecidas, el SLO se rompe, así que más de 1 % llega tarde. El goodput de la prueba, la carga máxima que cumple, sigue siendo 900.
- Planeado al 60 %: 900 × 0.6 = 540 por segundo por réplica.
- 8Reto
Un pico sube el tráfico de C01 de 500 a 750 por segundo en un minuto. El gateway tiene 5 pods con 1 núcleo y S = 6 ms. Supón que el HPA tarda 3 minutos en tener listos los pods nuevos. (a) ¿Qué ρ, W y p99 tiene cada pod en esos 3 minutos, con M/M/1? (b) ¿Cuántos pods pide el HPA, con su objetivo de 60 %? (c) ¿Qué cambiarías para no depender de esos 3 minutos?
Ver la solución paso a paso
- (a) 750 ÷ 5 = 150 por pod; ρ = 150 × 0.006 = 0.9. W = 6 ÷ 0.1 = 60 ms; p99 ≈ 4.6 × 60 ≈ 276 ms: solo el pod ya pasa los 200 ms de la promesa. Contra el pico de diseño (ρ = 0.6, 9 ms de espera en la fila), la espera se multiplicó por 6 con 1.5 veces la carga: 0.9 × 6 ÷ 0.1 = 54 ms.
- (b) ⌈5 × 90 ÷ 60⌉ = ⌈7.5⌉ = 8 pods: 750 ÷ 8 ≈ 94 por pod, ρ ≈ 0.56.
- (c) Más headroom de base para los picos conocidos (un mínimo de pods más alto antes de un evento), el
MAX_INFLIGHTque rechaza rápido lo que no cabe, y mandar el tensor en uint8 para aliviar la red, el primer cuello de botella.
- 9Reto
A una T4 le ofrecen 1,600 fotos por segundo y procesa ≈ 1,210. (a) Sin tope en la fila, ¿cuánto espera la última request que llegó a los 5 s? ¿Cuál es el goodput si el gateway corta a los 150 ms? (b) Con el tope de 100 ms de Triton, ¿qué fracción se rechaza? (c) ¿Por qué la CPU del pod de Triton no le avisaría al HPA?
Ver la solución paso a paso
- (a) Sobran 1,600 − 1,210 = 390 por segundo. A los 5 s esperan 1,950, que tardan 1,950 ÷ 1,210 ≈ 1.6 s en salir. Todas pasan los 150 ms: goodput ≈ 0.
- (b) (1,600 − 1,210) ÷ 1,600 ≈ 24 % rechazadas; el 76 % restante sale antes del timeout. La Ronda (M06) trae justo este caso: ≈ 24 % de rechazos.
- (c) La CPU de ese pod sigue al throughput terminado, que se quedó en 1,210: la señal se congela mientras se rechaza una de cada cuatro. Por eso C01 escala Triton por el tiempo en la fila.
Lo que sigue
ExtraWhat comes next
En la Ronda:
- M03 · Latencia y capacidad es esta lección en orden de entrevista: latencia, throughput y concurrencia («25 = 500 × 0.05»), el presupuesto tramo por tramo, la tabla de M/M/1 con S = 10 ms, el pooling, el batching de Triton, cuántas GPUs comprar y la rampa con su codo. La frase que pide: «I'd plan each GPU at about 50% of its measured capacity».
- M06 · Orquestación y escalado escala el gateway por CPU y Triton por el tiempo en la fila, con KEDA, y explica por qué la CPU de Triton se congela.
- M07 · Servidores de modelos mide con
perf_analyzery separa el tiempo en la fila del tiempo de cómputo de Triton. - M14 · Sistemas con LLMs dimensiona con goodput, TTFT y TPOT.
- Los casos. C01 (2 T4 al 50 %, la ventana de 2 ms, los topes 300 → 250 → 150 → 100), C10 (el tope de admisión) y C13 (el atraso en tiempo real).
En el Dojo: cuando abra la estación 6, «Código de serving», ahí vas a poner los topes y los timeouts del gateway. La 9, «Kubernetes», trae el HPA; y la 11, «La request en vivo», mide la latencia de una request real, tramo por tramo.
En Sensei: esta lección cierra «La red y la web». La fase siguiente, «Datos y almacenamiento», trae «Colas y streams: mensajes que esperan», donde las filas de esta lección se vuelven servicios propios, con mensajes que se guardan en disco. Más adelante, «Regiones, zonas y disponibilidad» explica las zonas donde vive el repuesto N+1.
Palabras que vas a oír
EsencialWords you will hear
Estos son los conceptos que presentó esta lección. Cada uno tiene su tarjeta con la definición completa.
Latencia de punta a puntaend-to-end latency · e2eEl tiempo total de una request entre dos puntos que tú nombras, sumando todo lo que pasa en medio: viajar por la red, esperar en las filas y trabajar en cada servidor.Más +
Sin decir cuáles puntas, el número no significa nada. En C01, el SLO la mide del lado del servidor: desde que el balanceador tiene la request completa hasta que sale la respuesta; la subida desde el celular queda fuera. En cada salto, el tiempo se va en tres cosas: viajar (la red, con su RTT y sus handshakes), esperar (tiempo en cola) y trabajar (tiempo de servicio). Los tramos se miden por separado para saber dónde buscar, pero el p99 total se mide de punta a punta: los p99 de los tramos no se suman.
Ejemplo. Una foto de C01 con poco tráfico: 3 ms de entrada, 6 de preproceso, 3 de viaje a Triton, 2 de fila, 4 de GPU, 1 de vuelta y 1 de salida: 20 ms, de los que 7 son viaje, 2 espera y 11 trabajo.
Tiempo de servicioservice time · SEl tiempo que un recurso trabaja de verdad en una request, sin contar lo que ella espera su turno. En las fórmulas de colas se escribe S.Más +
Es el trabajo en sí: los ≈ 6 ms de CPU que el gateway de C01 gasta en decodificar y preparar una foto, o los 3 a 6 ms de la T4 para un batch chico. Casi no cambia con el tráfico. Se baja con mejor hardware o con menos trabajo: el draft mode de JPEG, un modelo en FP16, menos bytes. Junto con las llegadas da la utilización, ρ = λ × S ÷ c, y qué tan parejo sea también importa: un servicio constante espera la mitad que uno muy variable.
Ejemplo. Cinco fotos de 6 ms cada una tardan de 6 a 14 ms si llegan en ráfaga: el tiempo de servicio es el mismo para las cinco; lo que cambia es la espera.
Tiempo en colaqueueing time · queueing delay · wait timeLo que una request pasa esperando a que el recurso que necesita se desocupe. Casi cero con poco tráfico; es la parte de la latencia que se dispara cerca del pico.Más +
Aparece aunque el servidor no esté lleno, porque las requests llegan al azar y en ráfagas: cada ráfaga deja una fila. No se baja optimizando el código, sino con más capacidad o con llegadas y servicios más parejos. En una fila M/M/1 vale ρ × S ÷ (1 − ρ), y crece sin límite cuando la utilización se acerca a 1. Es la mejor señal de saturación: C01 escala Triton por el tiempo promedio en su fila, con un objetivo de 10 ms.
Analogía. La fila de la caja del súper: la cajera cobra igual de rápido, pero si llegan tres carritos juntos, el tercero espera a los otros dos. Se rompe en que tú ves la fila y te cambias de caja; una request espera donde la mandaron.
Ejemplo. Un pod del gateway con 6 ms por foto: a 60 % de utilización, cada foto espera ≈ 9 ms en promedio; a 90 %, ≈ 54 ms, con el mismo trabajo.
Utilizaciónutilization · ρ (rho)La fracción del tiempo en que un servidor está ocupado. Se calcula como las requests que llegan por segundo (λ), por los segundos de trabajo de cada una (S), entre el número de servidores (c): ρ = λ × S ÷ c.Más +
Es una cuenta de horas de trabajo: cada segundo llegan λ × S segundos de trabajo para c servidores. También se calcula como lo que llega entre la capacidad. Si llega a 1, la fila crece sin fin; y mucho antes, la espera ya se dispara, como 1 ÷ (1 − ρ). Por eso un servicio online se planea a ≈ 50–70 %. No es la utilización de GPU de nvidia-smi, que mide el tiempo con algún kernel corriendo, ni la fracción del pico de FLOPS de la lección de FLOPs.
Ejemplo. Un pod de C01 con 100 requests por segundo, 6 ms de CPU cada una y un núcleo: 100 × 0.006 ÷ 1 = 0.6. Una T4 con 250 fotos por segundo contra 1,200 de máximo: ≈ 21 %.
Teoría de colasqueueing theoryLa parte de las matemáticas que calcula cuánto se espera en una fila a partir de cómo llegan las requests, cuánto tarda cada una y cuántos servidores hay.Más +
Su modelo más simple, M/M/1, supone llegadas al azar (Poisson), tiempos de servicio al azar, un servidor y una fila en orden de llegada. Da W = S ÷ (1 − ρ) para el tiempo promedio en el sistema y, en ese modelo, un p99 de ≈ 4.6 × W. M/M/c tiene c servidores con una fila compartida (la fórmula de Erlang C), y M/D/1, un servicio constante, que espera la mitad. Los sistemas reales no cumplen los supuestos, pero conservan la forma: plana y después un palo de hockey. Sirve para argumentar; para dimensionar se mide con una prueba de carga.
Ejemplo. Con S = 6 ms: W = 12 ms al 50 %, 30 al 80 %, 60 al 90 %, 120 al 95 % y 600 al 99 %. De 50 a 90 %, la espera en la fila se multiplica por 9.
SaturaciónsaturationCuando un recurso ya no tiene tiempo libre: su utilización llega cerca del 100 % y lo que sigue llegando se va a la fila. La fila crece y el throughput deja de subir.Más +
Es una de las cuatro señales doradas (golden signals) que se vigilan en cada parte de un sistema, con la latencia, el tráfico y los errores; la lista viene del libro de SRE de Google. Se mide con una señal del propio recurso que se llena: el tiempo en la fila, las requests en espera, la CPU de un pod que calcula. La utilización de GPU no sirve: se queda en 100 % igual con 2 que con 200 en la fila. Cuando un recurso se satura, el cuello de botella es él.
Ejemplo. Una T4 de C01 a la que le ofrecen 1,300 fotos por segundo procesa ≈ 1,210: el throughput se aplanó, la fila llegó a su tope de 100 ms y ≈ 7 % se rechaza.
Cuello de botellabottleneckEl recurso que se satura primero en una cadena. Fija el throughput máximo de todo el sistema, y su fila es la que domina la latencia.Más +
Una request pasa por varios recursos en fila (red, CPU, GPU), y el throughput de la cadena es el del más angosto. Mejorar los demás no cambia nada. Se encuentra comparando la utilización de cada recurso o, en producción, mirando qué fila crece: el histograma del preproceso, el tiempo en la fila de Triton, la red. A menudo no es la pieza más cara.
Analogía. Una cadena de tubos: el agua que sale por segundo la decide el más angosto. Se rompe en que un sistema real tiene filas antes de cada tubo, y el más angosto también es donde más se espera.
Ejemplo. C01 en el pico de diseño: la red de cada nodo del gateway va al ≈ 77 %, la CPU de cada pod al 60 % y cada T4 al ≈ 21 %. El cuello de botella es la red, no la GPU: por eso la mejora anotada es mandar el tensor en uint8.
CapacidadcapacityEl throughput más alto que un servidor sostiene cumpliendo su presupuesto de latencia. No es lo más que alcanza a procesar: pasado ese punto todavía procesa más, pero tarde.Más +
No se adivina: se mide con una prueba de carga en rampa, con el mismo hardware, la misma configuración y datos reales, y es el último escalón cuyo p99 cabe en el presupuesto del tramo. Con ella se cuentan máquinas: ⌈pico ÷ (capacidad × utilización objetivo)⌉ + repuesto. Lo que se deja libre a propósito es el headroom.
Ejemplo. Una T4 de C01 con la ResNet-50 en TensorRT: ≈ 1,200 fotos por segundo con el p99 dentro de los ≈ 30 ms de Triton. A 1,300 todavía procesa ≈ 1,210, pero con un p99 de ≈ 130 ms.
Headroomheadroom · margenLa distancia entre la capacidad medida de un servidor y la carga que planeas darle: lo que dejas libre a propósito. Como regla práctica, un servicio online se planea a ≈ 50–70 % de su capacidad.Más +
No es desperdicio: es protección. Paga cuatro cosas a la vez: que el p99 se quede en la parte plana de la curva, que la caída de una réplica o de una zona se reparta en las demás, los minutos que tarda el autoscaler en traer máquinas nuevas y el error de la medición. Un job offline, que nadie espera, puede trabajar cerca del 100 %. Su precio es pagar capacidad para el pico las 24 horas.
Ejemplo. C01 planea cada T4 al 50 %: 600 de sus ≈ 1,200 fotos por segundo. Con dos, en el pico cada una va al ≈ 21 %; si cae una, la otra va al ≈ 42 %.
Prueba de cargaload testUn programa que le manda tráfico sintético a un servicio, cada vez más, y anota el throughput y los percentiles de latencia en cada escalón. Así se mide la capacidad.Más +
La básica es la rampa: subir por escalones hasta pasar el codo, donde el throughput se aplana y la latencia se dispara. Otras: el soak (el pico por horas, para fugas y relojes que bajan), el spike (de golpe 3 a 5 veces la carga) y el failover (apagar una máquina a media prueba). Para que prediga producción: datos reales, el mismo hardware y la misma configuración, descartar el arranque y un generador que no sea el que se satura. Para medir latencia se usa carga abierta. Herramientas: perf_analyzer, k6, Locust, wrk2.
Ejemplo. Una rampa contra una T4 de C01: a 600 fotos por segundo, p99 de 13 ms; a 1,200, de 28 ms (el codo); a 1,300, el throughput se queda en ≈ 1,210 y el p99 sube a ≈ 130 ms.
Carga abiertaopen-loop load · open modelUna manera de generar tráfico en una prueba: las requests llegan a una tasa fija, pase lo que pase con las respuestas. Se parece a los usuarios reales.Más +
Su contrario es la carga cerrada (closed-loop): N usuarios virtuales que mandan su siguiente request cuando reciben la respuesta de la anterior. La cerrada se frena junto con el servidor y mide menos requests justo en los peores momentos, así que su p99 sale mejor que la realidad: es la coordinated omission. Para medir latencia a una tasa dada se usa carga abierta (k6 con tasa fija, wrk2, perf_analyzer por tasa); para encontrar el máximo, sirve la cerrada.
Ejemplo. Un servidor de 10 ms con una pausa de 1 s, en una prueba de 10 s a 1,000 requests por segundo: la carga cerrada reporta un p99 de 10 ms; la abierta, de ≈ 0.9 s.
Backpressurebackpressure · contrapresiónLa señal que viaja hacia atrás y le dice a quien manda trabajo «no puedo más, frénate». Así el que produce va al ritmo del que consume, en vez de llenarle una fila sin fondo.Más +
Puede ser un error rápido como un 503 con el header Retry-After, una fila que no deja meter nada hasta que haya lugar (cola acotada) o una conexión que deja de aceptar bytes. Sin ella, la sobrecarga no se ve: todo se acepta, la fila crece y todas las respuestas llegan tarde. Rechazar a propósito lo que no cabe es load shedding.
Ejemplo. Cuando un pod del gateway de C01 tiene 16 requests en vuelo, la 17.ª recibe un 503 en microsegundos y el backend le muestra a Ana el selector de categorías.
Cola acotadabounded queueUna fila con un límite, por cuántas caben o por cuánto pueden esperar. Si está llena, la request nueva se rechaza en el momento, en vez de esperar para nada.Más +
Una fila sin tope nunca falla: solo se atrasa en silencio cuando llega más de lo que el servidor procesa, hasta que todo vence su timeout y el goodput cae a cero. El tope va donde se rompe el presupuesto de latencia, no donde se acaba la memoria. Un tope por número también acota el tiempo, por la Ley de Little: lugares ÷ throughput. Es la forma más simple de backpressure.
Ejemplo. C01 pone topes en cada salto: 16 requests en vuelo por pod del gateway, 2 decodes a la vez con 50 ms de espera máxima, y la fila de Triton que rechaza lo que ya esperó 100 ms.
Batching dinámicodynamic batchingJuntar en el servidor las requests que llegan casi al mismo tiempo para correrlas en un solo batch, un grupo que la GPU procesa de una vez. El batch sale cuando se llena o cuando vence una espera máxima, lo que pase primero.Más +
Una GPU corre un batch de 8 en mucho menos que 8 veces lo que tarda uno, porque parte del costo es fijo por ejecución. El batching dinámico cambia throughput por latencia: cada request espera la ventana y el batch tarda más. El tamaño real depende de lo que llega: con poco tráfico casi no junta nada. Solo baja la latencia si el sistema estaba saturado y el batching, al subir la capacidad, vació la fila. En los LLMs, la versión es el batching continuo.
Ejemplo. Triton en C01 espera como máximo 2 ms y junta hasta 32. A 250 requests por segundo por GPU llegan 0.5 en la ventana: batches de 1 a 3. Llenar uno de 32 tardaría 128 ms.
Goodputgoodput · throughput útilEl throughput que sí cuenta: las requests por segundo que terminan bien y dentro de su promesa de latencia (el SLO). En una prueba de carga, la carga máxima a la que todavía se cumple esa promesa.Más +
El throughput cuenta todo lo que el servidor termina, aunque llegue tarde o con error. El goodput separa un servidor ocupado de uno que sirve: con una fila sin tope, el throughput se queda en el máximo y el goodput cae a cero. En los LLMs es el número con el que se compra la flota; el artículo de DistServe (OSDI 2024) lo define por GPU: la tasa máxima de requests que cumple a la vez el TTFT y el TPOT, en la fracción pedida (por ejemplo, el 90 % de las requests), medida con tu mezcla real de largos.
Ejemplo. Una réplica de C10 llega a ≈ 6.4 requests por segundo con 96 a la vez, pero su TTFT p95 es 2.4 s. Su goodput, la última carga que cumple TTFT ≤ 1 s y TPOT ≤ 50 ms, es ≈ 6 requests por segundo; se planea al 70 %, ≈ 4.2.
Chequeo
EsencialCheck
Intenta responder en tu cabeza antes de abrir cada respuesta.
1¿En qué tres cosas se va el tiempo de una request en cada salto, y cuál crece cerca del pico?Ver respuesta
Viajar (la red), esperar (la fila) y trabajar (el tiempo de servicio). Cerca del pico crece la espera. La utilización, ρ = λ × S ÷ c, dice cuánto: un pod con 100 requests por segundo de 6 ms y un núcleo está a 0.6.
2¿Por qué la espera explota cerca del 100 % aunque todavía quede tiempo libre?Ver respuesta
Porque las llegadas vienen en ráfagas y cada ráfaga deja una fila que solo se deshace con tiempo libre. En M/M/1, W = S ÷ (1 − ρ): de 50 a 90 % la espera en la fila se multiplica por 9.
3¿Qué es el cuello de botella de C01 en el pico de diseño, y qué no lo es?Ver respuesta
La red del gateway (≈ 77 %) y luego su CPU (60 %). La GPU, la pieza más cara, va al ≈ 21 %.
4¿Para qué sirve el headroom?Ver respuesta
Para que el p99 no salga de la parte plana de la curva, para aguantar la caída de una réplica o zona, para cubrir los minutos que tarda el autoscaler y para el error de la medición.
5¿Qué hace una cola acotada que no hace una fila sin tope?Ver respuesta
Rechaza rápido lo que no va a llegar a tiempo. Así las demás se contestan a tiempo. Una fila sin tope atiende a todas y todas llegan tarde: throughput al máximo y goodput en cero.
6¿Por qué una prueba de carga cerrada reporta un p99 mejor que la realidad?Ver respuesta
Porque cuando el servidor se frena, los usuarios virtuales esperan y mandan menos requests justo en el peor momento. Es la coordinated omission. Para medir latencia, carga abierta a una tasa fija.