El dashboard dice que el tiempo de respuesta medio de una API es de 180 ms. Todo en verde. Al mismo tiempo, el canal de soporte se llena de mensajes de usuarios diciendo que la aplicación «va a tirones» o que «a veces se queda pillada». Nadie miente: ambas cosas son ciertas a la vez, y la grieta entre ellas tiene un nombre técnico muy concreto — la media no está capturando la cola de la distribución, y la cola es exactamente donde vive la mitad de los incidentes reales.
Este es uno de esos conceptos que se da por sentado en cualquier conversación sobre SLOs, alertas o dashboards, pero que rara vez se explica desde cero. Vamos a hacerlo aquí: qué es un percentil, para qué lo usa de verdad un equipo de observabilidad en el día a día, y qué falla cuando se aplica mal.
Qué es realmente un percentil
Un percentil es un punto de corte, no una magia estadística. Si hay 1.000 tiempos de respuesta ordenados de menor a mayor, el percentil 95 (p95) es el valor que deja al 95% de esas peticiones por debajo y al 5% restante por encima. El p99 hace lo mismo pero deja fuera solo al 1% más lento.
La trampa mental más habitual es pensar que «p95 = 350 ms» significa que la mayoría de las peticiones tardan 350 ms. No. Significa que una de cada veinte tarda más que eso. En un servicio con un millón de peticiones diarias, ese 5% son 50.000 experiencias lentas cada día — un volumen que ninguna media va a mostrar jamás, porque la media diluye ese grupo entre el resto de peticiones rápidas.
La razón estadística de fondo es que las distribuciones de latencia casi nunca son una campana de Gauss. Son distribuciones de cola larga (long-tail): un pico afilado de peticiones rápidas y una cola que se estira mucho más allá, con unas pocas peticiones catastróficamente lentas. Sobre ese tipo de forma, la media es una mentira matemáticamente correcta pero prácticamente inútil.
Para qué sirven de verdad estos dashboards en una empresa
Aquí está la pregunta que casi nunca se responde con un ejemplo concreto: ¿qué decisión distinta tomo si miro p50/p95/p99 en vez de mirar solo la media?
Imaginemos un servicio de checkout de un e-commerce, con estos números reales de una ventana de una hora:
| Métrica | Valor | Lo que un equipo concluye al verlo |
|---|---|---|
| avg | 210 ms | «Va rápido, sin problema» |
| p50 | 140 ms | La mitad de los usuarios, efectivamente, va rápido |
| p95 | 480 ms | Una de cada 20 compras tarda casi medio segundo |
| p99 | 4.200 ms | Una de cada 100 compras tarda más de 4 segundos |
La media (210 ms) es matemáticamente correcta y operativamente inútil: no le dice a nadie que 1 de cada 100 clientes está a punto de abandonar el carrito por lentitud. El p99 sí lo dice, con un número que se puede llevar a una reunión de negocio («estamos perdiendo conversión en el 1% de los checkouts») en vez de a una de estadística.
Esto es, en la práctica, para lo que sirve cada una de estas métricas dentro de un equipo de observabilidad o SRE:
p50 (la mediana) — se usa como termómetro general del servicio. Es la referencia para detectar regresiones de rendimiento amplias tras un deploy: si el p50 sube de forma sostenida, algo ha empeorado para todo el mundo, no solo para un grupo minoritario. Es la métrica que se compara entre versión anterior y nueva en un pipeline de CI/CD antes de dar luz verde a un release.
p95 — es la métrica de referencia para definir SLOs de cara al usuario («el 95% de las peticiones debe responder en menos de 300 ms») y para las alertas del día a día, porque es lo bastante sensible para detectar una degradación real sin dispararse por un único request perdido. Cuando un equipo dice «vamos a alertar sobre latencia», casi siempre significa «vamos a alertar sobre el p95», no sobre la media ni sobre el p99 (demasiado ruidoso para alertar en tiempo real, como se explica más abajo).
p99 (o p99.9 en sistemas más exigentes) — es la métrica de diagnóstico de arquitectura y de capacity planning. Un p99 que se dispara mientras el p50 se mantiene tranquilo es la señal clásica de un problema de cola: pausas de garbage collection, timeouts de una dependencia externa, saturación puntual de un pool de conexiones. Es también la métrica que se vigila antes de un pico de tráfico esperado (Black Friday, un lanzamiento de producto): si el p99 ya iba mal con tráfico normal, con tráfico de pico va a ser un incendio.
avg (media) — sigue teniendo su sitio, pero en otro terreno: métricas de recursos sin cola larga extrema, como uso medio de CPU o memoria a lo largo del día, donde no hay una minoría de valores catastróficos que distorsionen la lectura. Como medida de experiencia de usuario en latencia, es la que menos se debería usar en solitario.
En resumen, y es la frase que resume todo lo anterior: la media te dice cómo le va al sistema en promedio; los percentiles te dicen cómo le va al usuario que peor lo está pasando — y ese usuario es, casi siempre, el que define si hay incidente o no.
Un vistazo rápido a la forma de la curva
Una práctica habitual en dashboards de guardia es pintar p50, p95 y p99 juntos en el mismo panel y leer la forma, no solo el valor:
- p50 y p99 bajos y juntos → servicio sano y consistente.
- p50 bajo, p99 alto (brecha ancha) → problema de cola: GC, caché fría, dependencia lenta, contención de lock. Es el patrón más común al empezar una investigación de incidente.
- p50 y p99 subiendo juntos → saturación sistémica, el sistema se está quedando sin capacidad.
La forma dice la causa probable antes incluso de abrir una traza.Con números concretos, la diferencia entre los tres casos queda mucho más clara:
| Escenario | p50 | p99 | Lectura |
|---|---|---|---|
| Servicio sano | 90 ms | 140 ms | Brecha pequeña y estable → nada que investigar. |
| Problema de cola | 95 ms | 3.800 ms | p50 casi no se ha movido, pero el p99 se ha disparado x27 → algo puntual (GC, caché fría, una dependencia lenta) está afectando solo a una minoría de peticiones. |
| Saturación | 480 ms | 5.200 ms | Ambos han subido varias veces respecto a su valor habitual → no es un problema puntual, es que el sistema entero va cargado y necesita más capacidad (o hay que investigar por qué el tráfico ha crecido tanto). |
El primer caso no genera ni una alerta. El segundo activa el runbook de «busca al culpable puntual» (revisar logs de GC, ver si hubo un deploy reciente, comprobar el estado de una dependencia externa). El tercero activa el runbook de «esto no se arregla con un fix de código» — toca autoescalado, rate limiting o revisar qué está generando ese volumen de tráfico.
Así es como se ven realmente estos tres casos en un panel de guardia: en el servicio sano las tres líneas van pegadas y estables; en el problema de cola el p50 (cian) ni se inmuta mientras el p99 (rojo) da un pico aislado; en la saturación las tres líneas suben juntas y en paralelo, sin que ninguna se despegue de las demás. Esa forma de conjunto es, muchas veces, más informativa de un vistazo que cualquier número aislado en una tabla.
Los sospechosos habituales detrás de un p99 disparado
Cuando el p50 va bien pero el p99 se dispara, los culpables suelen repetirse:
- Pausas de garbage collection, sobre todo en JVMs con heaps grandes mal ajustados.
- Cachés fríos tras un deploy o un reinicio — la primera petición a una clave de caché paga el camino completo sin descuento.
- «Noisy neighbors» en infraestructura compartida: otro proceso satura CPU, disco o red durante unos segundos y el servicio paga el peaje.
- Reintentos y fallbacks: cuando una dependencia downstream falla, la lógica de retry añade su propio presupuesto de tiempo (a menudo 100-500 ms por intento) a la latencia percibida.
- Saturación del pool de conexiones: bajo concurrencia, las peticiones esperan turno antes de empezar a trabajar.
El problema que casi nadie tiene en cuenta: el volumen de tráfico
Aquí está un matiz que se pasa por alto casi siempre: un percentil calculado sobre pocas muestras no es una medida, es ruido con forma de número.
IBM documenta este problema directamente a raíz de incidencias reales con bases de datos DataStax/Astra: durante ventanas de tráfico bajo (menos de 5 peticiones por segundo), el p99 se vuelve extremadamente inestable, no porque el sistema vaya peor, sino porque el 99º percentil de una muestra de 300 peticiones es, literalmente, la petición número 297 más rápida. Si esa única petición se cruza con un glitch de red o contención de disco puntual, su latencia puede dispararse a varios segundos — y ese único valor pasa a ser el p99 reportado, aunque las otras 296 peticiones hayan ido perfectas.
La regla general que circula entre practicantes de SRE es que hacen falta del orden de cientos a miles de muestras por ventana temporal para que un p99 sea estable, y bastantes más aún para un p99.9. En endpoints de tráfico bajo, la recomendación práctica es ensanchar la ventana temporal de cálculo o agregar por familia de rutas en lugar de mirar el endpoint aislado — de lo contrario se persiguen fantasmas estadísticos en cada revisión de guardia.
La consecuencia directa para cualquiera que dashboardee percentiles por endpoint: antes de sacar conclusiones de un pico de p99 en un endpoint poco usado, la primera pregunta no es «¿qué ha pasado?» sino «¿cuántas peticiones había en esa ventana?».
El pecado capital: promediar percentiles ya calculados
Hay un error de agregación que se repite tanto en la industria que generó su propio hilo de queja en Hacker News: promediar percentiles ya calculados por instancia, como si fueran una métrica más que se pudiera sumar y dividir. No se puede. Un percentil no es una cantidad aditiva — es un punto de corte sobre una distribución concreta, y la distribución de «todos los servidores juntos» no es la suma de las distribuciones individuales de cada uno.
Con un ejemplo pequeño se ve mejor por qué falla. Dos servidores detrás de un balanceador, cada uno procesando 100 peticiones en la misma ventana de un minuto:
| Peticiones rápidas (50 ms) | Peticiones lentas (300 ms) | p95 de ese servidor | |
|---|---|---|---|
| Servidor A | 95 | 5 | 50 ms |
| Servidor B | 90 | 10 | 300 ms |
Si se promedian esos dos p95 tal cual — algo que se hace constantemente sin pensarlo — sale (50 + 300) / 2 = 175 ms. Ese es el número que aparece en un dashboard que «promedia percentiles por instancia».
Pero si se juntan las 200 peticiones reales de ambos servidores y se calcula el p95 de verdad sobre ese conjunto completo, hay 185 peticiones rápidas y 15 lentas. La petición número 190 de 200 — el verdadero p95 global — ya cae dentro del grupo lento. El p95 real es 300 ms, no 175 ms.
El «promedio de percentiles» dice que el sistema va a 175 ms cuando en realidad el 5% de los usuarios está sufriendo 300 ms. Es un número tranquilizador y falso a la vez, y la razón de fondo es que el resultado depende de cuántas peticiones tuvo cada servidor — algo que un simple promedio, sin ponderar y sin conocer la distribución interna de cada uno, no tiene forma de saber.
El resultado práctico a mayor escala: si hay diez réplicas y se calcula el p95 en cada una por separado y luego se promedian esos diez valores, el número que se obtiene cambia según cuántas réplicas haya desplegadas o qué combinación de tags se use para filtrar — sin que el comportamiento real del sistema haya cambiado en absoluto. Es un artefacto de la agregación, no una señal.
La forma correcta de resolverlo es agregar sobre los datos crudos (o sobre una estructura que preserve suficiente resolución, como un histograma o un sketch) antes de calcular el percentil, no después.
Cómo lo calcula cada herramienta por debajo (resumen)
No todas las herramientas resuelven esto igual, y la diferencia importa sobre todo a la hora de fiarse de un número que cruza varias réplicas o regiones:
- Prometheus ofrece dos caminos: el tipo Summary calcula el percentil en la propia aplicación (no se puede agregar entre réplicas), y el tipo Histogram cuenta observaciones en buckets y calcula el percentil en el servidor con
histogram_quantile(), con un margen de error que depende de cuán finos sean los buckets. Desde 2023 soporta también native histograms con resolución exponencial, que reducen mucho ese error. - OpenTelemetry define histogramas de buckets explícitos y, más recientemente, histogramas exponenciales que ajustan los límites automáticamente y dan mejor precisión con menos buckets.
- Dynatrace calcula
percentile()en DQL sobre Grail usando una representación de histograma exponencial, con una precisión garantizada de ~2,2%. Merece la pena recordar que Grail no incluyó esta función en su lanzamiento inicial (solo min, max, sum, avg y count) — se añadió más tarde a petición explícita de la comunidad. - Datadog resuelve el problema de agregación entre hosts de raíz con DDSketch, una estructura que agrega los valores crudos de todas las instancias antes de calcular el percentil, evitando el antipatrón de «promediar percentiles ya calculados».
- New Relic expone una función
percentile()directamente en NRQL sobre los eventos almacenados. - Grafana no calcula nada por sí mismo: es una capa de visualización que delega el cálculo en la fuente de datos (Prometheus, Tempo, etc.).
En síntesis: si el stack agrega sobre datos crudos o histogramas antes de calcular el percentil (Dynatrace, Datadog, Prometheus con native histograms), los números entre réplicas y regiones son de fiar. Si en algún punto de la cadena se están promediando percentiles ya calculados, no lo son — sea cual sea la herramienta.
Cómo aplicarlo sin tropezar
- Nunca decidir nada de un p99 sin mirar antes el volumen de tráfico de esa ventana. Si el endpoint tiene pocas peticiones por segundo, ensanchar la ventana temporal o agrupar por familia de rutas antes de sacar conclusiones.
- No promediar percentiles ya calculados entre instancias o regiones. Si la herramienta no agrega sobre datos crudos, calcular el percentil global explícitamente en la consulta, no como un promedio de promedios.
- Pintar p50, p95 y p99 juntos, nunca uno solo. La brecha entre ellos es el diagnóstico, no el valor absoluto de cada uno.
- Reservar avg() para métricas de tendencia sin cola larga (uso de CPU, memoria) y max() para límites duros y SLAs contractuales — no como sustituto de un percentil de latencia.
- No perseguir p100. El request más lento de todos es, casi siempre, ruido puro: un timeout de red, un proceso de mantenimiento, una anomalía de una sola vez que no representa nada sistémico.
- Segmentar antes de alertar. Un p99 global puede esconder qué segmento concreto sufre — separar por endpoint, por región o por tipo de cliente antes de decidir si hay incidente real.
Para seguir aprendiendo
- Documentación oficial de Prometheus sobre histogramas y summaries.
- Especificación de métricas de OpenTelemetry, sección de tipos de histograma.
- Documentación de Dynatrace sobre la función
percentile()en DQL y su garantía de error (~2,2%). - Documentación de Datadog sobre distribution metrics y DDSketch.
- IBM: por qué el p99 es poco fiable con tráfico bajo.
- Hacker News: el hilo sobre promediar percentiles.