1. Asume que un mensaje puede volver

Un worker termina una escritura y pierde la conexión antes de confirmar el mensaje. La cola no puede saber si el efecto ocurrió y lo entrega de nuevo. Este caso es suficiente para romper un consumidor que incrementa un contador sin identificar el evento. Las colas Standard de SQS documentan entrega al menos una vez; el consumidor debe tolerar duplicados.

Piensa en idempotencia como una propiedad del efecto: repetir la misma intención deja el estado de negocio como después de una sola ejecución. No equivale a ignorar cualquier solicitud parecida. Necesitas una identidad estable para la misma operación y una política ante la misma clave con contenido diferente.

Documentación: AWS: entrega al menos una vez en SQS

2. Asigna identidad antes de publicar

El productor debe generar y conservar event_id con la operación de negocio. Reintentar la publicación conserva ese identificador. El consumidor usa una clave que incluya su nombre o versión de efecto y el tenant verificado del evento; así dos consumidores distintos pueden procesar legítimamente el mismo evento.

Autentica el canal y valida el esquema antes de confiar en esos campos. Guarda una huella del contenido relevante para detectar una reutilización incorrecta de clave. Decide cuánto dura el registro de deduplicación según la ventana real de reintentos, replays y retención; borrarlo demasiado pronto permite que un replay vuelva a aplicar el efecto.

3. Acopla deduplicación y efecto local

Cuando el efecto está en la misma base de datos, una restricción única y una transacción permiten acoplar la marca de procesamiento con la escritura. Insertar la marca, confirmar y después aplicar el efecto abre una ventana donde una caída pierde trabajo. Hacer primero el efecto fuera de la transacción abre la ventana inversa: duplicarlo.

SQL
CREATE TABLE processed_events (
  tenant_id uuid NOT NULL,
  consumer text NOT NULL,
  event_id uuid NOT NULL,
  processed_at timestamptz NOT NULL DEFAULT now(),
  PRIMARY KEY (tenant_id, consumer, event_id)
);
Esquema mínimo ilustrativo. En una implementación completa conserva también la huella del payload y tu política de retención.
Pseudocódigo
begin transaction
insert dedup key on conflict do nothing returning event_id
if inserted:
  apply business change in this same database transaction
else:
  verify stored payload fingerprint matches this event
commit
ack message
Si cualquier operación falla, rollback y no confirmar el mensaje. La marca nueva y el efecto deben confirmarse juntos.

ON CONFLICT ofrece el mecanismo para resolver una colisión de unicidad. Tu código todavía debe distinguir una colisión válida de un mensaje inconsistente y comprobar que se cumplió la precondición del negocio. Una escritura que no afectó la fila esperada puede requerir error y rollback, no éxito silencioso.

Documentación: PostgreSQL: INSERT y ON CONFLICT

4. Separa publicación y efectos externos

La transacción anterior no incluye una API externa. Si envías un correo o llamas a otro servicio antes del commit, un rollback local no revierte ese efecto. Si lo haces después, una caída puede impedir el envío. Diseña ese límite con una outbox transaccional y, cuando exista, una clave de idempotencia aceptada por el destino.

La outbox guarda la intención de publicar junto con el cambio de negocio en la misma transacción. Un relay la envía y marca progreso. Ese relay también puede reenviar si cae entre publicar y registrar éxito; el consumidor sigue necesitando deduplicación. La outbox resuelve la pérdida entre base y broker, no convierte todos los sistemas en una transacción única.

Documentación: AWS: patrón transactional outbox

5. Reintenta con presupuesto y dispersión

Clasifica fallos: un timeout o una indisponibilidad temporal pueden recuperarse; un esquema inválido no mejorará repitiendo. Define número de intentos, tiempo total y demora máxima. Añade jitter para que muchos workers no vuelvan a golpear un servicio al mismo instante. Respeta las indicaciones de espera del destino cuando correspondan.

TypeScript
function retryDelayMs(attempt: number) {
  const base = 500;
  const cap = 30_000;
  const ceiling = Math.min(cap, base * 2 ** Math.min(attempt, 16));
  return Math.floor(Math.random() * ceiling);
}
Ejemplo de full jitter; base y límite son valores ilustrativos. El llamador debe limitar intentos y duración total.

Ajusta la visibilidad o el mecanismo de lease al tiempo de procesamiento y extiéndelo si el proveedor lo permite. Si expira mientras trabajas, otro consumidor puede recibir el mismo mensaje. No uses una demora larga como sustituto de idempotencia.

6. Diseña una DLQ que puedas operar

Una cola de mensajes fallidos necesita dueño, alerta y procedimiento de revisión. Conserva causa, intentos y versión del consumidor sin volcar secretos en los logs. Reproduce el fallo en el entorno autorizado, corrige la causa y reenvía un lote pequeño con la identidad original antes de una recuperación masiva.

Punto de caídaResultado esperado
Antes del commitSe revierte efecto y marca; un reintento puede procesar.
Después del commit, antes del ackEl reintento detecta la clave y no repite el efecto.
Payload inválidoSe aísla con una causa operable, sin bucle infinito.
Destino externo ambiguoSe consulta o reutiliza la identidad del destino; no se inventa éxito.

Mide antigüedad del mensaje más viejo, errores, backlog y tiempo hasta recuperación. El precio por millón ayuda a estimar gasto, pero no expresa cuánto tardará tu equipo en reparar un evento atascado. Esa capacidad operativa forma parte de elegir una cola.

Fuentes y alcance

Documentación consultada el 8 de septiembre de 2026. Los ejemplos y criterios de decisión son propuestas editoriales; adáptalos al contrato de tu aplicación y valídalos en tu entorno de pruebas autorizado.

Del diseño a la decisión

Compara colas de mensajes

Revisa tarifas, límites, condiciones y fuentes de cada opción.

Abrir comparador