Solución de problemas de procesamiento lento de mensajes: identificación de cuellos de botella en RabbitMQ
Diagnostique las ralentizaciones de RabbitMQ separando los cuellos de botella del productor, broker, cola, consumidor, disco y confirmaciones.
Solución de problemas de procesamiento lento de mensajes: identificación de cuellos de botella en RabbitMQ
Cuando una cola de RabbitMQ se acumula, la cola solo te muestra el síntoma. El cuello de botella podría ser un consumidor lento, un publicador bloqueado, una alarma de disco, un valor de prefetch incorrecto, una carga útil de mensaje enorme o una base de datos aguas abajo que comenzó a agotar el tiempo de espera silenciosamente. Reiniciar RabbitMQ puede limpiar el gráfico por unos minutos, pero rara vez soluciona la razón por la que los mensajes eran lentos.
La ruta de solución de problemas más rápida es separar el flujo en partes: publicación en RabbitMQ, enrutamiento a colas, almacenamiento de mensajes, entrega a consumidores, procesamiento del trabajo y confirmación de finalización. Cada pieza deja diferentes evidencias.
Primera división: listos o no confirmados
Comience con los contadores de la cola:
rabbitmqctl list_queues name messages_ready messages_unacknowledged consumers state
messages_ready significa que los mensajes están en la cola esperando ser entregados. Si este número crece, RabbitMQ no tiene consumidores disponibles, los consumidores están en su límite de prefetch, o la entrega está siendo bloqueada por otra condición.
messages_unacknowledged significa que los mensajes ya han sido entregados a los consumidores y RabbitMQ está esperando un ack, nack, rechazo o cierre de canal. Si este número crece, el cuello de botella suele estar dentro del consumidor o en algo que el consumidor llama.
Esta distinción importa. Si los mensajes listos son altos y los no confirmados son bajos, agregar más memoria al broker no hará que aparezcan consumidores. Si los no confirmados son altos, agregar más particiones de cola puede no ayudar porque el trabajo ya ha salido de la cola.
Verifique si los consumidores están realmente presentes
Una cantidad sorprendente de incidentes de "RabbitMQ es lento" son en realidad incidentes de "los consumidores no se están ejecutando". La implementación falló, el escalado automático llegó a cero, las credenciales cambiaron, se usó el host virtual incorrecto o el servicio está conectado a staging mientras los productores publican en producción.
Use:
rabbitmqctl list_consumers queue_name channel_pid consumer_tag ack_required prefetch_count active
Si no hay consumidores, solucione eso primero. Si los consumidores están presentes pero inactivos, verifique los registros de la aplicación y el estado de la conexión. Si cada consumidor tiene un prefetch de 1 y cada mensaje toma varios segundos, la baja concurrencia de entrega puede ser esperada. Si cada consumidor tiene un prefetch de 500 y los no confirmados son enormes, los consumidores pueden estar acumulando trabajo que no pueden terminar rápidamente.
Mida el tiempo de procesamiento del consumidor
RabbitMQ puede decirle que los mensajes no están confirmados. No puede decirle si el consumidor está analizando una carga útil gigante, esperando en PostgreSQL, reintentando una llamada HTTP o atascado en un bloqueo.
Agregue temporización alrededor del manejador real:
message_received_at
decode_ms
business_logic_ms
database_ms
external_api_ms
ack_ms
message_completed_at
No necesita un sistema de trazado perfecto para aprender algo. Incluso unos pocos campos de registro estructurados pueden mostrar que el manejador normalmente toma 80 ms pero ahora pasa 4 segundos esperando una API aguas abajo.
Si el trabajo es lento pero paralelizable, agregue instancias de consumidor o aumente la concurrencia interna de trabajadores. Si el sistema aguas abajo es el límite, agregar consumidores puede empeorar las cosas. Es posible que necesite limitación de velocidad, procesamiento por lotes, almacenamiento en caché o una cola de reintentos separada.
Ajuste el prefetch después de entender el manejador
El prefetch controla cuántos mensajes no confirmados RabbitMQ puede enviar a cada consumidor. A menudo está involucrado en incidentes de procesamiento lento porque cambia dónde es visible el trabajo acumulado.
Con un prefetch bajo, los mensajes permanecen listos en RabbitMQ hasta que un consumidor esté listo para más. Esto es justo y fácil de observar, pero puede subutilizar consumidores muy rápidos.
Con un prefetch alto, los mensajes se mueven rápidamente a los consumidores. Esto puede mejorar el rendimiento para manejadores rápidos, pero también puede ocultar la latencia. Un consumidor lento con un valor de prefetch grande puede sentarse sobre cientos de mensajes mientras otros consumidores se quedan sin trabajo.
Un movimiento práctico en incidentes es reducir el prefetch para consumidores lentos o inestables y observar si la latencia de cola mejora. Para consumidores rápidos con bajo uso de CPU y altos recuentos de listos, aumente el prefetch con precaución y mida nuevamente.
Busque cuellos de botella del lado del publicador
A veces la cola no se acumula porque los consumidores son lentos. Se acumula porque los productores publican en ráfagas y luego esperan ineficientemente las confirmaciones.
Las confirmaciones del publicador son la herramienta correcta cuando los publicadores necesitan saber que RabbitMQ aceptó los mensajes. El patrón lento es esperar cada confirmación antes de publicar el siguiente mensaje. Eso convierte cada publicación en un viaje de ida y vuelta.
Los mejores patrones usan confirmaciones asíncronas o lotes limitados. El publicador puede mantener un número limitado de mensajes en vuelo, manejar nacks y aún así evitar bloquearse en cada mensaje individual. El límite importa. La publicación ilimitada en vuelo puede mover el cuello de botella a la memoria del publicador o la presión del broker.
Verifique las métricas del publicador: tasa de publicación, latencia de confirmación, recuento de confirmaciones en vuelo, reconexiones, mensajes devueltos y excepciones de canal. En la interfaz de administración, compare las tasas de publicación con las tasas de entrega/confirmación. Si la publicación es baja aunque la aplicación esté ocupada, el productor puede estar esperando confirmaciones, transacciones o cambios de conexión.
Evite las transacciones AMQP para publicaciones de alto rendimiento a menos que haya una razón específica. Son mucho más costosas que las confirmaciones del publicador para la publicación confiable típica.
Observe el disco antes de culpar a RabbitMQ
Los mensajes persistentes, las colas de quórum, los flujos y las acumulaciones grandes involucran el disco. Cuando la latencia del disco aumenta, el flujo de mensajes puede ralentizarse drásticamente.
En el nodo de RabbitMQ, verifique:
rabbitmq-diagnostics status
rabbitmq-diagnostics alarms
rabbitmq-diagnostics memory_breakdown
A nivel del sistema operativo, use herramientas como iostat, vmstat o sus gráficos de monitoreo en la nube. Observe la latencia del disco y la espera de E/S, no solo el rendimiento. Un disco en la nube que ha agotado los créditos de ráfaga puede verse normal en la configuración y terrible en la práctica.
Si el disco es el cuello de botella, las posibles soluciones incluyen almacenamiento más rápido, menos escrituras persistentes, mensajes más pequeños, mejor agrupación de confirmaciones del publicador, división de colas o mover cargas de trabajo de tipo reproducción a flujos u otro sistema orientado a registros. No deshabilite la persistencia para mensajes que la empresa no puede perder solo para que un gráfico se vea verde.
Verifique las alarmas y las conexiones bloqueadas
RabbitMQ se protege a sí mismo con alarmas de memoria y disco. Cuando una alarma está activa, los publicadores pueden ser bloqueados. Esto puede parecer lentitud de la aplicación desde el lado del productor.
Ejecute:
rabbitmq-diagnostics alarms
rabbitmqctl list_connections name user state channels send_pend recv_cnt send_cnt
Si las alarmas de memoria están activas, averigüe si la memoria está ocupada por colas, conexiones, mensajes no confirmados, binarios o complementos. Si las alarmas de disco están activas, libere espacio o agregue capacidad antes de intentar enviar más mensajes a través del broker.
Las conexiones bloqueadas no son un error en sí mismas. Son RabbitMQ diciéndoles a los publicadores que se ralenticen porque el nodo está protegiendo la disponibilidad.
El tamaño del mensaje puede ser el culpable silencioso
Un sistema que maneja 10,000 mensajes pequeños por segundo puede tener dificultades con 500 mensajes grandes por segundo. Las cargas útiles grandes aumentan la transferencia de red, la presión de memoria, las escrituras en disco, el trabajo de recolección de basura y el tiempo de procesamiento del consumidor.
Si los mensajes contienen documentos grandes, imágenes, informes o matrices grandes, considere almacenar la carga útil en almacenamiento de objetos o una base de datos y enviar una referencia a través de RabbitMQ. Incluya suficientes metadatos para el enrutamiento y la idempotencia, pero mantenga al broker fuera del rol de almacenamiento masivo cuando sea posible.
También verifique las opciones de compresión. Comprimir cargas útiles enormes puede reducir el uso de red y disco pero aumentar la CPU. Si eso ayuda depende de dónde esté el cuello de botella.
Los reintentos pueden crear el cuello de botella
Un servicio aguas abajo que falla puede convertir un mensaje en muchos intentos. Si los consumidores reencolan inmediatamente los fallos, pueden procesar los mismos mensajes incorrectos repetidamente mientras el trabajo nuevo espera. La profundidad de la cola puede aumentar, la CPU puede parecer ocupada y se realiza muy poco trabajo útil.
Busque altas tasas de reentrega y registros de error repetidos con los mismos ID de mensaje. Si la misma carga útil falla una y otra vez, muévala fuera del flujo principal. Un intercambio de cartas muertas, una cola de reintentos retrasados o un mecanismo de reintentos programados le da tiempo a la dependencia para recuperarse y evita que los mensajes venenosos bloqueen el trabajo normal.
Tenga cuidado con las tormentas de reintentos. Si una API está caída durante diez minutos y cada mensaje se reintenta cada segundo, la recuperación se vuelve más difícil cuando la API regresa. Use retroceso. Limite los intentos. Haga visible el fallo final en una cola de cartas muertas con suficiente contexto para investigar.
La idempotencia también es parte de la solución de problemas de rendimiento. Si un consumidor reintenta después de completar parcialmente el trabajo, los duplicados pueden crear contención en la base de datos, errores de clave única o llamadas adicionales aguas abajo. Un manejador que pueda procesar de manera segura el mismo mensaje dos veces es mucho más fácil de escalar y recuperar.
Las tasas de la interfaz de administración necesitan contexto
La interfaz de administración de RabbitMQ es útil, pero los gráficos de tasas pueden engañar si lee una sola línea. Una alta tasa de entrega con una baja tasa de confirmación significa que el trabajo se está entregando más rápido de lo que se completa. Una alta tasa de confirmación con un alto recuento de listos puede significar que los consumidores están trabajando pero no lo suficiente para ponerse al día. Una baja tasa de publicación durante un incidente puede significar que los productores están bloqueados o esperando confirmaciones.
Observe varias tasas juntas:
publish: mensajes que ingresan a los intercambios.deliver/get: mensajes enviados a los consumidores.ack: mensajes completados por los consumidores.redeliver: mensajes que se entregan nuevamente después de un fallo anterior o cierre de canal.
Para una cola de trabajo estable y saludable, las tasas de publicación y confirmación deben estar cerca a lo largo del tiempo. Los picos cortos son normales. Las brechas largas significan que el trabajo acumulado está aumentando o disminuyendo. Si las reentregas aumentan bruscamente, no solo agregue más consumidores. Averigüe por qué los mensajes están regresando.
Las ventanas de muestreo importan. Un gráfico de un minuto puede ocultar una parada de cinco segundos que perjudica a los usuarios. Un gráfico de un segundo puede hacer que la ráfaga normal parezca caos. Haga coincidir la ventana del gráfico con la latencia que les importa a sus usuarios o sistemas aguas abajo.
Separe el trabajo acumulado normal del trabajo acumulado roto
No todo el trabajo acumulado es una emergencia. Un sistema por lotes puede acumular trabajo intencionalmente durante el día y drenarlo por la noche. Un flujo de trabajo orientado al usuario puede no ser saludable si los mensajes esperan treinta segundos. La misma profundidad de cola puede ser aceptable en un sistema y grave en otro.
Defina una señal basada en la edad, no solo un recuento. El recuento de mensajes le dice cuántos están esperando; la edad del mensaje le dice si el negocio se está quedando atrás. Si su monitoreo puede rastrear la edad del mensaje más antiguo o el tiempo de extremo a extremo desde la publicación hasta la confirmación, detectará las ralentizaciones antes que la profundidad de la cola sola.
Vincule las alertas a esa expectativa. Alertar sobre 10,000 mensajes puede ser ruidoso para una cola de exportación nocturna y demasiado tarde para una cola de restablecimiento de contraseña. Alertar sobre "el mensaje más antiguo supera el objetivo de servicio" suele estar más cerca de lo que les importa a los usuarios.
Una cola caliente sigue siendo una cola caliente
Agregar nodos al clúster no divide automáticamente una cola entre todos los nodos. Una sola cola caliente puede permanecer limitada por su líder, sus consumidores y su ruta de almacenamiento.
Si una cola transporta tipos de trabajo no relacionados, divídala por el comportamiento de procesamiento real. Por ejemplo, el redimensionamiento de imágenes, el envío de correos electrónicos y la captura de facturación no deberían compartir una cola genérica de trabajos si tienen diferentes necesidades de latencia y reintentos. Las colas separadas le permiten escalar consumidores de forma independiente y aislar mensajes venenosos.
Si un tipo de trabajo sigue siendo demasiado caliente, fragmente solo cuando los requisitos de orden lo permitan. La fragmentación por ID de cliente, inquilino, región u otra clave estable puede funcionar, pero empuja la complejidad al enrutamiento y las operaciones. No fragmente solo para evitar arreglar un manejador lento.
Un orden tranquilo para la solución de problemas
En un incidente, uso este orden:
- Verifique las alarmas: memoria, disco y conexiones bloqueadas.
- Verifique los contadores de la cola: listos, no confirmados, consumidores.
- Verifique los registros del consumidor y la temporización del manejador.
- Verifique el prefetch y la distribución de no confirmados por consumidor.
- Verifique la latencia de confirmación del publicador y los mensajes devueltos.
- Verifique la latencia del disco y la presión de recursos del nodo.
- Verifique el tamaño del mensaje y los cambios recientes en la carga útil.
- Solo entonces cambie la topología o agregue nodos al broker.
Este orden evita un error común: escalar el broker cuando el cuello de botella es un trabajador, o escalar trabajadores cuando el cuello de botella es el disco.
RabbitMQ suele ser muy claro una vez que lee los contadores correctos. Un recuento creciente de listos dice que el trabajo está esperando. Un recuento creciente de no confirmados dice que el trabajo está en progreso pero no se está completando. Un publicador bloqueado dice que el broker se está protegiendo. Trate cada señal como una pista, y la solución se vuelve mucho menos dramática.