English · Español
03 — Ley de Little, profundidad de cola y dimensionado de capacidad¶
La ley de Little es una ecuación de tres letras que ata throughput, latencia (latency) y profundidad de cola. Sin ella, dimensionar un servicio es adivinar. Con ella, es aritmética.
La ley de Little¶
Para cualquier sistema de colas en régimen estacionario con tasa de llegada \(\lambda\) (requests por segundo) y tiempo medio en el sistema \(W\) (segundos por request):
donde \(L\) es el número medio de requests en el sistema. La derivación es puramente combinatoria — sin supuestos sobre la distribución de llegadas o tiempos de servicio. Mientras el sistema sea estable (tasa de entrada ≤ capacidad), esto se cumple.
Por qué importa para el servicio de inferencia¶
Tienes tres mandos:
- Tasa de llegada \(\lambda\): cuántas requests/s entran. La marca tu tráfico.
- Tiempo en el sistema \(W\): latencia de extremo a extremo por request (espera en cola + servicio). Lo que los usuarios sienten.
- Concurrencia \(L\): cuántas requests están "vivas" dentro del sistema.
La ley de Little dice que solo puedes elegir dos. La tercera se deriva.
Ejemplo. El tutor tarda \(W_\text{service} = 200\) ms por request de media. Quieres \(\lambda = 50\) req/s. Por la ley de Little (con \(W = W_\text{service}\) en el mejor caso, ignorando la espera en cola):
Necesitas 10 requests in flight para sostener 50 req/s. Si el MAX_INFLIGHT de tu servidor es 4, tu régimen estacionario no puede alcanzar 50 req/s — las requests se acumulan en la cola, \(W\) crece y \(L\) crece hasta que peta por OOM o el load balancer hace timeout.
La cota: \(L \le L_\text{max}\)¶
El sistema tiene una capacidad dura \(L_\text{max}\) — para nuestro scheduler, es el parámetro MAX_INFLIGHT. De la ley de Little:
Si \(W = 200\) ms y \(L_\text{max} = 8\), entonces \(\lambda_\text{max} = 40\) req/s. Cualquier cosa por encima y la cola crece sin cota.
Este es el techo de capacidad. Píntalo: \(\lambda\) vs \(W\) para \(L_\text{max}\) fijo — verás un codo en \(\lambda_\text{max}\) donde la latencia se vuelve vertical.
Qué sale mal cuando excedes la capacidad¶
Si \(\lambda > \lambda_\text{max}\):
- La cola de pendientes crece linealmente con el tiempo.
- La \(W\) de cada request = (su espera en cola) + (su tiempo de servicio). La espera en cola crece linealmente con la longitud de la cola.
- El uso de memoria crece linealmente con la longitud de la cola (cada request son unos pocos KB + su KV cache).
- Eventualmente: cascada de timeouts, OOM o el load balancer empieza a tirar tráfico.
Por esto los sistemas de producción implementan control de admisión — rechazar (con 503) o tirar requests cuando la cola es demasiado profunda. Mejor rechazar el 10 % de las requests que hacer que el 100 % de los usuarios esperen 30 segundos.
Para el lab 03: añade un parámetro MAX_QUEUE_DEPTH; si se excede, devuelve HTTP 503.
Una distinción sutil: media vs p95¶
La ley de Little usa medias. Pero a los usuarios les importan las colas. Si tu \(W\) media es 100 ms pero el p95 es 1500 ms, la experiencia es mala aunque el throughput parezca correcto.
El continuous batching ayuda específicamente al p95 porque deja que las requests rápidas salgan antes — no se quedan atascadas detrás de las lentas. El static batching mantiene la media aproximadamente igual pero destroza el p95.
Regla práctica operacional: Dimensiona \(L_\text{max}\) para tu tiempo de servicio p95, no para la media.
Backpressure: decirle al cliente que vaya más despacio¶
Cuando la cola está casi llena tienes tres opciones:
- Bloquear: Aceptar la request, hacer que el cliente espere en la cola. Malo para la memoria.
- Rechazar: Devolver HTTP 503 (Service Unavailable) con un header
Retry-After. Empuja el problema aguas arriba — al load balancer o a la lógica de retry del cliente. - Degradar: Devolver una respuesta más rápida y de menor calidad (p. ej., omitir la explicación, devolver solo la frase corregida). Específico del dominio.
Para la Fase 33: implementa la opción 2. Es el valor por defecto correcto.
Health checks¶
Dos endpoints, ambos obligatorios:
-
/healthz(liveness): "¿Está vivo el proceso?" — devuelve 200 siempre (o 503 si el proceso está fundamentalmente roto). El orquestador lo usa para decidir si reiniciar el proceso. -
/readyz(readiness): "¿Estás listo para aceptar tráfico?" — devuelve 200 solo si el modelo está cargado Y la profundidad de la cola está bajo un umbral. El load balancer lo usa para decidir si mandar tráfico.
Si /readyz devuelve 503, el load balancer enruta tráfico a otras réplicas. Esto es backpressure pasivo — mejor que el rechazo explícito porque es transparente para los clientes.
Una fórmula útil: profundidad de cola objetivo¶
Quieres elegir MAX_INFLIGHT tal que:
- Se cumpla el objetivo de throughput: MAX_INFLIGHT >= target_rps * mean_service_time
- Se respete el presupuesto de memoria: MAX_INFLIGHT * mem_per_request <= available_memory
- Se cumpla el objetivo de latencia de cola: elige MAX_INFLIGHT desde un load test, no desde una fórmula
El lab 03 hará un barrido MAX_INFLIGHT ∈ {1, 2, 4, 8, 16, 32} y elegirá el codo de la curva throughput-vs-latencia. Este es el trabajo de ingeniería que la ley de Little motiva pero no determina.
Una nota sobre unidades¶
Cuidado: \(W\) en la ley de Little es el tiempo en el sistema de extremo a extremo (espera en cola + tiempo de servicio). No solo el tiempo de forward del modelo. Si los confundes, sobreaprovisionarás.
Cuando la cola está vacía: \(W \approx W_\text{service}\). Cuando la cola tiene \(k\) requests por delante de ti: \(W \approx W_\text{service} \cdot (k + 1) / B\) (donde \(B\) es el batch size efectivo). El continuous batching hace que \(k\) se encoja rápido para requests cortas.
Lo que este archivo NO cubre¶
- Análisis de colas M/M/1 y M/M/c con distribuciones exponenciales. La forma "solo media" de la ley de Little es suficiente.
- Autoscaling — ¿cuándo añades más réplicas? Fase 34.
- Manejo de ráfagas — token bucket, leaky bucket. Fase 37.
Siguiente: ../lab/00-minimal-fastapi.md