Simulación de Eventos Discretos · Tema 32

Modelo M/M/c

Múltiples servidores con tráfico markoviano — Erlang C para predecir espera y dimensionar sin adivinar.

01 · Punto de partida

El hermano mayor del M/M/1

Un call center con 8 agentes no es “ocho M/M/1”: es un M/M/c con fila única y pooling. Con la misma oferta a = λ/μ = 6, ocho separados colapsan (ρ = 0.75 cada uno con su racha) y ocho unidos respiran. La diferencia la escribe Erlang C: la probabilidad de esperar.

Este modelo es la calculadora de dimensionamiento: dado λ y μ, dice el c mínimo estable y el c que cumple el SLA (“80% en 20 s”).

  • ¿Qué supuestos definen al M/M/c y qué es la oferta a?
  • ¿Cómo se calcula C(c,a) y qué significa?
  • ¿Cómo paso de C a Lq, Wq, W y L?
  • ¿Qué c elijo para un SLA dado?

02 · Definición

Oferta, Erlang C y medidas

a

Oferta a

a = λ/μ (erlangs): servidores ocupados equivalentes. c mínimo estable = ⌊a⌋ + 1.

C

Erlang C

P(esperar) = P(N ≥ c). Cae fuerte con cada servidor extra hasta aplanarse.

Lq

Medidas

Lq = C·ρ/(1−ρ), Wq = Lq/λ, W = Wq + 1/μ, L = λW. Todo cuelga de C.

M/M/c en números: λ = 0.9/min, μ = 0.5 (a = 1.8).
cρC(c,a) P(espera)Wq
20.90≈ 0.85≈ 8.5 min (rescate)
30.60≈ 0.35≈ 0.9 min (pulido)
40.45≈ 0.12≈ 0.2 min (lujo)

03 · El corazón: muerte por tramos

Nacer a λ, morir a min(n,c)μ

nace λ→muere nμ si n≤c→muere cμ si n>c

Con n < c hay n servidores activos (muerte nμ); con n ≥ c todos trabajan (muerte cμ fija) y la cola crece. El balance por tramos da P0, de P0 sale C(c,a) y de C salen todas las medias. Es el mismo nacimiento-muerte del M/M/1 con techo de servicio.

  1. 1
    Calcular a y ρ.

    a = λ/μ; ρ = a/c < 1 o no hay régimen. c mínimo = ⌊a⌋ + 1.

  2. 2
    Erlang C.

    C = [ac/(c!(1−ρ))] / [Σk<c ak/k! + ac/(c!(1−ρ))].

  3. 3
    Medias.

    Lq = Cρ/(1−ρ); Wq = Lq/λ; W = Wq + 1/μ; L = Lq + a.

  4. 4
    Dimensionar por SLA.

    P(Wq > t) = C·e−(cμ−λ)t: subir c hasta cumplir “X% en t segundos”.

P0
P(sistema vacío) = 1/[Σk<c ak/k! + ac/(c!(1−ρ))]. El denominador de Erlang C.
Economías de escala
A igual ρ, más c ⇒ menos espera: un M/M/8 con ρ=0.9 espera menos que un M/M/2 con ρ=0.9. Lo grande absorbe rachas.
SLA “X% en t”
P(Wq ≤ t) = 1 − C·e−(cμ−λ)t. La fórmula con la que se venden call centers (80/20).
M/M/1 como caso
Con c = 1, C(1,a) = ρ y Wq = ρ/(μ−λ): se recupera el Tema 31. Un solo modelo, dos capítulos.

M/M/1

Base exacta

Sin pooling: cada racha la sufre uno. Sirve para validar el motor antes de paralelizar.

M/M/c y M/M/c/c

Pooling y pérdida

M/M/c espera unida (Erlang C); M/M/c/c pierde sin espera (Erlang B). Misma a, distinto destino del pico.

04 · Ejemplos

Tres M/M/c con placa

  1. 1
    Call center 8 agentes.

    λ = 6/min, S = 1 min (a = 6, ρ = 0.75): C ≈ 0.4, Wq ≈ 0.2 min. SLA 80/20 holgado.

  2. 2
    Banco 3 cajas.

    λ = 0.9/min, S = 2 (a = 1.8, ρ = 0.6): Wq ≈ 0.9 min. La tercera caja humaniza.

  3. 3
    Web 4 workers.

    λ = 100/s, S = 0.03 s (a = 3, ρ = 0.75): Wq ≈ 0.02 s. Pooling perfecto a microescala.

05 · Representación en Python

mmc() que dimensiona y simula

Erlang C con factoriales para predecir; libre_en[c] para validar con fila única.

Python en tu navegador. El primero calcula C y todo M/M/c; el segundo simula y compara. Barré c y buscá tu SLA.

# Todo M/M/c en una función (Erlang C)
import math
def mmc(lam, mu, c):
    a = lam / mu
    rho = a / c
    assert rho < 1, "sin régimen"
    s = sum(a**k / math.factorial(k) for k in range(c))
    C = (a**c / (math.factorial(c)*(1-rho))) / (s + a**c/(math.factorial(c)*(1-rho)))
    Wq = C / (c*mu - lam)
    return {"a": a, "rho": rho, "C": C, "Wq": Wq, "W": Wq + 1/mu}

for c in [2, 3, 4]:
    print(c, {k: round(v, 2) for k, v in mmc(0.9, 0.5, c).items()})
# Validar M/M/c simulando fila única (T largo)
import random
random.seed(32)
lam, mu, c, T = 0.9, 0.5, 3, 300.0
t, libres, wqs = 0.0, [0.0]*c, []
while t < T:
    t += random.expovariate(lam)
    k = min(range(c), key=lambda j: libres[j])
    ini = max(t, libres[k])
    libres[k] = ini + random.expovariate(mu)
    wqs.append(ini - t)
print(f"Wq sim={sum(wqs)/len(wqs):.2f} vs {mmc(lam, mu, c)['Wq']:.2f}")

06 · Exploración

Laboratorio: la curva de Erlang C

Fijamos S = 2 (μ = 0.5) y elegís la oferta a (vía λ) y c. Las barras muestran la Wq teórica M/M/c para c = 1…5 (capada donde no hay régimen); la barra marcada es tu c y el punto teal es tu simulación (T = 150, réplica). Subí a y mirá cómo la curva exige más c.

EXPERIMENTO 32

Erlang C manda, la sim obedece

Wq(c) = C(c,a)/(cμ−λ) · S=2

Los resultados numéricos aparecen debajo.
ρ = a/c0
C(c,a) P(espera)0
Wq sim / teórica0
c mínimo estable0

Mové la oferta para exigir más c.

Barras lima = Wq teórica por c (roja donde ρ ≥ 1); borde grueso = tu c; punto teal = Wq simulada en tu c. S = 2 fijo.

Preguntas para explorar

  1. Con a=1.8: ¿Wq con c=2 vs c=3? ¿C en cada uno? ¿Qué compró el tercero?
  2. Subí a=2.4: ¿c mínimo? ¿Wq con ese c vs uno más?
  3. ¿Por qué la sim con c al límite (ρ≈0.95) tiembla entre réplicas?
Ver respuestas sugeridas
  1. c=2: C≈0.85, Wq≈8.5; c=3: C≈0.35, Wq≈0.9. El tercero compró −90% de espera y −60% de probabilidad de esperar.
  2. a=2.4 ⇒ c mínimo 3 (ρ=0.8); con 4 la Wq se parte (~1/3). Dimensionar es elegir en esa curva.
  3. Al límite el régimen tarda y la varianza de Wq explota: T=150 no alcanza; se alarga T o se promedia réplicas (Temas 41–43).

07 · Comprensión

Confusiones frecuentes

«C(c,a) es la utilización»

No: ρ = a/c es utilización; C es P(esperar), siempre mayor que ρ y mucho mayor al límite. Con ρ=0.9 y c=2, C≈0.95.

«M/M/3 es tres M/M/1»

Tres M/M/1 con λ/3 esperan más que un M/M/3 con λ (pooling). El código con una sola barra es otra cosa.

«P0 = 1 − ρ también con c > 1»

Solo con c = 1. Con c > 1, P0 lleva la suma de Erlang: 1/[Σak/k! + ac/(c!(1−ρ))].

«Erlang C vale con prioridades»

No: supone FIFO sin clases. Con prioridades la espera por clase se calcula aparte (Tema 39) o se simula.

08 · Práctica guiada

Ejercicios con Python

Ejercicio 1: mínimo estable

λ=6/min, S=1 min. ¿a, c mínimo y ρ con ese c?

lam, S = 6.0, 1.0
a = lam * S
c = int(a) + 1
print(f"a={a} c_min={c} rho={a/c:.2f}")  # a=6 c=7 rho=0.86
Ver solución razonada

a = 6 erlangs ⇒ c = 7 mínimo (ρ≈0.86, tenso); c = 8 lo deja en 0.75 (SLA viable).

Ejercicio 2: leer la curva

Con λ=0.9, μ=0.5: ¿Wq con c=2, 3, 4? Usá tu mmc().

# Esperado: ~8.5, ~0.9, ~0.2 (ver tabla de la sección 02)
print("El salto 2->3 vale 10x; el 3->4 vale 4x: retornos decrecientes")
Ver solución

8.5 → 0.9 → 0.2: el rescate lo hace el 3º; el 4º es lujo salvo SLA estricto.

Ejercicio 3: SLA 80/20

λ=6/min, S=1, c=8: ¿P(Wq ≤ 1/3 min)? Fórmula P(Wq>t) = C·e−(cμ−λ)t.

Ver una posible respuesta
import math
# con mmc(6, 1, 8): C≈0.36, cμ−λ=2
C, resto, t = 0.36, 2.0, 1/3
print(f"P(Wq<={t:.2f})={1 - C*math.exp(-resto*t):.0%}")  # ~82%

~82%: cumple el 80/20 por poco. Con c=7 caería bajo el SLA.

09 · Síntesis

Ideas para recordar

  • M/M/c = Poisson + Exp + c + ∞ + FIFO; a = λ/μ, ρ = a/c.
  • Erlang C = P(esperar); de ella cuelgan Lq, Wq, W, L y el SLA.
  • c mínimo = ⌊a⌋+1 estabiliza; el siguiente humaniza; el resto decora.
  • En Python: mmc() con factoriales + libre_en[c] para validar.
  • A igual ρ, más c espera menos: lo grande absorbe rachas.

En el próximo tema miraremos el otro extremo del mostrador: la utilización del servidor.