La API principal consume 180 ms en lógica de aplicación, la consulta a la base de datos tarda 220 ms y una dependencia externa añade otros 250 ms. Ningún componente parece catastrófico por separado. El usuario, sin embargo, espera 800 ms.
Ese desfase es el problema operativo: la latencia no pertenece a un único servicio. Es el resultado acumulado de cada conexión, cola, dependencia, reintento, bloqueo, serialización y decisión arquitectónica que forma parte del camino crítico de una solicitud.
Por eso, un análisis útil no puede limitarse a preguntar «¿qué servicio está lento?». Debe identificar dónde se consume el tiempo transcurrido, qué parte es variable, qué esperas pueden evitarse, qué trabajo puede salir del camino síncrono y si la corrección propuesta mejora el percentil visible para el usuario sin desplazar el cuello de botella hacia otro componente.
Este artículo explica cómo interpretar P50, P95, P99 y tail latency; diseñar un presupuesto end-to-end; detectar amplificación por fan-out; separar tiempo de procesamiento de tiempo de espera; e investigar una API lenta con métricas, trazas, logs y validación controlada.
La latencia es tiempo transcurrido, no velocidad
La latencia es el tiempo transcurrido entre el inicio de una operación y el momento en que su resultado queda disponible para el observador que importa.
Ese observador debe definirse explícitamente. Un backend puede registrar 120 ms de procesamiento mientras el navegador mide 430 ms desde que inicia la solicitud hasta que recibe el último byte. Ambas mediciones pueden ser correctas porque cubren límites distintos.
En una solicitud distribuida, el tiempo puede separarse en varias categorías:
| Categoría | Qué mide | Evidencia habitual |
|---|---|---|
| Latencia de red | Tiempo empleado en atravesar rutas de red | Round-trip time, pérdida de paquetes, comparación por región o ruta |
| Latencia de conexión | DNS, establecimiento de transporte, TLS y negociación con proxies | Tiempos por fase en cliente, tasa de reutilización, spans de handshake |
| Latencia de procesamiento | Tiempo ejecutando lógica de aplicación o trabajo de base de datos | Perfiles de CPU, planes de ejecución, self-time de spans |
| Latencia de cola | Tiempo esperando antes de empezar a trabajar | Profundidad de cola, espera en executors, adquisición de conexiones |
| Latencia por contención | Tiempo esperando recursos compartidos | Lock waits, threads estacionados, bloqueos en base de datos |
| Latencia de dependencia | Tiempo esperando otro servicio o datastore | Client spans, métricas de dependencia, conteo de timeouts |
| Latencia de serialización | Codificación, decodificación, compresión y copias de payload | Perfiles de CPU, tamaño de payload, spans de serialización |
| Latencia end-to-end | Tiempo total visible para el usuario | RUM, pruebas sintéticas, temporizadores de edge o cliente |
Reducir la latencia a «velocidad» oculta la decisión que realmente importa. Un sistema no es lento por una razón universal. Puede estar calculando lentamente, esperando capacidad, creando conexiones una y otra vez, ejecutando trabajo síncrono innecesario o amplificando una cola pequeña mediante fan-out.
El objetivo del diagnóstico es separar esos mecanismos antes de cambiar configuración.
Latencia y throughput responden preguntas distintas
Latencia y throughput están relacionados, pero no son intercambiables.
| Métrica | Pregunta que responde |
|---|---|
| Latencia | ¿Cuánto tarda una operación? |
| Throughput | ¿Cuántas operaciones terminan por unidad de tiempo? |
Un sistema puede presentar cualquiera de estas combinaciones:
- Baja latencia y bajo throughput: un servicio con poca carga completa cada solicitud rápidamente, pero procesa poco trabajo total.
- Baja latencia y alto throughput: el estado deseado, siempre que la tasa de error y el margen de recursos sigan siendo aceptables.
- Alta latencia y alto throughput: el batching o las colas profundas mantienen alto el número de completados mientras cada solicitud espera demasiado.
- Alta latencia y throughput descendente: la saturación, la contención, los reintentos o la recuperación de fallos han cruzado un punto de degradación no lineal.
El error de interpretación más peligroso consiste en usar throughput estable como prueba de que la latencia está sana. Un pool de workers puede seguir completando 1,000 solicitudes por segundo mientras su cola crece en 100 solicitudes por segundo. El throughput parece estable; el tiempo de respuesta ya se está deteriorando.
También existe el error inverso. Reducir concurrencia puede disminuir la latencia de cola y, al mismo tiempo, reducir el throughput máximo. Que eso sea una mejora depende del SLO, la forma de la carga y la capacidad requerida.
Por qué el promedio oculta el impacto real
Las distribuciones de latencia suelen ser asimétricas. La mayoría de las solicitudes se concentra alrededor de un rango central, mientras una fracción menor se extiende hacia una cola larga debido a cache misses, contención de locks, pausas de garbage collection, retries, conexiones frías, noisy neighbors, almacenamiento lento o dependencias saturadas.
Considera 100 solicitudes:
99 solicitudes: 100 ms cada una
1 solicitud: 10,000 ms
La media aritmética es:
((99 × 100) + 10.000) / 100 = 199 ms
Un promedio de 199 ms puede parecer aceptable si el objetivo es 250 ms. También oculta una solicitud que tardó diez segundos.
El promedio no es falso. Responde una pregunta operacional débil: «¿cuál fue el tiempo total dividido entre el número de solicitudes?». No muestra cuántos usuarios sufrieron la cola, cuán extrema fue ni si las solicitudes afectadas compartían región, versión, operación, forma de payload o dependencia.
Los percentiles ofrecen una lectura más útil, pero siguen necesitando una población y una ventana definidas. Un P99 calculado mezclando todos los endpoints, todos los códigos de estado y un día completo puede ocultar una regresión grave en una operación crítica.
Para profundizar en cálculo de cuantiles, precisión de histogramas, ventanas de observación y alertas, consulta el artículo dedicado a cómo interpretar percentiles de latencia (P50, P95 y P99).
Cómo interpretar P50, P95, P99 y P99.9
Un percentil es un umbral dentro de una distribución observada.
- P50: el 50 % de las observaciones terminó en ese valor o menos. Representa la mediana, no el promedio.
- P95: el 95 % terminó en ese valor o menos. Expone una parte relevante de la cola sin concentrarse únicamente en los casos más extremos.
- P99: el 99 % terminó en ese valor o menos. A escala, el 1 % restante puede seguir representando miles o millones de solicitudes.
- P99.9: el 99.9 % terminó en ese valor o menos. Se vuelve operacionalmente relevante cuando una de cada mil solicitudes ocurre con suficiente frecuencia como para dejar de ser excepcional.
Un P99 de 1.8 segundos significa que, para la población y la ventana definidas, el 99 % de las observaciones fue de 1.8 segundos o menos. No significa que todas las solicitudes del 1 % más lento hayan tardado exactamente 1.8 segundos.
Todo percentil debe ir acompañado de tres datos:
- Población: endpoint, operación, clase de cliente, estado, región, versión y clase de payload.
- Ventana: un minuto, cinco minutos, una hora, intervalo de despliegue o periodo pico.
- Estimador: muestras exactas, summary del cliente, histograma clásico, histograma nativo u otra aproximación.
Los límites de buckets afectan la precisión de los cuantiles. Un P95 estimado a partir de buckets gruesos puede servir para un umbral de SLO y seguir siendo demasiado impreciso para evaluar una optimización de 20 ms. Prometheus documenta los trade-offs entre histogramas y summaries, así como el error introducido por el diseño de buckets.
No compares percentiles si su alcance y cálculo no son compatibles. Comparar un P95 del cliente móvil con un P95 del servidor sobre solicitudes exitosas puede ser útil, pero la diferencia debe interpretarse como una diferencia de límites de medición, no como una contradicción.
Por qué la tail latency se convierte en latencia del sistema
Una solicitud lenta es un evento local. Una operación distribuida que espera múltiples subsolicitudes transforma colas locales en comportamiento agregado.
Supón que un servicio llama en paralelo a 20 dependencias y no puede responder hasta que todas terminan. Si cada dependencia tiene, de forma independiente, una probabilidad del 1 % de ser «lenta», la probabilidad de que al menos una sea lenta es:
P(al menos una dependencia lenta)
= 1 - P(todas las dependencias rápidas)
= 1 - 0.99^20
≈ 18.2 %
La cola local del 1 % se convirtió en una probabilidad del 18.2 % de que la operación agregada encuentre al menos una rama lenta.
La independencia es solo un modelo. En producción, las dependencias suelen compartir redes, runtimes, clústeres, almacenamiento, eventos de despliegue o picos de tráfico. La ralentización correlacionada puede empeorar el comportamiento agregado respecto de lo que predice el modelo independiente. En otros casos, caches compartidas o co-localización pueden mover las ramas de forma conjunta y alterar la distribución de otra manera.
El hecho arquitectónico importante se mantiene: cuando el resultado depende de la rama más lenta, la duración máxima domina el camino crítico. El paper The Tail at Scale describe este efecto y la necesidad de construir servicios tolerantes a la cola, en lugar de asumir que componentes variables producirán por sí solos un sistema predecible.
La latencia end-to-end es un problema de camino crítico
Medir solo el servicio principal produce un modelo incompleto. El recorrido visible para el usuario puede incluir:
- planificación en el cliente y latencia de su red local;
- resolución DNS;
- establecimiento de TCP, QUIC u otro transporte;
- negociación TLS;
- CDN, balanceador, reverse proxy, WAF o API gateway;
- autenticación y autorización;
- cola y ejecución de aplicación;
- adquisición de conexión, consulta y transferencia de resultados de base de datos;
- APIs downstream, sistemas de mensajería o almacenamiento;
- serialización, compresión y transferencia de respuesta;
- renderizado o posprocesamiento en el cliente.
Una primera pregunta útil es:
¿El tiempo se consumió antes de que la solicitud llegara a la aplicación, durante el procesamiento, mientras esperaba otro recurso o durante el retorno de la respuesta?
Esa pregunta evita optimizar código cuando el coste dominante es el establecimiento de conexiones, o ampliar capacidad de base de datos cuando el span dominante es la espera para adquirir una conexión del pool.
Cómo diseñar un presupuesto de latencia
Un presupuesto de latencia convierte un objetivo vago de rendimiento en una restricción arquitectónica. Define cuánto tiempo puede consumir la operación completa y cómo se distribuye ese tiempo a lo largo del camino crítico.
Comenzar con un objetivo end-to-end
Ejemplo:
Objetivo de API de checkout: P95 ≤ 800 ms
Límite de medición: inicio de solicitud en cliente hasta respuesta completa
Población: solicitudes exitosas de checkout en la región primaria
Ventana: 5 minutos móviles para alertas; 28 días para reporte de SLO
El límite y la población forman parte del objetivo. «P95 menor de 800 ms» está incompleto sin ellos.
Google SRE considera la latencia una de las cuatro señales doradas y recomienda separar la latencia de solicitudes exitosas de la de solicitudes fallidas, porque los fallos pueden ser rápidos y distorsionar la distribución combinada.
Distribuir el presupuesto por componente del camino crítico
Un primer borrador podría asignar los 800 ms completos:
| Componente | Presupuesto inicial |
|---|---|
| Cliente y red | 80 ms |
| API gateway | 40 ms |
| Autenticación | 60 ms |
| Servicio principal | 180 ms |
| Base de datos | 220 ms |
| Dependencia externa | 170 ms |
| Serialización | 50 ms |
| Total | 800 ms |
La suma es correcta y el diseño es frágil. Asume que todos los componentes permanecerán simultáneamente dentro de su asignación. No existe margen para jitter de red, pausas del runtime, cache misses, efectos de despliegue, retrasos del scheduler o error de medición.
Un presupuesto operativo debe reservar holgura:
| Componente | Asignación operativa |
|---|---|
| Cliente y red | 70 ms |
| API gateway | 35 ms |
| Autenticación | 50 ms |
| Servicio principal | 150 ms |
| Base de datos | 180 ms |
| Dependencia externa | 160 ms |
| Serialización | 35 ms |
| Reserva de variabilidad y crecimiento | 120 ms |
| Objetivo total | 800 ms |
La reserva no es tiempo sin propietario. Protege frente a variación y cambio futuro. Si se consume de forma permanente, deja de ser reserva.
Asignar usando percentiles compatibles
Un objetivo end-to-end P95 no puede garantizarse sumando simplemente el P95 de cada componente. Los percentiles no son aditivos en general, especialmente cuando las operaciones se superponen o están correlacionadas.
Usa percentiles por componente como restricciones e indicadores diagnósticos, y valida el objetivo agregado mediante mediciones end-to-end bajo tráfico representativo. Si necesitas un modelo formal, usa la distribución conjunta o simulación, en lugar de asumir que P95(A) + P95(B) equivale a P95(A + B).
Propagar deadlines a través del grafo de llamadas
El timeout downstream debe caber dentro del tiempo restante del llamador. Si una solicitud upstream tiene 300 ms antes de vencer, asignar 500 ms a una dependencia no protege el objetivo end-to-end.
Una secuencia práctica es:
presupuesto restante
- limpieza local y serialización de respuesta
- margen para retry, si está justificado
- margen de seguridad
= duración máxima del intento downstream
Los timeouts deben derivarse del deadline de la operación, la distribución de la dependencia y la semántica de fallo; no copiarse de un valor predeterminado del framework. El artículo sobre timeouts correctamente configurados debe concentrar la política completa de timeouts y retries.
Caso práctico: descomponer una solicitud de 800 ms
Supón que una traza muestra el siguiente camino crítico serial:
| Segmento del camino crítico | Duración observada |
|---|---|
| API gateway | 40 ms |
| Autenticación | 60 ms |
| Procesamiento del servicio principal | 180 ms |
| Operación de base de datos | 220 ms |
| Dependencia externa | 250 ms |
| Serialización y transferencia final de red | 50 ms |
| Total end-to-end | 800 ms |
40 + 60 + 180 + 220 + 250 + 50 = 800 ms
El componente más grande es la dependencia externa, con 250 ms, pero «optimizar el número más grande» todavía no es una decisión. Pregunta:
- ¿Qué componente domina el camino crítico? Aquí la llamada externa es la mayor, pero la base de datos está cerca.
- ¿Qué componente presenta más variabilidad? Una llamada estable de 250 ms puede influir menos en P99 que una consulta que fluctúa entre 40 ms y 1.5 segundos.
- ¿Qué operaciones son realmente seriales? Si base de datos y dependencia externa pueden ejecutarse en paralelo sin violar la semántica, el máximo de ambas ramas podría reemplazar su suma.
- ¿Qué trabajo puede salir del camino síncrono? Auditoría, enriquecimiento, notificaciones o indexación secundaria quizá no necesiten bloquear la respuesta.
- ¿Qué llamada puede eliminarse? Datos cacheados, precalculados, desnormalizados o transportados en la propia solicitud pueden eliminar una dependencia, pero la frescura y la corrección limitan esa decisión.
- ¿Dónde debe aplicarse un timeout? Debe preservar tiempo suficiente para fallback, compensación o error controlado antes del deadline upstream.
- ¿Cuál es la métrica de validación? El cambio debe mejorar P95 o P99 end-to-end para la operación objetivo sin introducir regresiones inaceptables de error, obsolescencia, recursos o coste.
Una optimización local que reduzca 40 ms de CPU, pero deje intacta una cola P99 de 900 ms en una dependencia, puede mejorar benchmarks sin cambiar la experiencia del usuario.
Fan-out y amplificación de latencia
Fan-out ocurre cuando una operación emite múltiples llamadas downstream. Ejecutarlas en paralelo reduce la suma de duraciones, pero el join sigue esperando la rama obligatoria más lenta.
El P99 agregado puede ser materialmente peor que el P99 individual de cada dependencia porque cada operación de usuario crea más oportunidades de encontrar una rama lenta. Los retries pueden añadir nuevas ramas mientras el trabajo original sigue ejecutándose, elevando la carga y la correlación.
Las respuestas posibles dependen de la semántica:
| Decisión | Cuándo ayuda | Trade-off |
|---|---|---|
| Reducir fan-out | Algunas llamadas son redundantes o pueden agregarse | Puede exigir rediseñar APIs o el modelo de datos |
| Paralelizar llamadas seriales | Las llamadas son independientes | Aumenta la concurrencia instantánea y la carga downstream |
| Usar resultados parciales | No todas las ramas son obligatorias | La respuesta puede ser incompleta o de menor calidad |
| Aplicar hedging | Eventos raros de cola larga dominan y duplicar es seguro | Añade carga; requiere controles estrictos y cancelación |
| Cachear o precalcular | Los datos cambian menos de lo que se consultan | Costes de frescura, invalidación, memoria y consistencia |
| Mover trabajo a asíncrono | El usuario necesita aceptación, no finalización inmediata | Finalización eventual y mayor complejidad operativa |
| Definir deadlines por rama | Una rama opcional no debe consumir toda la solicitud | Requiere un comportamiento degradado explícito |
Hedging no es un retry genérico. Emite deliberadamente un segundo intento antes de que el primero haya fallado, normalmente tras un retraso derivado de la distribución normal de latencia. Sin controles de capacidad y semántica idempotente, puede empeorar una sobrecarga.
DNS, TCP, TLS y establecimiento de conexiones
La latencia puede acumularse antes de que el procesamiento de la aplicación siquiera comience.
Una solicitud sobre una conexión fría puede requerir:
- resolución DNS;
- establecimiento de la conexión de transporte;
- negociación TLS;
- negociación con proxy o gateway;
- inicialización del protocolo de aplicación;
- recién entonces, envío de la solicitud y procesamiento en el servidor.
El establecimiento de una conexión TCP utiliza un handshake de tres pasos, especificado en RFC 9293. TLS añade negociación criptográfica y autenticación; la especificación vigente de TLS 1.3 es RFC 9846. El número real de round trips depende de la versión del protocolo, reanudación de sesión, transporte, pérdida de paquetes, middleboxes e implementación.
La distinción operativa relevante es entre conexiones frías y reutilizadas:
| Señal | Interpretación probable |
|---|---|
| Tiempo DNS alto | Retraso del resolver, cache misses, search domains o problema de ruta |
| Tiempo de conexión alto | RTT, pérdida de paquetes, retransmisiones SYN, listener saturado o routing |
| Tiempo TLS alto | Handshake completo, validación de certificados, coste criptográfico, pérdida o reanudación fallida |
| Baja reutilización de conexiones | Keep-alive deshabilitado, vidas demasiado cortas, configuración incompatible del pool o comportamiento del proxy |
| Pico tras un despliegue | Pools reiniciados, caches vacías, nuevas instancias o churn de conexiones |
| Span del servidor rápido y duración del cliente lenta | El coste está fuera del límite medido por el servidor |
No deduzcas la fase a partir de un único temporizador agregado. Captura tiempos por fase en el cliente cuando estén disponibles y correlaciónalos con las trazas del servidor. Un span rápido del servidor no explica el tiempo gastado antes de que el servidor reciba la solicitud.
Reutilizar conexiones suele reducir el coste de establecimiento, pero una vida ilimitada tampoco es universalmente correcta. Cambios DNS, rotación del balanceador, conexiones obsoletas, renovación de certificados o distribución desigual pueden requerir vidas acotadas y validación de salud.
Los pools de conexiones pueden eliminar o crear esperas
Un pool amortiza el coste de crear conexiones y limita el uso concurrente de un recurso downstream. También puede convertirse en una cola cuyo tiempo de espera supera al de la propia operación.
Considera esta traza:
Adquisición del pool de base de datos: 380 ms
Ejecución de la consulta: 42 ms
Decodificación del resultado: 8 ms
Span total de base de datos: 430 ms
Optimizar SQL no recuperará los 380 ms invertidos esperando una conexión.
Las señales relevantes del pool son:
- conexiones activas;
- conexiones ociosas;
- máximo configurado;
- solicitudes pendientes o waiters;
- distribución del tiempo de adquisición;
- conteo de timeouts de adquisición;
- tasa de creación y fallos al crear conexiones;
- edad de las conexiones y motivo de expulsión;
- utilización de conexiones en el servidor downstream;
- duración de transacciones o leases.
Dimensionar un pool es una decisión de control de concurrencia, no una regla de tres.
| Estado del pool | Acción tentadora | Riesgo | Proceso de decisión más sólido |
|---|---|---|---|
| Pool agotado con frecuencia | Aumentar el máximo | Sobrecargar la base de datos, aumentar locks o agotar conexiones del servidor | Confirmar capacidad downstream, concurrencia de consultas, duración de transacciones y suma de pools entre instancias |
| Muchas conexiones ociosas | Reducir el pool | Reintroducir coste de conexión o generar ráfagas de creación | Comparar vida ociosa, forma de los picos, coste de establecimiento y límites del servidor |
| Espera de adquisición larga y consulta corta | Aumentar el pool | Mover la cola hacia la base de datos | Probar incrementos controlados observando CPU, I/O, lock waits, throughput y P95/P99 de consultas |
| Alta tasa de creación | Extender la vida | Conservar conexiones obsoletas o mal distribuidas | Revisar expulsiones, resets de red, DNS, idle timeout del proxy y política de validación |
| Timeouts con capacidad libre | Aumentar el pool | No corrige leaks, deadlocks ni errores de contabilización | Inspeccionar propiedad, devolución y transacciones bloqueadas |
Un cambio válido mejora la latencia end-to-end y reduce la espera del pool sin provocar saturación downstream. Debe validarse con concurrencia representativa, no solo con una prueba de un usuario.
Cold start no describe un único problema
El término cold start suele emplearse para etiquetar cualquier lentitud en la primera solicitud. Los mecanismos subyacentes son distintos:
- creación de proceso o contenedor;
- inicialización del runtime;
- carga de módulos o inyección de dependencias;
- compilación JIT;
- carga diferida de datos;
- pools de conexiones vacíos;
- page cache o filesystem cache fríos;
- cache de aplicación fría;
- primera consulta que compila un plan o carga páginas;
- autoscaling que añade capacidad después de que la demanda ya llegó.
Cada mecanismo requiere una corrección diferente. Mantener instancias preaprovisionadas puede resolver la creación del proceso y no una cache fría. Abrir conexiones anticipadamente puede reducir el primer acceso y provocar una tormenta de conexiones durante el rollout. Calentar cada ruta posible puede alargar el despliegue y consumir recursos sin retorno.
Para diagnosticar un posible cold start, segmenta la latencia por:
- edad de la instancia;
- ordinal de solicitud desde el arranque;
- evento de despliegue o escalado;
- duración de inicialización del runtime;
- número de conexiones creadas;
- tasa de aciertos de cache;
- actividad de JIT o compilación;
- primera consulta frente a estado estable.
Una validación útil compara las primeras N solicitudes de instancias nuevas con solicitudes en estado estable, usando la misma entrada y carga. Si solo la primera es lenta, la traza debe mostrar qué segmento de inicialización consumió el tiempo.
Colas, saturación y backpressure
La latencia suele aumentar antes de que aparezcan errores visibles. Cuando las llegadas superan la capacidad sostenible de procesamiento, el trabajo espera en una cola. El servicio puede seguir completando solicitudes y mostrar CPU promedio moderada mientras executors, particiones, conexiones o recursos downstream específicos ya están saturados.
Google SRE identifica la sobrecarga como una causa frecuente de fallos en cascada y recomienda diseñar explícitamente el comportamiento bajo overload, en lugar de permitir que colas y retries crezcan sin control.
La ley de Little aporta una relación útil en estado estable:
L = λ × W
Donde:
Les el número promedio de elementos dentro del sistema;λes la tasa promedio de llegada o finalización;Wes el tiempo promedio dentro del sistema.
Si un servicio estable procesa 500 solicitudes por segundo y el tiempo promedio en el sistema es 200 ms:
L = 500 × 0,2 = 100 solicitudes en vuelo, en promedio
Si la población observada en vuelo aumenta hacia 600 mientras la tasa de llegada se mantiene cerca de 500 solicitudes por segundo, el tiempo promedio se ha desplazado hacia 1.2 segundos o el sistema ha dejado de estar en estado estable. La ecuación ayuda a detectar una inconsistencia; no demuestra dónde se produce la espera.
Backpressure es el mecanismo mediante el cual un sistema comunica o impone que no puede aceptar trabajo ilimitado al ritmo actual. Según el protocolo, puede materializarse como colas acotadas, control de flujo, límites de concurrencia, admission control, rate limits, load shedding o respuestas explícitas de sobrecarga.
La decisión no es «cola o no cola». Una cola acotada puede absorber ráfagas breves. Una cola sin límite convierte overload en crecimiento de memoria y trabajo obsoleto. Una cola demasiado pequeña puede rechazar picos inofensivos. El límite depende del tiempo de espera tolerable, tasa de servicio, características de las ráfagas y semántica de fallo.
Los reintentos pueden convertir latencia en sobrecarga
Un retry consume tiempo y genera trabajo adicional. Solo se justifica cuando el fallo es transitorio, la operación puede repetirse de forma segura, queda deadline suficiente y la carga añadida no dificulta la recuperación.
Los retries en varias capas son especialmente peligrosos. Supón que tres capas permiten tres intentos totales: el intento inicial más dos reintentos. En el peor caso, una operación original puede producir:
3 × 3 × 3 = 27 intentos downstream
Si cada capa permite tres reintentos adicionales, existen cuatro intentos totales por capa:
4 × 4 × 4 = 64 intentos downstream
La diferencia entre intentos totales y reintentos adicionales debe quedar explícita en configuración y documentación.
AWS describe los retries como «egoístas» porque consumen recursos del servidor para mejorar la probabilidad de éxito de un llamador, y recomienda timeouts, exponential backoff acotado y jitter en lugar de reintentos inmediatos y sincronizados. Google SRE también advierte que los reintentos pueden amplificar una sobrecarga y contribuir a fallos en cascada.
Una política defendible debe especificar:
- qué resultados son retryable;
- si la operación es idempotente o está protegida por una idempotency key;
- intentos totales, evitando la expresión ambigua «número de retries»;
- timeout por intento;
- deadline global;
- función de backoff y retraso máximo;
- estrategia de jitter;
- presupuesto o límite de concurrencia para retries;
- observabilidad separada entre intentos originales y reintentos;
- cancelación cuando la solicitud upstream ya terminó.
El desarrollo completo pertenece al artículo sobre tormentas de reintentos. El punto específico aquí es que los retries consumen el mismo presupuesto de latencia y capacidad que se está investigando. Cuando la degradación se vuelve persistente, un circuit breaker puede cortar los intentos antes de que amplifiquen la caída.
Caminos síncronos y asíncronos
Mover trabajo a un camino asíncrono no elimina la latencia. Cambia qué latencia espera el usuario y dónde se gestionan la finalización, los retries, el orden y los fallos.
| Dimensión de decisión | Camino síncrono | Camino asíncrono |
|---|---|---|
| Resultado final inmediato | Normalmente disponible | No necesariamente |
| Acoplamiento temporal | Mayor | Menor entre productor y consumidor |
| Latencia visible para el usuario | Incluye finalización downstream | Puede incluir solo validación y aceptación |
| Latencia de finalización | Coincide con la respuesta | Continúa después del acknowledgement |
| Comunicación de fallos | Respuesta o timeout inmediato | Estado, callback, evento, polling o reconciliación |
| Propietario de retries | Cliente o servicio | Consumidor o plataforma de mensajería |
| Consistencia | Inmediata o cercana | A menudo eventual |
| Complejidad operativa | Menor en flujos simples | Mayor por colas, replay, orden, duplicados y poison messages |
La pregunta decisiva es:
¿El usuario necesita el resultado final ahora o solo una confirmación persistente de que la solicitud fue aceptada?
Un diseño asíncrono es apropiado cuando el contrato de producto tolera finalización diferida. No lo es cuando el usuario debe saber si se reservó inventario, se concedió acceso o se confirmó una transacción antes de continuar.
Mide ambas:
- latencia de acknowledgement: tiempo hasta que el productor recibe la aceptación;
- latencia de finalización: tiempo hasta que el estado solicitado realmente se alcanza.
Reportar solo la primera puede hacer que el sistema parezca rápido mientras el backlog crece durante minutos.
Instrumentación mínima para analizar latencia
Diagnosticar latencia exige evidencia compatible entre métricas, trazas y logs.
Métricas por operación
Como mínimo, captura:
- número de solicitudes;
- conteo de éxitos y errores;
- histograma de duración;
- P50, P95 y P99 derivados de un instrumento de distribución adecuado;
- tiempo de espera en cola o scheduler;
- espera para adquirir conexiones;
- duración por dependencia, operación de baja cardinalidad y clase de destino;
- conteo de timeouts;
- número de intentos de retry;
- solicitudes en vuelo o concurrencia;
- señales de saturación del recurso restringido.
OpenTelemetry define http.server.request.duration y http.client.request.duration como histogramas de duración HTTP en sus convenciones semánticas. Usa las convenciones soportadas por la versión de instrumentación desplegada y evita mezclar silenciosamente nombres legacy y stable en un mismo dashboard.
Separa la latencia de éxitos y fallos. Una solicitud rechazada rápidamente y una solicitud exitosa lenta representan resultados distintos. Combinarlas puede hacer que el gráfico de latencia baje durante una caída.
Trazas distribuidas
Una traza debe hacer visible el camino crítico mediante:
- spans de servidor y cliente;
- relaciones padre-hijo;
- instante de inicio y duración;
- nombre o clase de dependencia;
- estado y tipo de error;
- retries y redirects;
- eventos de cola o adquisición cuando exista instrumentación;
- atributos seleccionados y de baja cardinalidad, como operación, región, versión y ruta.
Las convenciones semánticas de OpenTelemetry para HTTP y bases de datos definen estructuras comunes de spans, y W3C Trace Context estandariza la propagación de traceparent y tracestate, permitiendo mantener correlación entre componentes y proveedores. Para ampliar la arquitectura de trazas, propagación y sampling, revisa observabilidad con métricas, logs y trazas.
El sampling debe corresponder al objetivo diagnóstico. Un muestreo aleatorio de baja tasa puede perder eventos P99.9. El tail-based sampling puede conservar trazas lentas o fallidas, pero añade estado en collectors, retraso, coste y nuevos modos de fallo. La elección es una decisión de arquitectura de observabilidad, no un valor predeterminado universal.
Logs estructurados
Campos útiles para investigar latencia:
trace_id
span_id
operation
route
region
version
dependency
duration_ms
queue_wait_ms
connection_wait_ms
attempt_number
outcome
error_type
No coloques identificadores sin límite, consultas completas, secretos ni payloads sensibles en labels de métricas o atributos de trazas sin control. La alta cardinalidad puede volver el sistema de telemetría caro o inutilizable precisamente durante un incidente.
Requisitos de correlación
Las métricas identifican la distribución y el segmento afectado. Las trazas explican el recorrido. Los logs aportan estado detallado y contexto de error. Los instrumentos deben compartir dimensiones estables para que un ingeniero pueda pasar de:
Regresión P99 en operación X, región Y y versión Z
a:
Trazas lentas de operación X, región Y y versión Z
y después a:
Logs del span dominante y resultado de su dependencia
Para un flujo completo de investigación, consulta cómo investigar un incidente con métricas, logs y trazas.
Método de producción para diagnosticar una API lenta
El orden importa. Saltar directamente a optimizar código suele desperdiciar tiempo porque el retraso dominante puede estar en una cola, una conexión o una dependencia.
Paso 1: confirmar impacto y límites de medición
Establece:
- la operación afectada;
- P50, P95 y P99 en cliente y servidor;
- latencia separada para éxitos y errores;
- volumen y mezcla de tráfico;
- SLO u objetivo explícito visible para el usuario;
- instante de inicio y relación con despliegues, escalado o eventos de dependencias.
Un aumento de P99 de 700 ms a 1.4 segundos con diez solicitudes por minuto tiene una relevancia y estabilidad estadística distintas del mismo aumento con 20,000 solicitudes por segundo.
Paso 2: segmentar antes de promediar
Divide la distribución por dimensiones de baja cardinalidad:
- ruta u operación;
- región o zona;
- versión de aplicación;
- clase de cliente;
- estado o resultado;
- dependencia;
- clase de payload o workload;
- hit o miss de cache, cuando pueda modelarse de forma segura.
El objetivo es encontrar una población donde la lentitud se concentre. No crees labels sin límite para conseguirlo.
Paso 3: separar procesamiento de espera
Inspecciona:
- CPU y presión de run queue;
- retraso del event loop o de threads;
- tiempo de cola en executors;
- espera en pools de conexiones;
- locks e I/O de base de datos;
- duración de solicitudes downstream;
- garbage collection o pausas del runtime;
- esperas de filesystem, red o almacenamiento.
CPU baja no demuestra capacidad disponible. Un servicio bloqueado esperando un pool saturado puede mostrar poca CPU y mucha latencia. Una sola partición caliente puede estar sobrecargada mientras el promedio de la flota permanece moderado.
Paso 4: seguir la traza e identificar el camino crítico
Busca:
- la rama obligatoria más larga;
- cadenas seriales de dependencias;
- spans superpuestos;
- intentos repetidos;
- huecos de instrumentación;
- tiempo anterior al primer span del servidor;
- tiempo posterior a la finalización de la aplicación;
- padres largos con poco self-time y pocos hijos, señal posible de una espera no instrumentada.
No sumes todos los spans. Determina cuáles forman el camino crítico de reloj.
Paso 5: comparar trazas sanas y lentas
Usa la misma operación y entradas similares. Compara:
- número de ramas;
- estado de cache;
- adquisición de conexiones;
- forma de la consulta;
- tamaño de payload;
- reutilización de conexiones;
- número de retries;
- región y edad de instancia;
- versión y feature flags.
Una traza lenta genera hipótesis. Una diferencia repetida sobre una muestra representativa aporta evidencia más sólida.
Paso 6: formular una hipótesis falsable
Hipótesis débil:
La base de datos está lenta.
Hipótesis falsable:
P99 aumentó porque las solicitudes de la versión 4.2 esperan una conexión de base de datos.
Predicción: las trazas lentas mostrarán más de 300 ms de adquisición y menos de 60 ms de ejecución; el problema se correlacionará con agotamiento del pool tras el nuevo límite de concurrencia.
La predicción define qué evidencia apoyaría o refutaría la afirmación.
Paso 7: mitigar en la capa correcta
Las mitigaciones posibles incluyen:
- reducir o limitar concurrencia;
- corregir una fuga del pool;
- acortar la duración de transacciones;
- optimizar la consulta dominante;
- eliminar una dependencia serial;
- reutilizar conexiones;
- precalcular o cachear dentro de restricciones de frescura;
- mover trabajo no esencial fuera del camino síncrono;
- aplicar un deadline downstream;
- desactivar retries injustificados;
- aplicar load shedding o devolver un resultado degradado controlado;
- revertir el cambio que introdujo la regresión.
La mitigación de menor riesgo durante un incidente puede ser distinta de la solución arquitectónica permanente.
Paso 8: verificar con la métrica original visible para el usuario
Una mitigación no queda validada porque un span local sea más corto. Confirma que:
- P95 y P99 end-to-end mejoraron para la población afectada;
- la tasa de error no aumentó más allá del trade-off aceptado;
- el throughput sigue siendo suficiente;
- profundidad de cola y volumen de retries se estabilizaron;
- el cuello de botella no se desplazó a otra dependencia;
- la mejora se mantiene con carga pico representativa;
- el impacto en recursos y coste sigue siendo aceptable.
Diagnóstico trabajado: CPU baja y latencia alta
Supón que la API muestra:
P50: 210 ms
P95: 920 ms
P99: 1,480 ms
Tasa de error: 0.2 %
CPU promedio: 42 %
La primera afirmación es: «La CPU está baja, por tanto no existe un problema de capacidad». Las trazas muestran otra cosa:
Adquisición del pool de base de datos P95: 510 ms
Ejecución de consulta P95: 58 ms
Pico de solicitudes pendientes del pool: 140
CPU de base de datos: 37 %
Lock waits de base de datos: bajos
La espera dominante ocurre antes de ejecutar la consulta. Aumentar el pool podría reducirla, pero solo si la base de datos y el presupuesto total de conexiones soportan más consultas concurrentes. Las siguientes comprobaciones son:
- capacidad total de pools sumando todas las instancias;
- duración de leases y posibles fugas;
- límites de transacción;
- cambio de concurrencia tras el último despliegue;
- máximo de conexiones y rango operativo seguro de la base de datos;
- throughput de consultas e I/O durante un incremento controlado.
Supón que el despliegue duplicó la concurrencia de aplicación y dejó el pool sin cambios. Un aumento controlado reduce la espera a 70 ms, eleva modestamente el P95 de consultas de 58 a 65 ms y baja el P95 end-to-end de 920 a 470 ms sin aumentar errores. Esa observación respalda la hipótesis.
Si el P95 de consultas subiera a 600 ms y los lock waits se dispararan, el aumento solo habría trasladado la cola a la base de datos. La respuesta correcta sería limitar concurrencia, reducir el tiempo de transacción o ampliar capacidad downstream; no seguir aumentando conexiones.
Errores de implementación frecuentes
1. Reportar únicamente el promedio
La media oculta la forma de la distribución y la cola. Reporta distribución, volumen y percentil relevante por operación y resultado.
2. Tratar P99 como una propiedad universal del servicio
P99 depende de población, ventana, carga, región, estado y estimador. Adjunta esas dimensiones al número.
3. Sumar spans para calcular tiempo end-to-end
Los spans hijos en paralelo se superponen. Usa duración de reloj e identifica el camino crítico.
4. Asumir que CPU baja significa ausencia de saturación
Colas, pools, locks, event loops, almacenamiento, límites downstream y particiones calientes pueden dominar con CPU promedio baja.
5. Aumentar todos los pools y límites de threads
Límites mayores pueden trasladar la espera a un recurso menos controlado y provocar contención o colapso.
6. Usar cache como parche genérico
Una cache puede reducir latencia y carga, pero introduce frescura, invalidación, consistencia, memoria y nuevos modos de fallo. Define qué datos pueden quedar obsoletos y cómo se comportan los misses.
7. Reintentar en todas las capas
Los retries en capas multiplican intentos y consumen el deadline. Asigna un propietario y un presupuesto explícito de intentos totales.
8. Configurar timeouts mayores que el deadline restante
La operación downstream continúa cuando la solicitud visible para el usuario ya está condenada. Propaga deadlines y cancela trabajo abandonado cuando sea posible.
9. Llamar cold start a todo retraso inicial
Separa inicialización de proceso, JIT, calentamiento de cache, creación de pools y efecto de primera consulta. Cada uno requiere evidencia y mitigación distintas.
10. Afirmar que el procesamiento asíncrono elimina latencia
Solo reduce la latencia de aceptación cuando el contrato permite finalización diferida. La latencia de finalización y el backlog siguen existiendo.
11. Alertar sobre percentiles volátiles sin contexto de volumen
Con muy pocas muestras, los percentiles altos pueden ser inestables. Combina alertas con umbrales de tráfico, ventanas mayores o métodos basados en SLO adecuados a la carga.
12. Optimizar un componente local sin validar el recorrido del usuario
Una consulta más rápida solo ayuda si modifica el camino crítico o crea margen necesario. Verifica el objetivo end-to-end original.
Checklist operativo
Definir
- Especificar el límite de medición visible para el usuario.
- Definir operación, región, resultado y población de tráfico.
- Establecer un objetivo end-to-end por percentil y una ventana de reporte.
- Reservar margen en lugar de asignar continuamente el 100 %.
- Definir qué trabajo debe terminar antes de responder.
Instrumentar
- Registrar duración como distribución, no solo como promedio.
- Separar latencia de éxitos y fallos.
- Capturar cola, pool, dependencia, timeout, retry y concurrencia.
- Propagar contexto de traza a través de todos los límites soportados.
- Controlar la cardinalidad de métricas y atributos de trazas.
- Diferenciar claramente los límites de medición de cliente y servidor.
Diagnosticar
- Segmentar por operación, región, versión, resultado y dependencia.
- Separar tiempo de procesamiento de tiempo de espera.
- Identificar el camino crítico en lugar de sumar todos los spans.
- Comparar trazas sanas y lentas con entradas similares.
- Formular una hipótesis falsable y la evidencia esperada.
Cambiar
- Confirmar capacidad downstream antes de aumentar concurrencia o pools.
- Derivar timeouts del deadline restante y del comportamiento de la dependencia.
- Asignar propietario de retries e intentos totales.
- Definir comportamiento parcial o degradado antes de aplicar load shedding.
- Documentar frescura e invalidación de caches.
- Medir aceptación y finalización en flujos asíncronos.
Validar
- Volver a medir P95 y P99 end-to-end sobre la población original.
- Verificar error rate, throughput, profundidad de cola, retries y saturación.
- Probar con tráfico pico y condiciones de fallo representativas.
- Confirmar que el cuello de botella no se desplazó.
- Mantener un dashboard de regresión y comparación por despliegue.
Preguntas frecuentes
¿Cuál es una buena latencia para una API?
No existe un número universal. El objetivo depende de la interacción, protocolo, geografía, grafo de dependencias, payload, necesidad de consistencia y consecuencia de esperar. Define un SLO visible para el usuario y distribuye un presupuesto para esa operación.
¿Qué diferencia existe entre P95 y P99?
P95 es el umbral en el que terminó el 95 % de las observaciones; P99 hace lo mismo para el 99 %. P99 se adentra más en la cola. Con tráfico alto, el 1 % más lento puede representar miles o millones de solicitudes.
¿Por qué la latencia puede subir mientras la CPU permanece baja?
El servicio puede estar esperando una cola, pool, lock, base de datos, red, almacenamiento, rate limit o dependencia externa. La CPU promedio también puede ocultar un core, shard, partición o subconjunto de instancias saturado.
¿Qué es tail latency?
Es el extremo lento de la distribución, normalmente observado mediante P95, P99, P99.9 o máximos. Importa porque el fan-out aumenta la probabilidad de que una operación encuentre al menos un componente lento.
¿Cómo se construye un presupuesto de latencia?
Define un objetivo end-to-end por percentil y su límite de medición, modela el camino crítico, asigna restricciones por componente con margen, propaga deadlines, instrumenta cada espera y valida la distribución agregada bajo tráfico realista. No asumas que los percentiles se suman aritméticamente.
¿Los sistemas asíncronos eliminan la latencia?
No. Pueden reducir el tiempo hasta el acknowledgement trasladando trabajo después de la respuesta, pero siguen existiendo latencia de finalización, colas, retries y recuperación de fallos.
¿P99 siempre es más útil que P95?
No. P99 es más sensible a la cola, pero puede ser más ruidoso con bajo volumen y más costoso de optimizar. Elige el percentil que represente el riesgo para usuario y negocio, y usa otros percentiles para comprender la distribución.
Conclusión
La latencia no es una propiedad que pueda asignarse a un servicio y optimizarse de forma aislada. Es la consecuencia temporal del recorrido completo: establecimiento de conexiones, colas, adquisición de recursos, procesamiento, llamadas downstream, fan-out, retries y entrega de la respuesta.
El método accionable es:
- definir un objetivo end-to-end por percentil y un límite de medición;
- preservar margen mediante un presupuesto de latencia;
- medir distribuciones en lugar de promedios;
- identificar el camino crítico de reloj;
- separar cómputo de espera;
- cambiar la capa propietaria del retraso dominante;
- validar el mismo percentil visible para el usuario bajo carga representativa.
Una plataforma con CPU disponible y pocos errores puede seguir siendo operacionalmente lenta. La evidencia que resuelve esa tensión no es otro dashboard agregado. Es un recorrido coherente desde el percentil afectado hasta la espera, dependencia, cola, conexión o política de reintentos que consumió el presupuesto.
Fuentes técnicas
- Google SRE — «Monitoring Distributed Systems»: sre.google/sre-book/monitoring-distributed-systems
- Jeffrey Dean y Luiz André Barroso — «The Tail at Scale»: research.google/pubs/the-tail-at-scale
- Google SRE — «Addressing Cascading Failures»: sre.google/sre-book/addressing-cascading-failures
- Google SRE — «Handling Overload»: sre.google/sre-book/handling-overload
- Marc Brooker, AWS Builders' Library — «Timeouts, retries, and backoff with jitter»: builder.aws.com — timeouts, retries, and backoff with jitter
- OpenTelemetry — «Semantic conventions for HTTP metrics»: opentelemetry.io/docs/specs/semconv/http/http-metrics
- OpenTelemetry — «Semantic conventions for HTTP spans»: opentelemetry.io/docs/specs/semconv/http/http-spans
- OpenTelemetry — «Semantic conventions for database client spans»: opentelemetry.io/docs/specs/semconv/db/database-spans
- W3C — «Trace Context»: w3.org/TR/trace-context
- Prometheus — «Histograms and summaries»: prometheus.io/docs/practices/histograms
- IETF — RFC 9293, «Transmission Control Protocol (TCP)»: rfc-editor.org/info/rfc9293
- IETF — RFC 9846, «The Transport Layer Security (TLS) Protocol Version 1.3»: rfc-editor.org/info/rfc9846