aws.crafter.run

Fiabilidad

DLQ y redrive

También llamado Dead-letter queue

Apartar los mensajes que fallan una y otra vez para que dejen de bloquear al consumidor y de quemar dinero, y poder investigarlos y reinyectarlos cuando el bug esté arreglado.

Servicios
DirectoVerificado el

El problema

Un mensaje entra en tu cola con un campo que tu código no espera. El consumidor lanza una excepción. El mensaje no se borra, así que al expirar el visibility timeout vuelve a la cola. Se vuelve a entregar. Vuelve a fallar.

Eso, solo, no es un desastre: es lo que hace fiable a una cola. El desastre es que no hay nada que detenga el bucle. Sin una DLQ configurada, ese mensaje se reintentará hasta agotar la retención de la cola: por defecto cuatro días, y hasta catorce si alguien subió el valor.

cola-cobrosmaxReceiveCount: 3cobrarPedidolanza excepcióncola-cobros-dlqretención más largarecibe · intentos 1, 2 y 3falla · reaparece al expirar el visibility timeout4ª recepciónEl mensaje entra en la DLQ conservando su timestamp de entrada original (colas estándar)Si pasó 1 día en la cola de origen, en una DLQ con 4 días de retención le quedan 3
La DLQ no es un buzón de errores: es el freno de un bucle. Sin ella, un mensaje que siempre falla se reintenta hasta agotar la retención — hasta 14 días de invocaciones que fallan y se facturan.

Mientras tanto pasan tres cosas, y ninguna sale en un dashboard hasta que la buscas:

Y al final el mensaje desaparece. A los cuatro días se borra por retención, sin avisar y sin dejar copia. El dato que necesitabas para entender el bug se ha ido.

La solución

Una segunda cola y un número. En la redrive policy de la cola de origen declaras dos cosas: cuál es la DLQ y cuántas recepciones aguanta un mensaje antes de ir a parar allí (maxReceiveCount).

Cuando la cuenta de recepciones supera ese número, SQS mueve el mensaje a la DLQ. El consumidor deja de verlo, el bucle se corta y el mensaje se conserva para que puedas mirarlo.

La segunda mitad del patrón —la que más gente se salta— es el redrive: una vez arreglado el bug, SQS puede mover los mensajes de vuelta de la DLQ a la cola original para que se reprocesen. Una DLQ sin un plan de redrive es un cementerio con buenas vistas.

Cuándo usarlo

Cuándo NO usarlo

Cómo implementarlo

  1. Crea la DLQ primero. Es una cola normal; lo que la convierte en DLQ es que otra la referencie.
  2. Del mismo tipo que la de origen y —esto lo exige AWS— en la misma cuenta y la misma región.
  3. Dale más retención que a la cola de origen. 14 días frente a los 4 por defecto. La trampa 1 explica por qué no es opcional.
  4. Fija maxReceiveCount entre 3 y 5. Suficiente para absorber fallos transitorios, poco para no quemar dinero.
  5. Restringe quién puede usarla con la redrive allow policy: byQueue admite hasta 10 ARN de colas de origen.
  6. Pon una alarma sobre ApproximateNumberOfMessagesVisible de la DLQ, con umbral 1. Cualquier mensaje ahí es una noticia.
  7. Escribe el procedimiento de redrive antes de necesitarlo.

El código

import * as cdk from 'aws-cdk-lib';
import * as sqs from 'aws-cdk-lib/aws-sqs';
import * as cw from 'aws-cdk-lib/aws-cloudwatch';

const dlq = new sqs.Queue(this, 'ColaCobrosDlq', {
  // Mas larga que la de origen: el mensaje llega con su timestamp
  // original, no con uno nuevo. Ver trampas.
  retentionPeriod: cdk.Duration.days(14),
});

const cola = new sqs.Queue(this, 'ColaCobros', {
  retentionPeriod: cdk.Duration.days(4),
  // Con margen sobre el timeout de la funcion: si el mensaje reaparece
  // mientras la Lambda sigue viva, la cuenta de recepciones sube por
  // una razon que no es un fallo.
  visibilityTimeout: cdk.Duration.minutes(6),
  deadLetterQueue: { queue: dlq, maxReceiveCount: 3 },
});

// Una DLQ sin alarma es una falsa sensacion de seguridad.
new cw.Alarm(this, 'AlarmaDlq', {
  metric: dlq.metricApproximateNumberOfMessagesVisible({
    period: cdk.Duration.minutes(5),
    statistic: 'Maximum',
  }),
  threshold: 1,
  evaluationPeriods: 1,
  comparisonOperator: cw.ComparisonOperator.GREATER_THAN_OR_EQUAL_TO_THRESHOLD,
  treatMissingData: cw.TreatMissingData.NOT_BREACHING,
});

Te va a morder

Coste

Una DLQ vacía no cuesta prácticamente nada: las colas no se facturan por existir ni por almacenar, solo por peticiones, y a una DLQ ociosa nadie le manda peticiones salvo tu alarma de CloudWatch.

Lo caro es no tenerla. La cuenta de un mensaje venenoso sin freno, con la retención por defecto de 4 días y un visibility timeout de 30 segundos, son del orden de once mil reintentos — cada uno con su invocación de Lambda y sus peticiones de SQS. Por un solo mensaje. Multiplícalo por el número de mensajes que trae un despliegue con un bug de deserialización.

Fuentes

Política de redrive, maxReceiveCount, redrive allow policy, requisito de misma cuenta y región, comportamiento del timestamp de retención en colas estándar y FIFO, y el reordenado al final de la cola: Using dead-letter queues in Amazon SQS. Retención por defecto y máxima de una cola: Amazon SQS message quotas. Política de reintentos de SNS a destinos gestionados: Amazon SNS message delivery retries.

Patrones relacionados

Un enlace sin la relación nombrada es un "ver también". Aquí cada uno dice qué relación tiene y por qué.