Módulo 3: Métricas — latencia, throughput y errores
7. Promedio vs p95 sobre una distribución con cola
Descripción
Esta lección es el clímax del módulo. En la lección 2 diste el primer golpe a la trampa del promedio; en la 3 aprendiste a calcular percentiles; aquí ves, con toda la fuerza numérica que se puede reunir, cuánto miente el promedio sobre una distribución de latencias con cola —y por qué eso no es una curiosidad estadística, sino la razón por la que las SLO de latencia del mundo entero se escriben en percentiles y nunca en promedio—. Al terminar, no solo sabrás que "el promedio miente": vas a poder demostrarlo con un número que deja a cualquiera sin respuesta.
Ese número es este: sobre la distribución con cola de /quote_slow, el 88% de las peticiones fue más rápido que el promedio. Léelo otra vez. El promedio no es "el punto medio" de nada: es un valor que casi nueve de cada diez usuarios batieron, arrastrado hacia arriba por una minoría lenta. Un número que el 88% de la gente supera no describe a "el usuario típico"; describe a nadie. Esta lección desmenuza por qué pasa eso (la forma de una distribución de latencias, asimétrica, con cola a la derecha), qué percentil elegir según lo que te importe, y cómo se traduce todo esto en la práctica de escribir un objetivo de rendimiento. Es la lección que convierte "usa percentiles" de consejo en convicción.
Conexión con el módulo: aquí culminan las lecciones 2 (por qué percentiles) y 3 (cómo calcularlos), y se conecta con lo que viene: en el módulo 5 escribirás thresholds como p(95)<500 —umbrales sobre el percentil que aquí aprendes a elegir y defender—. La distinción entre p50, p95 y p99 que cierras aquí es exactamente la que necesitarás para decidir sobre qué percentil poner tu umbral. Reusamos el endpoint /quote_slow declarado en la lección 1 y el generador de siempre.
El sueldo promedio del bar
Hay una analogía clásica y perfecta para esto. Estás en un bar con nueve amigos; todos ganáis un sueldo parecido, digamos 30.000 al año. El sueldo promedio de la mesa es 30.000, y describe bien a todos. De pronto entra al bar una multimillonaria que gana 100 millones al año y se sienta con vosotros. Ahora el sueldo promedio de la mesa es de más de 9 millones. ¿Describe ese promedio a alguien de la mesa? A nadie: ni a los diez que ganáis 30.000, ni a ella que gana 100 millones. El promedio saltó a un lugar donde no hay nadie sentado, arrastrado por un solo valor extremo. Si un periodista escribiera "los clientes de este bar ganan de media 9 millones", diría algo técnicamente cierto y profundamente falso.
Las latencias con cola son ese bar. La inmensa mayoría de las peticiones son "sueldos normales" —rápidas, agrupadas—, y unas pocas de la cola son "la multimillonaria" —lentísimas, un pico de la base de datos, un lock, un garbage collector—. El promedio, que suma y divide, es incapaz de resistirse a esos pocos valores gigantes: los pesa igual que a todos los demás y se dispara. La mediana (p50), en cambio, es inmune: es el valor del que está justo en el medio, y la multimillonaria que entra al bar no cambia quién está en el medio de la fila. Por eso, para latencias, la mediana describe al típico y el promedio describe a un fantasma. Y por eso el p95 —el que está en el puesto 95 de 100— captura cuán mala es la cola sin dejarse arrastrar a tierra de nadie.
En una distribución con cola, el promedio no es el centro: es un valor que la mayoría bate, empujado por una minoría extrema. La mediana (p50) describe al usuario típico; el p95/p99 describen a los que sufren la cola. El promedio, solo, describe a nadie.
Ejemplo trabajado: el 88% batió al promedio
Vamos a demostrarlo con números medidos. El generador golpea /quote_slow con 2000 peticiones y 50 clientes, guarda las 2000 latencias, y calcula no solo los percentiles sino algo revelador: cuántas peticiones cayeron por debajo del promedio.
import json, statistics, time, urllib.request
from concurrent.futures import ThreadPoolExecutor
def one(url, payload):
s = time.perf_counter()
req = urllib.request.Request(url, data=payload,
headers={"Content-Type": "application/json"})
with urllib.request.urlopen(req, timeout=10) as r:
r.read()
return (time.perf_counter() - s) * 1000
url = f"http://127.0.0.1:{PORT}/quote_slow"
payload = json.dumps({"room": "Focus", "tier": "basic", "hours": 3}).encode()
with ThreadPoolExecutor(max_workers=50) as pool:
latencies = sorted(pool.map(lambda _: one(url, payload), range(2000)))
q = lambda p: statistics.quantiles(latencies, n=100, method="inclusive")[p - 1]
avg = statistics.fmean(latencies)
below_avg = sum(1 for x in latencies if x < avg)
print(f"promedio (avg) : {avg:7.2f} ms")
print(f"mediana (p50) : {q(50):7.2f} ms")
print(f"p95 : {q(95):7.2f} ms")
print(f"p99 : {q(99):7.2f} ms")
print(f"peticiones por DEBAJO del promedio: {below_avg} de {len(latencies)} "
f"({below_avg / len(latencies) * 100:.1f}%)")
Qué esperar. El promedio va a caer muy por encima de la mediana, y la gran mayoría de las peticiones va a estar por debajo de ese promedio —la firma inconfundible de una cola a la derecha—. Esta es la salida real:
promedio (avg) : 29.34 ms
mediana (p50) : 11.95 ms
p95 : 187.27 ms
p99 : 241.47 ms
peticiones por DEBAJO del promedio: 1764 de 2000 (88.2%)
Ahí está el golpe. El promedio dice 29.34 ms. La mediana dice 11.95 ms: el usuario típico vio la latencia a menos de la mitad del promedio. Y lo demoledor: 1764 de las 2000 peticiones (el 88.2%) fueron más rápidas que el promedio. Un número que el 88% supera no es "el centro"; es un valor inflado por el 5-10% de la cola (el p95 en 187 ms, el p99 en 241 ms) que arrastra la media hacia arriba. Si reportaras "la API tarda de media 29 ms", estarías dando un número que casi nadie experimentó como su realidad: los rápidos vieron ~12 ms, los lentos vieron ~190 ms, y "29" es la tierra de nadie entre ambos. El p50 (11.95) le habla al típico; el p95 (187) le habla al que sufre; el promedio no le habla a nadie.
La forma de una distribución de latencias
¿Por qué las latencias tienen siempre esta forma —agrupadas a la izquierda, con una cola larga a la derecha—? Porque hay un piso pero no un techo. Una petición no puede tardar menos que su tiempo de servicio mínimo (hay un límite físico: el cómputo, la red), así que las latencias se apelmazan contra ese piso por la izquierda. Pero por la derecha no hay tope: una petición puede tardar el doble, diez veces, cien veces más si le toca esperar un lock, un pico de la base de datos, un reintento. Esa asimetría —piso duro a la izquierda, cola abierta a la derecha— es universal en sistemas, y es exactamente la forma que hace mentir al promedio.
Visualízalo con las latencias de /quote_slow agrupadas en tramos:
tramo (ms) cuántas peticiones (aprox.)
0 – 15 ████████████████████████████ la gran mayoría (el grueso rápido)
15 – 50 ████████ algunas
50 – 120 ██ pocas
120 – 250 ███ la COLA (el ~10% lento que dispara el promedio)
El grueso vive pegado a la izquierda (0-15 ms); una minoría vive en la cola (120-250 ms). El promedio es el "centro de gravedad" de toda la masa, y esa cola de la derecha, aunque sea poca, tira del centro de gravedad hacia ella. La mediana y los percentiles no se dejan tirar: cuentan posiciones, no masa. Por eso capturan la forma real —"la mayoría aquí, unos pocos allá"— que el promedio aplasta en un solo número engañoso.
Qué percentil elegir: p50, p95 o p99
Si el promedio se descarta, ¿cuál percentil miras? Depende de la pregunta, y conviene tener el mapa:
| Percentil | A quién describe | Cuándo es tu métrica |
|---|---|---|
| p50 (mediana) | El usuario típico. | Para saber cómo va "la experiencia normal". Nunca solo: no dice nada de la cola. |
| p90 / p95 | Los usuarios lentos (1 de cada 10 / 1 de cada 20). | El estándar de una SLO de latencia. El p95 es el más común: "casi todos deben ver esto o mejor". |
| p99 | La cola extrema (1 de cada 100). | Cuando la cola importa mucho: sistemas de pago, salud, o donde un usuario lento es un usuario perdido. |
| p99.9 | La cola larguísima (1 de cada 1000). | Sistemas a escala enorme, donde 1 de cada 1000 son millones de peticiones al día. |
La lección práctica: se elige el percentil según cuánto te importa el usuario desafortunado. Un p95 dice "acepto que 1 de cada 20 la pase peor que mi umbral"; un p99, "solo 1 de cada 100". Cuanto más alto el percentil que vigilas, más te comprometes con la cola —y más caro suele ser cumplirlo, porque domar la cola es lo más difícil de un sistema—. Google, en su libro de SRE, insiste en este punto: mide la latencia de la cola (percentiles altos), porque el promedio y la mediana pueden verse sanos mientras una fracción de usuarios sufre latencias inaceptables que ese resumen esconde.
De la métrica a la SLO (adelanto del módulo 5)
Todo esto desemboca en cómo se escribe un objetivo de rendimiento, un SLO (Service Level Objective). Un SLO de latencia del mundo real nunca se escribe sobre el promedio; se escribe sobre un percentil:
"El 95% de las peticiones a /quote debe responder en menos de 200 ms."
-> p(95) < 200 ms
Fíjate en que ese enunciado es, casi literalmente, un threshold de k6 (http_req_duration: ['p(95)<200']), que es el módulo 5. Aquí solo lo adelantamos para cerrar el arco: la razón de que el módulo 5 ponga sus umbrales sobre p(95) y no sobre avg es exactamente lo que demostraste en esta lección —que el promedio esconde la cola y el percentil la revela—. Elegir el percentil de tu SLO (¿p95? ¿p99?) es elegir a qué fracción de usuarios desafortunados te comprometes; ponerle el número (¿200 ms? ¿500 ms?) es negociar entre lo que el usuario tolera y lo que el sistema puede dar. La métrica que aprendiste a calcular y leer aquí es la materia prima de esa decisión.
Errores comunes
Escribir una SLO sobre el promedio. Qué pasa: "la latencia media debe ser < 100 ms" suena a objetivo, pero es humo: un sistema puede cumplir un promedio de 100 ms mientras 1 de cada 20 usuarios espera 2 segundos (la cola inflaría el promedio menos de lo que crees si el grueso es rapidísimo). Por qué pasa: el promedio es el resumen por defecto y se cuela en los objetivos. Cómo detectarlo: si tu SLO menciona "media" o "promedio", está mal formulada. Cómo corregirlo: escribe la SLO sobre un percentil (p(95) < X), que sí acota la experiencia de la cola.
Reportar solo el p50 y creer que basta. Qué pasa: alguien pasa del promedio al p50 (bien) pero reporta solo el p50, sin p95, y declara la API sana —sin ver que el p95 es 10 veces el p50—. Por qué pasa: se aprende "usa la mediana" y se olvida que la mediana tampoco dice nada de la cola. Cómo detectarlo: si tu único número es el p50, estás describiendo al típico e ignorando al que sufre. Cómo corregirlo: reporta siempre al menos p50 y p95 (y p99 si la cola te importa). El p50 sin el p95 esconde la cola tanto como el promedio.
Elegir un percentil altísimo "por si acaso" y no poder cumplirlo. Qué pasa: alguien pone su SLO en p(99.9) < 50 ms pensando que "más estricto es mejor", y luego el equipo se quema persiguiendo una cola imposible. Por qué pasa: se confunde "estricto" con "correcto". Cómo detectarlo: si tu SLO exige una cola que ningún sistema realista da y te cuesta más de lo que el negocio necesita, sobre-especificaste. Cómo corregirlo: elige el percentil según lo que de verdad importa al usuario y al negocio. Para muchísimos servicios, un p95 sensato es el punto correcto; el p99/p99.9 se reserva para cuando la cola extrema tiene un costo real.
Ejercicios
Ejercicio 1 — El número que deja sin respuesta. En la corrida de /quote_slow, el promedio fue 29.34 ms y el 88.2% de las peticiones fue más rápido que él. (a) Explica en una frase, para alguien no técnico, por qué eso hace inútil el promedio. (b) ¿Qué número usarías en su lugar para describir al usuario típico? (c) ¿Y para describir al que peor la pasa?
Ver solución
- (a) "El promedio de 29 ms es un número que casi 9 de cada 10 usuarios superaron: no describe a 'el usuario normal', sino un punto inflado por la minoría lenta donde no está casi nadie."
- (b) La mediana (p50 = 11.95 ms): la mitad de los usuarios vio eso o mejor. Es el número honesto del típico.
- (c) El p95 (187.27 ms) —o el p99 (241.47 ms) para la cola extrema—: describe lo que sufrió el 5% (o el 1%) peor. Juntar p50 y p95 cuenta la historia completa; el promedio, ninguna.
Ejercicio 2 — La forma de la distribución. Para cada juego de números, di si la distribución tiene cola larga a la derecha y cómo lo sabes por la relación promedio/mediana. (a) avg = 29.34, p50 = 11.95. (b) avg = 100, p50 = 99. (c) avg = 8, p50 = 12.
Ver solución
- (a) Cola larga a la derecha. El promedio (29.34) es mucho mayor que la mediana (11.95): la cola de valores altos tiró del promedio hacia arriba. Es la firma de
/quote_slow. - (b) Sin cola (compacta). avg ≈ p50 (100 ≈ 99): la distribución es simétrica y plana. El promedio describe bien. (Lenta, pero consistente.)
- (c) Imposible / dato sospechoso. El promedio (8) no puede ser menor que la mediana (12) si la cola es a la derecha; eso indicaría una cola a la izquierda, rarísima en latencias (implicaría muchos valores muy bajos y un piso alto), o un error en los datos. En latencias reales, avg ≥ p50 casi siempre.
La regla mecánica: avg > p50 → cola a la derecha (lo normal en latencias); avg ≈ p50 → compacta; avg < p50 → sospecha de los datos.
Ejercicio 3 — Elige el percentil de la SLO. Para cada servicio, propón un percentil (p50, p95, p99, p99.9) para su SLO de latencia y justifícalo en una frase. (a) Un blog de recetas. (b) Una pasarela de pagos. (c) La API de /quote de Reservo, con tráfico moderado. (d) Un servicio de un banco global con millones de peticiones diarias.
Ver solución
- (a) Blog de recetas → p95. Importa que la experiencia general sea buena, pero una página de recetas lenta de vez en cuando no es un desastre; el p95 acota al 5% peor sin obsesionarse con la cola extrema.
- (b) Pasarela de pagos → p99 (o más). Aquí un usuario lento puede ser una venta perdida o un pago fallido; conviene comprometerse con la cola (1 de cada 100), no solo con el 5%.
- (c)
/quotede Reservo con tráfico moderado → p95. Es el estándar sensato para una API típica: acota la experiencia del 95% sin exigir una cola cara que el negocio no necesita. - (d) Banco global, millones de peticiones → p99.9. Con ese volumen, "1 de cada 1000" son miles de usuarios al día; la cola larguísima tiene un costo real y hay que vigilarla.
No hay una respuesta única; la clave es que el percentil que eliges crece con cuánto duele un usuario lento. Cuanto más caro el usuario desafortunado, más alto el percentil que vigilas.
Resumen y siguiente paso
En esta lección viste, con toda su fuerza, por qué la latencia se mide en percentiles y no en promedio. El número que lo resume: sobre la distribución con cola de /quote_slow, el 88.2% de las peticiones fue más rápido que el promedio (29.34 ms) —un valor que casi nueve de cada diez usuarios batieron no describe a nadie—, mientras la mediana (11.95 ms) describía al típico y el p95 (187.27 ms) al que sufría. Entendiste por qué pasa: las latencias tienen piso pero no techo, así que su distribución es asimétrica, con una cola a la derecha que arrastra el promedio pero no los percentiles.
Y cerraste el arco hacia la práctica: se elige el percentil según cuánto importe el usuario desafortunado (p95 para lo típico, p99/p99.9 cuando la cola duele de verdad), y una SLO de latencia se escribe siempre sobre un percentil (p(95) < 200 ms), nunca sobre el promedio —que es exactamente la forma de los thresholds del módulo 5—. Antes del proyecto deberías poder: demostrar con el dato del 88% por qué el promedio miente; explicar la forma con cola de una distribución de latencias; y elegir y justificar el percentil de una SLO.
Lo que sigue es ponerlo todo junto con tus manos. En la lección 8, el mini-proyecto: levantas Reservo con su endpoint lento, corres el generador de carga, reportas p50/p95/p99, RPS y % de error reales, escribes el resumen de k6 equivalente como contenido, e interpretas por escrito por qué el p95 le importa al usuario más que el promedio. Es el módulo entero, ejecutado por ti.
Recursos
- Google SRE Book — Worrying About Your Tail (percentiles de latencia) — el argumento canónico de por qué se vigila la cola (percentiles altos) y no el promedio; el marco de toda esta lección.
statistics.fmeanystatistics.quantiles— documentación de Python — el promedio y los percentiles con los que se hace la comparación medida de esta lección.- k6 — Thresholds sobre percentiles — cómo se escribe
p(95)<200como umbral que hace pasar o fallar la prueba; el adelanto del módulo 5 que cierra este arco. - Google SRE Workbook — Implementing SLOs — cómo se eligen y escriben las SLO de latencia sobre percentiles en la práctica. Profundiza la sección "de la métrica a la SLO".