Volver al inicio

Diagnósticos de latencia HTTP: desde Nginx hasta el Kernel

El artículo describe un enfoque sistemático para diagnosticar la latencia en el pipeline de procesamiento de solicitudes HTTP. Cubre métodos para localizar problemas en el lado del balanceador de carga, la aplicación y la ruta de red usando logs de Nginx, cabeceras Server-Timing y métricas TCP_INFO del kernel de Linux.

Encontrar causas de latencia en el pipeline HTTP en 5 minutos
Advertisement 728x90

# # Diagnóstico de latencia en solicitudes HTTP: Del balanceador de carga al kernel de Linux

Los tiempos de respuesta crecientes o errores 5xx repentinos en producción exigen una localización instantánea y precisa del problema. En lugar de revisar servicios de forma caótica, es más efectivo usar un enfoque sistemático que divida todo el pipeline de procesamiento de solicitudes en segmentos de red y aplicación. Desglosaremos un método para encontrar rápidamente los cuellos de botella usando registros estándar de Nginx, cabeceras Server-Timing y métricas de bajo nivel TCP_INFO.

Punto de partida: Análisis de registros del balanceador de carga

Una arquitectura típica de aplicaciones web incluye varias capas: usuario, CDN, balanceador de carga (SLB), aplicación, pool de conexiones y base de datos. Cada una puede introducir latencia, pero el balanceador de carga debe ser siempre tu punto de partida para diagnósticos: está en el centro del pipeline y te permite determinar instantáneamente la dirección de búsqueda.

Para Nginx, las variables clave son $request_time y $upstream_response_time. La primera registra el tiempo completo de procesamiento de la solicitud desde la recepción del primer byte del cliente hasta el envío del último byte de la respuesta. La segunda mide solo el tiempo de espera de una respuesta del servidor upstream. Si aún no se recopilan estas métricas, amplía tu formato de registro:

Google AdInline article slot
log_format timing '$remote_addr - $request_uri '
                  'status=$status '
                  'rt=$request_time '
                  'uct=$upstream_connect_time '
                  'urt=$upstream_response_time';

access_log /var/log/nginx/access.log timing;

La interpretación de datos se basa en una comparación simple. Si $upstream_response_time es cercano a $request_time, el cuello de botella está a la derecha del balanceador de carga: en el código de la app, pool de conexiones o base de datos. Si $upstream_response_time es normal pero $request_time es anormalmente alto, el problema está a la izquierda: en la infraestructura de red, enrutamiento de CDN o del lado del cliente. Ten en cuenta que inyectar $request_time en cabeceras de respuesta mediante add_header no se recomienda para mediciones precisas, ya que Nginx forma las cabeceras antes de completar la transmisión del cuerpo de la respuesta. Los datos confiables solo están disponibles en el registro de acceso después del cierre de la conexión.

Localización precisa de problemas en el lado de la aplicación

Cuando el balanceador de carga indica retrasos upstream, necesitas profundizar en las operaciones internas de la app. El estándar W3C Server-Timing te permite enviar tiempos precisos para etapas individuales de procesamiento de solicitudes al cliente o sistema de monitoreo. La cabecera se ve así: Server-Timing: app;dur=120, db;dur=95, pool-wait;dur=18. Esto proporciona transparencia sin desplegar agentes APM pesados.

Integrar la cabecera en tu pila de apps requiere cambios mínimos. Ejemplos de implementación para lenguajes populares:

Google AdInline article slot

Go (net/http):

start := time.Now()
rows, err := db.QueryContext(ctx, query)
dbDur := time.Since(start)

w.Header().Set("Server-Timing",
    fmt.Sprintf("db;dur=%.2f", float64(dbDur.Microseconds())/1000))

Python (Django middleware):

class ServerTimingMiddleware:
    def __init__(self, get_response):
        self.get_response = get_response

    def __call__(self, request):
        start = time.monotonic()
        response = self.get_response(request)
        dur = (time.monotonic() - start) * 1000
        response["Server-Timing"] = f"app;dur={dur:.2f}"
        return response

Node.js (Express):

Google AdInline article slot
app.use((req, res, next) => {
  const start = process.hrtime.bigint();
  res.on('finish', () => {
    const ms = Number(process.hrtime.bigint() - start) / 1e6;
    // already sent, but for logging:
    console.log(`Server-Timing: app;dur=${ms.toFixed(2)}`);
  });
  // for header — time up to response:
  const origEnd = res.end;
  res.end = function(...args) {
    const ms = Number(process.hrtime.bigint() - start) / 1e6;
    res.setHeader('Server-Timing', `app;dur=${ms.toFixed(2)}`);
    origEnd.apply(this, args);
  };
  next();
});

Para reenviar las métricas propias de Nginx vía Server-Timing, usa la directiva map para evitar enviar valores vacíos en archivos estáticos o errores:

Nginx (upstream timing as Server-Timing):

      # In http {} block of nginx.conf:
      map $upstream_header_time $server_timing_upstream {
      "-"     "";
       default "ngx-upstream;dur=$upstream_header_time;desc=\"NgxUpstream\"";
      }

      # In location {} block of your server:
      add_header Server-Timing-Ngx "ngx-total;dur=$request_time;desc=\"NgxTotal\"" always;
      add_header Server-Timing-Ngx $server_timing_upstream always;

Nota el uso del prefijo Server-Timing-Ngx y la variable $upstream_header_time. Los navegadores esperan valores en milisegundos, mientras que Nginx devuelve segundos. Además, $upstream_header_time captura el momento en que se reciben las cabeceras del upstream, reflejando con precisión el tiempo de generación de la respuesta, a diferencia de $upstream_response_time, que puede anularse a cero en ciertas condiciones de bufferizado.

Analizar los datos te permite clasificar rápidamente el incidente:

  • Un db;dur dominante apunta a consultas SQL subóptimas, índices faltantes o bloqueos de tablas.
  • Un pool-wait;dur alto señala agotamiento del límite de conexiones en PgBouncer o pooler similar.
  • Un app;dur grande con db;dur pequeño indica operaciones ligadas a CPU, E/S bloqueante o fugas de memoria en tiempo de ejecución.
  • Errores 502/504 en el balanceador de carga con tiempos normales suelen significar caídas de procesos de app o timeouts del orquestador.

Segmento de red: Fases HTTP y métricas del kernel

Si upstream responde rápidamente pero la latencia general aumenta, el problema está en la ruta de red. Cada solicitud HTTP pasa por seis fases secuenciales: DNS, Connect (handshake TCP), handshake TLS, Send, Wait (TTFB) y Receive. Anomalías en fases específicas acotan la búsqueda de inmediato. Un DNS largo apunta a problemas en el resolvedor o cadenas CNAME complejas. Valores altos en Connect y TLS indican latencia de red al nodo edge de CDN, falta de reanudación de sesión TLS o protocolos obsoletos. Si Connect y TLS son mínimos pero Wait es anormalmente alto, el retraso está en el WAF, workers de CDN o nivel de fetch del origen.

Para diagnósticos profundos de red, las desgloses HTTP estándar no bastan. Necesitas métricas del kernel de Linux vía getsockopt(TCP_INFO). El kernel rastrea cada conexión TCP y proporciona datos precisos: RTT, conteo de retransmisiones, tamaño de ventana de congestión (cwnd) y RTO. Estos son críticos porque los tiempos HTTP no pueden separar la latencia de red del tiempo de procesamiento del servidor cuando la respuesta cabe en un segmento TCP. Restar el RTT del kernel de la fase Wait da el tiempo real de procesamiento de la solicitud. Retransmisiones y cwnd estrujado apuntan directamente a pérdida de paquetes y algoritmos de control de congestión que añaden cientos de milisegundos por solicitud. Combinar cabeceras de app con métricas de red de bajo nivel crea observabilidad completa sin depender de proveedores externos.

Lecciones clave

  • Comparar $request_time y $upstream_response_time en Nginx separa instantáneamente responsabilidades de red y app.
  • Las cabeceras Server-Timing proporcionan visibilidad granular en retrasos internos (DB, pools, runtime) sin sistemas APM pesados.
  • Analizar fases de solicitudes HTTP identifica con precisión problemas de DNS, handshake TLS y enrutamiento CDN.
  • Las métricas del kernel TCP_INFO dan una vista objetiva de pérdidas y retrasos de red indetectables a nivel de app.
  • Un enfoque sistemático de diagnósticos reduce el MTTR y elimina reinicios caóticos de servicios durante incidentes.

— Editorial Team

Advertisement 728x90

Leer después