Colas de mensajes rechazados: manejo de mensajes tóxicos en sistemas distribuidos

Evita que los mensajes tóxicos bloqueen las colas

Índice

Una cola de cartas muertas (dead-letter queue) es la red de seguridad que captura los mensajes que tus consumidores no pueden procesar, de modo que un único payload defectuoso no bloquee ni omita silenciosamente todo lo que viene detrás en la cola.

Cada sistema impulsado por mensajes eventualmente recibe un mensaje que no puede manejar: un payload malformado, un esquema que cambió por debajo del consumidor o una llamada al servicio downstream que falla sin importar cuántas veces se reintente. Sin una cola de cartas muertas, ese mensaje o bien bloquea la cabecera de la cola para siempre o se descarta silenciosamente, y ambos resultados son peores que saber sobre el fallo.

Una cola de cartas muertas convierte un fallo invisible en uno visible e inspeccionable. Te proporciona un lugar para cuarentenar el mensaje, alertar sobre él y decidir — deliberadamente, no por accidente — si repararlo y volver a enviarlo o descartarlo definitivamente.

dead letter queue routing failed messages away from the main queue

Los mecanismos difieren entre los distintos brokers, pero el patrón subyacente es el mismo en todas partes: un contador de intentos de entrega, un umbral y un destino para los mensajes que lo superan. Esta guía cubre qué hace realmente una cola de cartas muertas, cómo distinguir un mensaje venenoso de un fallo transitorio, cuándo reintentar versus descartar y cómo volver a enviar con seguridad una vez que has corregido la causa raíz. Para un contexto más amplio sobre los patrones de integración en los que se sitúa este patrón, consulta Arquitectura de la Aplicación.

¿Qué es una Cola de Cartas Muertas?

Una cola de cartas muertas es una cola ordinaria y separada a la que un broker o consumidor enruta un mensaje después de que este haya fallado en su procesamiento demasiadas veces. No es una construcción especial: la cola de cartas muertas de RabbitMQ es una cola regular vinculada a un intercambio regular, y una cola de cartas muertas de SQS es una cola estándar o FIFO regular. Lo que convierte a una cola en una “cola de cartas muertas” es puramente el hecho de que algo más dirige los mensajes fallidos hacia ella.

flowchart LR P[Productor] --> Q[Cola Principal] Q --> C[Consumidor] C -- ack: éxito --> Eliminado[Mensaje eliminado] C -- fail / nack / timeout --> Q Q -- presupuesto de reintento agotado --> DLQ[Cola de Cartas Muertas] DLQ --> I[Inspeccionar / alertar] I -- corregir causa raíz --> R[Reenviar a la cola principal] I -- irrecuperable --> D[Archivar / descartar]

Cada broker implementa la redirección de manera diferente:

  • Amazon SQS utiliza una política de redriver con un maxReceiveCount. Una vez que un mensaje ha sido recibido ese número de veces sin ser eliminado, SQS lo mueve al deadLetterTargetArn configurado. AWS recomienda explícitamente mantener el período de retención de mensajes de la cola de cartas muertas más largo que el de la cola de origen, porque la marca de tiempo original de inserción — no la hora del movimiento — sigue gobernando la caducidad.
  • RabbitMQ convierte en carta muerta un mensaje cuando se rechaza con requeue=false, expira su TTL por mensaje, la cola alcanza un límite de longitud o una cola de quorum supera su delivery-limit. Configuras esto con los argumentos de cola x-dead-letter-exchange (y opcionalmente x-dead-letter-routing-key), y RabbitMQ adjunta encabezados x-death que registran la razón, la cola de origen y cuántas veces ocurrió.
  • Apache Kafka no tiene una cola de cartas muertas nativa del broker. Kafka solo rastrea los offsets; no tiene concepto de un mensaje “fallido”. El patrón de tema de carta muerta es algo que construyes en el consumidor, en una topología de Kafka Streams o en un conector de Kafka Connect — comúnmente emparejado con un nivel de temas de reintento antes del DLT terminal, como lo hacen @RetryableTopic y DeadLetterPublishingRecoverer de Spring Kafka.
  • Azure Service Bus convierte en carta muerta automáticamente una vez que el recuento de entregas de un mensaje supera MaxDeliveryCount (10 por defecto), y también por un puñado de razones del sistema como TTLExpiredException, HeaderSizeExceeded y MaxTransferHopCountExceeded, cada una registrada en la propiedad DeadLetterReason del mensaje.

Para una visión más amplia de cómo los brokers y las plataformas de streaming se encajan operativamente más que como un patrón de fiabilidad, Inicio rápido de Apache Kafka y RabbitMQ en AWS EKS vs SQS cubren el lado de la infraestructura al ejecutar estos brokers.

Mensajes Venenosos

Un mensaje venenoso es aquel que nunca tendrá éxito sin importar cuántas veces un consumidor lo reintente: un payload JSON malformado, un campo de esquema que un productor renombró, una violación de una regla de negocio o un error de programación que lanza una excepción ante un input específico cada vez. Esto es diferente de un fallo transitorio, donde el mensaje está bien pero el entorno brevemente no lo está: un tiempo de espera en el servicio downstream, un microcorte en la conexión a la base de datos, una respuesta de límite de tasa.

Tratar ambos tipos de fallo de la misma manera es el error más común con las colas de cartas muertas. Si conviertes en carta muerta en el primer fallo, castigas errores transitorios que habrían tenido éxito en el reintento. Si reintentas mensajes venenosos docenas de veces antes de rendirte, desperdicias capacidad de cálculo, retrasas mensajes no relacionados detrás de ellos (en colas ordenadas y particiones) y saturas tus registros con la misma traza de pila.

Algunas señales de detección ayudan a separar los dos casos:

  • Tipo de excepción. Los errores de deserialización, errores de validación y fallos de tipo ClassCastException son casi siempre permanentes. El DefaultErrorHandler de Spring Kafka trata explícitamente ciertas excepciones como fatales y omite los reintentos para ellas en lugar de agotar primero el presupuesto de reintento.
  • Conteo repetido sin variación. El array del encabezado x-death de RabbitMQ te permite ver exactamente cuántas veces se ha convertido un mensaje en carta muerta y por qué; un mensaje con un contador creciente y un x-first-death-reason idéntico en cada ciclo es venenoso, no desafortunado. Fallo consistente entre réplicas. Si cada instancia de consumidor falla con el mismo mensaje mientras tiene éxito con todo lo que lo rodea, el problema es el propio mensaje, no la infraestructura.

Para distinguir a nivel de código entre fallos reintentables y no reintentables — la misma clasificación en la que depende la política de una cola de cartas muertas — consulta Arquitectura de Manejo de Errores en Go: Límites y Patrones.

Reintento vs Descarte

La decisión de política central detrás de cada cola de cartas muertas es el umbral de reintento: cuántos intentos de entrega recibe un mensaje antes de ser cuarentenado. Si lo pones demasiado bajo, conviertes en carta muerta mensajes que habrían tenido éxito después de un breve percance en el servicio downstream. Si lo pones demasiado alto, un mensaje venenoso permanece en la cola principal durante mucho tiempo, consumiendo capacidad del trabajador y — en sistemas ordenados — bloqueando todo lo encolado detrás de él.

Las directrices actuales en los principales brokers convergen en números similares:

Broker Mecanismo Umbral típico
Amazon SQS maxReceiveCount en política de redriver 3–5 para cargas de trabajo mixtas transitorias/permanentes
RabbitMQ (colas de quórum) Argumento de política delivery-limit 3–5, ajustado por cola
Azure Service Bus MaxDeliveryCount Por defecto 10, a menudo reducido para colas sensibles a la latencia
Kafka (vía temas de reintento) Encabezado de conteo de reintento + nivel de tema de reintento 3–4 saltos a temas de reintento antes del DLT terminal

Un punto medio práctico en el que muchas equipos terminan es: empezar con conservador (2–3 intentos), observar la mezcla real de fallos en producción y elevar el umbral solo para las colas donde puedas demostrar que la mayoría de los fallos se resuelven en pocos reintentos. Empareja el conteo de reintento con retroalimentación exponencial y jitter entre intentos para que una caída del servicio downstream no se convierta en una tormenta de reintento — la misma disciplina cubierta en el diseño de retroalimentación y circuitos de ruptura. Un circuito de ruptura en el límite de integración complementa esto: deja de enviar solicitudes a una dependencia no saludable en lugar de permitir que cada mensaje en la cola descubra individualmente la caída y se convierta en carta muerta uno por uno.

Una vez que un mensaje está en la cola de cartas muertas, el “descarte” debe seguir siendo una acción deliberada, no una negligencia. Establece un período de retención en la propia cola de cartas muertas — lo suficientemente largo para investigar (AWS recomienda que la retención de la cola de cartas muertas exceda la de la cola de origen; una semana es un piso común para las colas de cartas muertas de RabbitMQ) — y alerta sobre la profundidad y edad de la cola de cartas muertas para que los fallos sean triajeados en lugar de expirar silenciosamente. Un mensaje que caduca de la cola de cartas muertas sin examinar es un mensaje al que decidiste perder sin haber decidido perderlo.

La idempotencia importa tanto aquí como en cualquier otro lugar: los duplicados pueden ocurrir; un mensaje que se vuelve a conducir desde una cola de cartas muertas a la cola principal es, funcionalmente, una entrega duplicada. Si tu consumidor no es seguro para ejecutarse dos veces con el mismo mensaje, volver a conducir desde una cola de cartas muertas puede crear exactamente el error de efecto secundario duplicado que estabas intentando evitar. Consulta Idempotencia en Sistemas Distribuidos que Realmente Funcionan para los patrones del lado del consumidor que hacen que la reconducción sea segura.

Estrategias de Reenvío

Sacar un mensaje de la cola de cartas muertas correctamente es su propia disciplina, separada de meterlo.

  1. Corrige la causa raíz primero. Implementar la corrección del consumidor antes de reenviar es la diferencia entre una recuperación limpia y volver a envenenar la cola con el mismo fallo una segunda vez.
  2. Vuelve a conducir deliberadamente, no automáticamente. SQS admite una función de reconducción a origen que mueve mensajes de vuelta a su cola original (u otro destino) bajo demanda; RabbitMQ y Kafka requieren que construyas tú mismo el consumidor o las herramientas equivalentes. En cualquier caso, trata el reenvío como una acción activada por el operador con un registro de lo que se reenvió y cuándo.
  3. Preserva el orden donde importa. Para Kafka, el tema de cartas muertas debe tener al menos tantas particiones como el tema de origen y debe retener la clave del mensaje original, de modo que los mensajes reenviados caigan de nuevo en la partición correcta y preserven el orden por clave.
  4. Limita los intentos de reenvío. Un mensaje que falla de nuevo después de un ciclo de corrección y reenvío no es transitorio; enrútalo a un archivo permanente (una tabla de base de datos, un cubo de almacenamiento de objetos) en lugar de hacer que dé vueltas indefinidamente por la cola de cartas muertas. Los propios documentos de RabbitMQ advierten que un mensaje convertido en carta muerta puede ser enrutado entre colas solo un número limitado de veces (16) antes de que el reenvío basado en TTL se deshabilite.
  5. Nunca permitas que una cola de cartas muertas se convierta en carta muerta a sí misma. Si tu cola de cartas muertas tiene su propio x-dead-letter-exchange (RabbitMQ) o su propia política de redriver (SQS) apuntando de vuelta a la misma cadena, un fallo de reenvío puede crear un bucle infinito. Mantén la configuración de carta muerta de la cola vacía o apúntala a un archivo estrictamente terminal.
  6. Alerta sobre el volumen, no solo la presencia. Un solo mensaje en una cola de cartas muertas es un punto de datos; un aumento repentino es un incidente. Conecta la profundidad y la edad de los mensajes de la cola de cartas muertas al mismo pipeline de alertas que usas para todo lo demás — consulta Diseño de Sistemas de Alerta Modernos para Equipos de Observabilidad para prácticas de enrutamiento y reducción de ruido que se aplican directamente a las alertas de colas de cartas muertas.

Si tu flujo de trabajo implica procesos largos y multi-paso en lugar de mensajes individuales, el mismo pensamiento de cola de cartas muertas se aplica en la capa de flujo de trabajo — la lógica de compensación de una saga necesita la misma disciplina de “cuarentena, inspeccionar, decidir” cuando un paso falla permanentemente en lugar de hacerlo de forma transitoria. Y cuando los propios eventos se originan a partir de una escritura de base de datos, el patrón de caja de salida transaccional ya construye el manejo de cartas muertas en el trabajador de relé, por lo que el patrón aparece una capa anterior al broker.

Dónde se Encajan las Colas de Cartas Muertas en el Cuadro General

Una cola de cartas muertas no hace que los fallos desaparezcan; hace que sean sobrevivibles y revisables en lugar de silenciosos. Funciona mejor junto con reintentos con retroalimentación para el caso transitorio, consumidores idempotentes para que la reconducción sea segura y un circuito de ruptura para que una dependencia luchadora no sature la cola principal (y, eventualmente, la cola de cartas muertas) con el mismo fallo miles de veces. Trata el umbral, la retención y las alertas de la cola de cartas muertas como decisiones de configuración de primera clase, no como valores predeterminados que dejas intactos, y las cartas muertas se convierten en una herramienta de diagnóstico en lugar de un lugar donde los datos desaparecen silenciosamente.

Enlaces Útiles

Suscribirse

Recibe nuevas publicaciones sobre sistemas, infraestructura e ingeniería de IA.