01 · Punto de partida
El banco corre en un data center
Tu API recibe 200 req/s que tardan 50 ms: hay 10 requests adentro (Little) y con 12 workers vas al 83%. El p95 pide 100 ms de cola; un downstream lento mete gigantes; un reintento agresivo multiplica λ justo en el pico. Misma teoría, otras unidades: rps, ms y workers.
Aplicarla acá es medir por endpoint, dimensionar pools con Little+Erlang, balancear al menos cargado y poner timeout con backoff antes que escalar a ciegas.
- ¿Cuántos workers/hilos/conexiones para mi rps y latencia?
- ¿Media o p95: qué SLA firmo?
- ¿Cómo balanceo y cómo escalo (vertical vs horizontal)?
- ¿Qué hacen timeout y reintentos con λ?
02 · Definición
Throughput, latencia y concurrencia
Throughput
req/s que llegan (rps) por endpoint y ventana. Con picos 10×: se dimensiona por pico +aiman? por pico.
Latencia
W = Wq (cola) + S (cómputo/DB). El SLA se firma en p95/p99, no en media.
Concurrencia
L = λW requests adentro = workers ocupados. El número que dimensiona pools e hilos.
| Servicio | λ × S | c y ρ | Lectura |
|---|---|---|---|
| API 200 r/s × 50 ms | a = 10 | 12 → 0.83 | Wq ≈ 11 ms, p95 de cola ≈ 100 ms |
| DB 500 r/s × 20 ms | a = 10 | pool 16 → 0.63 | Conexiones = L + margen |
| Worker 10 r/s × 2 s | a = 20 | 24 → 0.83 | Cola de mensajes con 24 consumers |
03 · El corazón: la receta backend
Medir, dimensionar, balancear, domar, escalar
- 1Medir por endpoint y ventana.
rps, p50/p95/p99 de S y W, por minuto. La media esconde el endpoint gigante que traba el pool.
- 2Dimensionar pools con Little+Erlang.
Conexiones e hilos = λW + margen; c con P(Wq ≤ t) del SLA. El pool chico es K finita con fuga (Tema 27).
- 3Balancear al menos cargado.
Least-connections / primero libre = fila única (pooling, Tema 29). Round-robin a ciegas parte el pooling.
- 4Timeout + backoff con jitter.
Timeout acota S (corta gigantes, baja Cs²); reintento suma λextra = λ·p/(1−p): con p = 20% es +25% de carga fantasma.
- 5Autoscaling por ρ/latencia.
Escalar al acercarse al codo (Tema 44), no en la pared; con headroom N+1. Escalar por CPU sola ignora la cola.
- Tail latency
- El p99 manda en cascada: con 10 llamadas en serie, P(alguna lenta) ≈ 10%. Doma gigantes (timeout) antes que promedios.
- Retry storm
- Timeouts sincronizados reintentan a la vez: λ se multiplica en el peor momento (ρ 0.83 → 1.25). Backoff + jitter la desarman.
- Vertical vs horizontal
- Subir μ (más CPU/rápido) baja W directo y Wq doble (Tema 36); sumar c (réplicas) aplana la curva. Se combinan por costo.
- Health-checks
- Sacar nodos enfermos baja c justo en el pico: el autoscaling debe reponer antes de declarar victoria.
Escalar workers (c)
Aplana
Más manos absorben rachas (pooling). Cuesta réplicas; rinde con retornos decrecientes.
Recortar latencia (S)
Acelera doble
Caché, índice, timeout: baja W directo y derrite Wq (doble dividendo). Suele rendir antes que el puesto.
04 · Ejemplos
Cuatro backends con número
- 1API 200 r/s × 50 ms, 12 workers.
a = 10, ρ = 0.83: Wq ≈ 11 ms, P(cola ≤ 100 ms) ≈ 99%. Headroom justo.
- 2Pool DB 500 r/s × 20 ms.
L = 10 conexiones ocupadas: pool 16 (ρ = 0.63) con margen para picos ×1.5.
- 3Consumers 10 r/s × 2 s.
a = 20: 24 consumers (ρ = 0.83). El lag de la cola es Lq visible.
- 4Retry storm del downstream.
p = 30% timeouts: λef ≈ 1.39×λ (0.83 → 1.15): de tenso a volteado. Backoff ya.
05 · Representación en Python
Pool por Little y carga fantasma por reintentos
Unidades rps y segundos: los mismos números del curso con otros nombres.
Python en tu navegador. El primero dimensiona el pool; el segundo multiplica λ por reintentos. Cambiá p y mirá la tormenta.
# Pool por Little + margen: lam rps, S segundos
def pool(lam, S, margen=1.5):
return {"ocupados": lam*S, "pool": round(lam*S*margen)}
print(pool(200, 0.05)) # 10 ocupados -> pool 15
print(pool(500, 0.02)) # 10 ocupados -> pool 15
# Carga fantasma: p timeout, hasta 3 intentos
def carga_fantasma(p, k=3):
return sum(p**i for i in range(k))
for p in [0.05, 0.2, 0.3]:
print(f"p={p:.0%} -> x{carga_fantasma(p):.2f} (rho 0.83 -> {0.83*carga_fantasma(p):.2f})")
06 · Exploración
Laboratorio: planificar capacidad + tormenta
Elegí λ (rps), S (ms) y workers c. La curva es Wq(λ) exacta (Erlang) con tu c; el punto es tu operación; la recta es el SLA de cola (100 ms). El interruptor de tormenta multiplica λ ×1.5 (reintentos): mirá cómo el punto se muda a la pared.
Capacidad y tormenta de reintentos
Wq(λ) Erlang · SLA cola ≤100 ms
Activá la tormenta para voltearlo.
Curva lima = Wq(λ); punto = tu λ (naranja con tormenta ×1.5); recta = SLA 100 ms. La tormenta corre el punto, no la curva.
Preguntas para explorar
- Default (200 rps, 50 ms, 12): ¿ρ? ¿L? ¿Wq y P(≤100 ms)?
- Con tormenta (λ × 1.5 = 300): ¿ρ? ¿Qué pasa con Wq?
- Subí a c=16 sin tormenta: ¿ρ y Wq? ¿Y con tormenta?
Ver respuestas sugeridas
- ρ ≈ 0.83, L ≈ 12, Wq ≈ 11 ms, P ≈ 99%: tenso pero dentro del SLA.
- ρ ≈ 1.25: avalancha (sin punto). La tormenta voltea lo que andaba.
- Sin tormenta ρ ≈ 0.63, Wq ≈ 0.5 ms; con tormenta ρ ≈ 0.94, Wq ≈ 37 ms (P ≈ 90%): el +4 absorbe la tormenta al borde del SLA.
07 · Comprensión
Confusiones frecuentes
«La latencia media alcanza para el SLA»
El SLA se firma en p95/p99: media 20 ms con p99 800 ms rompe contratos. Medir percentiles por endpoint.
«Hilos = rps pico»
Hilos = rps × latencia (Little): 200 r/s × 0.05 s = 10, no 200. Sin ×W se sobredimensiona ×20.
«Reintentar es gratis»
Cada reintento es λ extra con el sistema ya lento: p = 30% suma +43% de carga. Backoff + jitter o nada.
«Escalo por CPU»
CPU 60% con Wq disparada es cola, no cómputo: escalar por latencia/ρ (o por L = λW), no por CPU sola.
08 · Práctica guiada
Ejercicios con Python
Ejercicio 1: pool DB
λ=500 r/s, S=20 ms, margen 1.5. ¿Ocupadas y pool?
# 10 ocupadas -> pool 15 (ρ=0.63 con 16)
print(pool(500, 0.02))
Ver solución razonada
L = 10, pool 15–16: el margen absorbe picos ×1.5 sin encolar conexiones.
Ejercicio 2: tormenta numérica
ρ=0.83 con p=30% y 3 intentos. ¿ρ efectiva?
# x1.39 -> rho 1.15: voltea (el lab muestra 1.25 con p≈33%)
print(f"{0.83*sum(0.3**i for i in range(3)):.2f}")
Ver solución
≈ 1.15: la tormenta convierte lo tenso en avalancha. De ahí timeout + backoff primero.
Ejercicio 3: vertical vs horizontal
S=50→25 ms (caché) vs c=12→24 con λ=200. ¿ρ en cada caso?
Ver una posible respuesta
# caché: a=5, rho=0.42; workers: rho=0.42 también. Empate en rho...
print("...pero caché además baja W a la mitad: doble dividendo")
Empate en ρ (0.42), gana caché en W: recortar S paga dos veces (Tema 36).
09 · Síntesis
Ideas para recordar
- L = λW dimensiona hilos/pools; ρ = λS/c el margen; p95 el SLA.
- Balanceador = fila única; timeout acota S; retry suma λ (×1+p+…).
- En Python: pool() + carga_fantasma() antes de pedir máquinas.
- Autoscaling por latencia/ρ al codo, con N+1.
- Tail latency manda en cascada: domar gigantes primero.
En el próximo tema bajamos a planta: producción y logística.