One self-hosted console to run your entire business — commerce, ERP, HRM, CRM & manufacturing

Observabilidad para sistemas de alto volumen: logs, métricas y traces con los que sí puedes actuar

Un request ID, logs estructurados en OpenSearch, cuatro señales doradas, traces con muestreo y alertas ligadas al impacto en el usuario: el conjunto pequeño que te deja encontrar la capa lenta en minutos, y cómo vigilar una prueba de carga y el día del evento.

Author

Anichur Rahaman

hace 6 días12 min read2 views
Observabilidad para sistemas de alto volumen: logs, métricas y traces con los que sí puedes actuar

«Los clientes dicen que el pago se queda colgado». El mensaje de soporte llega a las 10:01 de la mañana del lanzamiento al responsable de la plataforma de un sitio de boletos. La venta abrió a las 10:00, unas mil personas hicieron clic en el mismo minuto y el plan hablaba de 440 solicitudes por segundo. El tablero muestra la CPU al 40 % y nada en rojo.

¿Qué capa va lenta? ¿Una ruta, un nodo, un cliente? Con una consola y una corazonada, los siguientes diez minutos se van en discutir. Con un request ID y unas cuantas cifras bien elegidas, se van en una sola búsqueda. (Esta escena es ilustrativa, no un incidente real.)

Para eso no hace falta una plataforma enorme. Hace falta un request ID, logs estructurados, las cuatro señales doradas y alertas ligadas al impacto en el usuario. En una plataforma que preparé para un pico programado, con unos mil usuarios actuando en el mismo minuto, los servidores estaban bien; lo que casi nos hizo daño fue que nadie podía decir rápido qué capa iba lenta, ni si un número alarmante era real. Este artículo arma el conjunto pequeño que responde esas preguntas y luego muestra cómo vigilar con él una prueba de carga y el día del evento.

Esta es la parte 4 de una serie de cinco, «Ingeniería para alto volumen». La parte 1 trata de lo que de verdad escala, la parte 2 de colas y workers y la parte 3 de la capa de datos. Esta parte trata de ver todo eso.

La observabilidad en un párrafo

El monitoreo te dice que algo anda mal. La observabilidad te permite preguntar por qué, sin tener que publicar código nuevo primero. Se apoya en tres tipos de datos, llamados señales:

  • Logs: un registro por evento, con detalle. Lo mejor para preguntar «¿qué le pasó exactamente a esta solicitud?»
  • Métricas: números muestreados a lo largo del tiempo. Lo mejor para preguntar «¿va empeorando, y qué tan rápido?»
  • Traces: el recorrido de una solicitud por todos los servicios, con el tiempo que pasó en cada paso. Lo mejor para preguntar «¿dónde se fue el tiempo?»

No necesitas las tres desde el primer día, ni una plataforma costosa. Necesitas que estén conectadas, para saltar de una métrica en rojo a los logs y al trace de una solicitud lenta. El pegamento es el request ID.

Empieza con un request ID que sobreviva cada salto

La mejora más barata que conozco es un identificador que acompaña a la solicitud desde el borde hasta el último trabajo de la cola. En la configuración que operé, nginx respeta el encabezado X-Request-Id entrante o genera uno, se lo pasa a PHP-FPM y ambos lo escriben en sus logs. Después la aplicación lo agrega a cada línea de log y lo copia en la carga de cualquier trabajo que despacha, de modo que una tarea en segundo plano se pueda ligar al clic que la originó.

Devuelve el mismo ID en el encabezado de la respuesta. Cuando un cliente o un agente de soporte reporta un problema, esa sola cadena encuentra toda la historia.

Diagrama de una solicitud seguida por su request ID desde el CDN, el balanceador, nginx, PHP-FPM, la aplicación y un trabajo de cola, en una sola búsqueda en OpenSearch
Un solo request ID, escrito por cada capa, convierte cinco archivos de log separados en una línea de tiempo.

Dónde debe aparecer el ID

CapaQué hacer
Borde / CDNReenviar el ID entrante, o dejar que la siguiente capa lo cree
nginx del host y nginx del contenedorReutilizar el ID entrante, generar uno si falta, escribirlo en el access log
PHP-FPM y la appLeerlo del encabezado y adjuntarlo a cada línea de log como contexto compartido
Trabajos de colaGuardarlo en la carga del trabajo y restaurarlo al ejecutarse
Llamadas HTTP salientesEnviarlo hacia adelante para que socios y servicios internos también lo registren
RespuestaDevolverlo como encabezado para que soporte pueda pedirlo

Logs estructurados hacia un stack ELK u OpenSearch

Los logs de texto plano se escriben para que una persona los lea de uno en uno. Con alto volumen necesitas buscarlos, contarlos y filtrarlos, lo que exige un objeto JSON por línea con campos con nombre. Para empezar basta un conjunto corto y estable de campos.

CampoPor qué se gana su lugar
timeOrdenar y correlacionar con las métricas
request_idEl hilo que une todas las capas
route o path (sin query string)Agrupar las solicitudes lentas por endpoint y no por URL cruda
statusTasa de errores por endpoint
durationLa materia prima de p95 y p99
upstream timeSepara «nginx fue lento» de «PHP fue lento»
user o tenant id (un ID interno, nunca un correo)Saber si el problema es de un solo cliente

Sobre los nombres: ELK es el apodo antiguo de tres herramientas, Elasticsearch, Logstash y Kibana, y el Elastic Stack más amplio sumó unos recolectores ligeros llamados Beats. OpenSearch nació en 2021 como un fork con licencia Apache de Elasticsearch y Kibana, impulsado por AWS, y en septiembre de 2024 pasó a la OpenSearch Software Foundation, bajo la Linux Foundation. Ya no son idénticos, pero para logs el flujo es el mismo: enviar, indexar, buscar, graficar. Por eso la gente dice «compatible con ELK».

No necesitas un clúster grande. En mi configuración, un nodo pequeño de OpenSearch con 2 GB de memoria y un vCPU guardó los logs de una plataforma que atendía un pico brusco. Es una sola configuración, no un benchmark, pero muestra que el costo es pequeño frente al primer incidente que diagnosticas en minutos en lugar de horas.

Qué no registrar

Los logs se copian, se indexan y se guardan durante meses, así que son un riesgo para la protección de datos. Oculta contraseñas, tokens, datos de tarjetas y datos personales completos antes de que una línea salga de la aplicación. Registra IDs internos, no nombres, correos ni teléfonos. Quita el query string de las rutas, porque a menudo lleva tokens.

Retención y ciclo de vida de los índices

Rotar los índices por día, con un ciclo de vida automático, mantiene sano un nodo pequeño: los días recientes siguen disponibles para búsqueda, los índices viejos se comprimen o se mueven, y lo que pasa de tu ventana de retención se borra. Define la ventana con quienes se encargan de privacidad y seguridad, no a ojo. Treinta días de logs detallados es un punto de partida común; la respuesta correcta depende de tus normas.

Métricas: las cuatro señales doradas

El libro de Google «Site Reliability Engineering», en su capítulo sobre monitoreo de sistemas distribuidos, nombra cuatro señales que cubren casi cualquier servicio de cara al usuario. Si solo puedes medir cuatro cosas, mide estas.

SeñalPregunta que respondeEn una plataforma web con workers
Latencia¿Cuánto tardan las solicitudes?p50, p95 y p99 por ruta; las solicitudes fallidas se miden aparte
Tráfico¿Cuánta demanda hay?Solicitudes por segundo en el borde, trabajos despachados por segundo
Errores¿Qué fracción falla?Tasa de 5xx, timeouts, trabajos fallidos
Saturación¿Qué tan lleno está el recurso más ajustado?Workers de FPM ocupados, profundidad y antigüedad de la cola, conexiones a la base de datos, memoria de Redis

Dos advertencias. Primera: nunca configures alertas ni planees con promedios, porque un promedio sano esconde una cola terrible. En una prueba de carga que hice sobre un frontend con renderizado en servidor, un solo proceso se saturó en unas 220 a 250 solicitudes por segundo, y el p95 saltó de unos 260 ms a unos 3 segundos mientras el host aún tenía núcleos libres. El promedio habría parecido aceptable por un rato. El percentil dijo la verdad.

Segunda: los problemas empiezan en la saturación, y rara vez es la CPU. En una plataforma como esta reviso primero:

  • Workers de PHP-FPM ocupados frente al máximo del pool. Cuando toca el límite, las solicitudes hacen fila en nginx.
  • Profundidad y antigüedad de cada cola. La profundidad dice cuánto espera; la antigüedad dice qué tan tarde vas ya.
  • Conexiones a la base de datos y rendimiento de escritura, no solo la CPU.
  • Memoria de Redis y evicciones, por rol (caché, sesiones, colas).
  • Memoria por contenedor, porque un proceso terminado por falta de memoria se ve desde fuera como un error aleatorio.

Tracing con OpenTelemetry

Los logs y las métricas te dicen que una solicitud fue lenta. Un trace te dice que pasó 40 ms en nginx, 90 ms en PHP, 1.2 segundos esperando una consulta y 30 ms en Redis. En un sistema con capa de frontend, capa de backend y workers, es la forma más rápida de encontrar el salto culpable.

OpenTelemetry es el estándar neutral respecto a proveedores para producir estos datos. Según la página de estado de su especificación, las señales de tracing y de logs son estables, y la API, el protocolo y el modelo de datos de las métricas también, mientras que la madurez de los SDK de cada lenguaje varía. El proyecto de PHP documenta traces, métricas y logs como disponibles, con una extensión opcional para instrumentación automática. Revisa el estado de las bibliotecas concretas que vayas a usar antes de comprometerte.

Mi consejo es adoptarlo en este orden. Primero asegúrate de que exista el request ID. Luego traza solo la ruta crítica, como el pago o el envío, y aplica muestreo: guardar todos los traces con alto volumen cuesta más de lo que enseña. Conserva todos los traces de errores y de solicitudes lentas, y una fracción pequeña del resto. Pon el trace ID en tus líneas de log junto al request ID, para que las dos herramientas se enlacen entre sí.

SLOs y alertas que signifiquen algo

Una alerta debe responder una sola pregunta: ¿alguien necesita actuar ahora? La CPU al 85 % rara vez lo exige. «El pago falla para el 2 % de los usuarios» siempre. Un objetivo de nivel de servicio (SLO) lo vuelve concreto: una meta como «el 99 % de las solicitudes de pago tiene éxito en menos de 2 segundos en 30 días», con un presupuesto de errores para el resto.

Se despierta a una persona porBasta un ticket o un mensaje de chat por
Tasa de errores del pago por encima del umbral de consumo del SLOUn disco que se llena despacio
p95 de una ruta crítica sobre la meta por varios minutosUn nodo con CPU alta y usuarios sin afectación
Antigüedad creciente en la cola de envíosUn trabajo que falló, reintentó y pasó
Trabajos fallidos en aumento rápidoUn certificado que vence en tres semanas

Un ejemplo con números lo aclara. Supón que el pago recibe 3,000,000 de solicitudes en 30 días y el objetivo es 99 % de éxito, así que el presupuesto de errores es de 30,000 solicitudes fallidas o demasiado lentas. Durante un pico de diez minutos a 440 solicitudes por segundo atiendes 264,000. Si el 2 % falla, son 5,280 fallos: cerca del 18 % del presupuesto de todo el mes se va en diez minutos. Eso merece una llamada, diga lo que diga la CPU. (Cifras ilustrativas.)

Cada alerta que despierta a alguien sin motivo enseña al equipo a ignorar la siguiente. Revisa las alertas después de cada evento y elimina o degrada las ruidosas.

El tablero del día del evento

Para un pico programado armo una sola pantalla. Arriba, las señales doradas de las rutas críticas; debajo, los números de saturación; al lado, la profundidad y la antigüedad de las colas, y una línea con la tasa de solicitudes objetivo. Nada más. Un tablero que obliga a desplazarse no se lee en el minuto pico.

Boceto de un tablero para el día del evento con paneles de latencia, tráfico, errores y saturación, más profundidad y antigüedad de colas, conexiones a la base de datos y memoria de Redis
Una pantalla para el minuto pico: primero las cuatro señales doradas, luego los recursos que se agotan.

Pon una marca de despliegue en cada gráfica. La mitad de las «regresiones misteriosas» son un lanzamiento que llegó diez minutos antes.

Cómo observar una prueba de carga

Una prueba de carga es el ensayo más barato que tendrás, y se desperdicia si no puedes ver lo que pasa dentro. Reproduce la mezcla real de solicitudes de un pico anterior, en lugar de golpear una sola URL. En mis pruebas usé k6 con umbrales de aborto: detenerse si el p95 pasa de 3 segundos, o ante cualquier 5xx o timeout. Un pequeño script guardián frenaba la corrida si se tocaban los límites de CPU, para que una prueba nunca se convirtiera en una caída.

  1. Define los umbrales de aborto antes de la primera corrida y escríbelos.
  2. Ejecuta el tablero que usarás el día del evento, no una vista especial de prueba.
  3. Marca el tráfico de prueba con un encabezado para poder filtrarlo en los logs.
  4. Compara tus métricas con las de tu proveedor de nube. En mis corridas coincidieron con diferencias de pocos puntos porcentuales; si las tuyas no coinciden, una de las dos está mal.
  5. Encuentra el primer recurso saturado, corrígelo y vuelve a correr con la tasa objetivo.
  6. Guarda los resultados junto al código, para que el siguiente evento parta de una línea base.

Verifica la alerta alarmante antes de actuar

Bajo presión es tentador actuar sobre todo lo que esté en rojo. Verifica primero. Algunas consolas etiquetan mal los contenedores, y una alerta de «100 % de CPU» puede apuntar a un proceso distinto del que estás por reiniciar. Mira la lista real de procesos, contrasta con una segunda fuente y luego decide.

Diagrama de flujo: salta una alerta; si ninguna señal de cara al usuario la respalda, verifica el proceso real y una segunda fuente; si la respalda, revierte cuando hubo un despliegue en los últimos 15 minutos, y si no, encuentra el primer recurso saturado, corrígelo y revisa el tablero
Adónde va una alerta roja después: la mayor parte del trabajo es decidir si es real.

Lo mismo vale para tu propio trabajo en segundo plano. Los health checks y los programadores también consumen recursos; en un caso, un health check que lanzaba un proceso en cada ejecución dejó procesos huérfanos bajo carga y desestabilizó un contenedor de base de datos. Medir el propio monitoreo no es paranoia.

Volvamos a las 10:01. Con todo esto en su lugar, el mensaje de soporte llega con un request ID adjunto. Una búsqueda muestra a nginx esperando 1.31 segundos a PHP y a la app registrando una consulta lenta en la misma solicitud. El tablero coincide: el p95 sube mientras 48 de 60 workers de FPM están ocupados. La única alerta roja de CPU pertenece a otro contenedor, así que nadie reinicia nada. El responsable tiene la causa en unos dos minutos en lugar de diez, y el arreglo va al lugar correcto. (También ilustrativo.)

En la última parte de esta serie cubro la otra mitad de mantenerse en pie: proteger el borde y publicar cambios sin tiempo de inactividad.

Ideas clave

  • Agrega un request ID en el borde y llévalo por nginx, PHP-FPM, los logs de la app y los trabajos de cola. Es la ganancia grande más barata.
  • Escribe logs JSON estructurados con un conjunto corto y estable de campos, oculta los datos personales y fija la retención a propósito.
  • Mide las cuatro señales doradas, usa percentiles en lugar de promedios y vigila la saturación: workers, antigüedad de la cola, conexiones, memoria.
  • Adopta el tracing con OpenTelemetry solo en la ruta crítica, con muestreo, y enlaza los trace ID con tus logs.
  • Despierta a la gente solo por impacto en el usuario ligado a un SLO, y poda las alertas ruidosas después de cada evento.
  • Ensaya con umbrales de aborto, compara con las métricas de tu proveedor y verifica una alerta alarmante antes de actuar.

Anichur Rahaman es arquitecto de software y creador de StoreConsole. Diseña sistemas de comercio y ERP para negocios en crecimiento, con enfoque en arquitectura orientada a eventos, integridad de datos y operación en servidores propios.

About the Author

Anichur Rahaman

Continue Reading