Cuando desarrollas con Node.js, el manejo de errores parece casi demasiado fácil al principio. Envuelves una ruta en un try-catch, envías un código de estado 500 y el cliente decide qué hacer a continuación. Ese modelo funciona bien para HTTP, pero se desmorona en el momento en que pasas a los trabajos en segundo plano (background jobs). En un sistema de colas, no hay un cliente esperando. Solo hay un worker, un payload y un contador de reintentos aumentando en algún lugar de Redis, RabbitMQ o SQS. Si manejas los fallos de la misma manera que manejas las peticiones web fallidas, no solo perderás una transacción. Detendrás todo tu pipeline, agotarás recursos de cómputo o harás que tus workers fallen repetidamente con el mismo mensaje envenenado (poisoned message).
La mentalidad HTTP se rompe en los trabajos en segundo plano
En un ciclo de solicitud-respuesta, el bucle de retroalimentación es inmediato. Un usuario hace clic en un botón, el servidor lanza un error y el usuario ve una pantalla de fallo. La limpieza es sencilla. Un worker de cola vive de forma aislada. Toma un trabajo, trabaja en él durante segundos o minutos y luego confirma el éxito (acknowledgment). Si algo sale mal en el medio, la cola no tiene idea de por qué. Solo sabe que la confirmación nunca llegó. Dependiendo de tu configuración, reintentará, tal vez para siempre. Un solo payload malformado puede rebotar entre workers cientos de veces, desperdiciando CPU y ocultándose detrás de trabajos legítimos que realmente necesitan ser procesados.
Dos tipos de fallos
La primera regla de las colas resilientes es dejar de tratar cada error de la misma manera. Necesitas clasificar los fallos en dos grupos en el instante en que ocurren.
Los fallos reintentables (retryable failures) son transitorios. Piensa en tiempos de espera de red (timeouts), límites de tasa (rate limits) de una API de terceros o una conexión a la base de datos que se restableció porque el pool se agotó brevemente. Estos son síntomas de un sistema vivo bajo presión. Podrían tener éxito en el próximo intento dentro de dos minutos.
Los fallos permanentes (permanent failures) son píldoras venenosas (poison pills). Estos incluyen payloads malformados, errores de validación de esquema o un campo requerido que falta porque un servicio ascendente (upstream) cambió su contrato. Reintentar estos es un desperdicio puro. Fallarán de la misma manera en el centésimo intento.
Si tu bloque catch no puede distinguir entre estos dos, tu cola está volando a ciegas.
Patrón 1: Clasifica los errores en el bloque catch
El bloque catch de tu worker debería ser el código más deliberado del archivo. Cuando surge un error, inspecciónalo de inmediato. ¿El código de error es ECONNRESET o un timeout? Ponlo en cola para reintentar. ¿Es un SyntaxError, un rechazo de validación de Joi o una restricción de clave foránea faltante? Muévelo directamente a una cola de mensajes no entregados, o DLQ (dead-letter queue), y no lo cuentes contra tu límite de reintentos.
La mayoría de las librerías de colas de Node.js, incluyendo BullMQ y Bee Queue, te permiten definir estrategias de backoff personalizadas y hooks de error. Úsalos. Un error permanente nunca debería dormir y reintentar tres veces por defecto. Debería ser evacuado de la cola principal para que el resto de tus trabajos puedan fluir. La DLQ preserva el payload exacto y el contexto del error, lo que te permite volver a ejecutar el trabajo más tarde, después de que hayas parcheado el error o corregido el esquema.
Patrón 2: Backoff exponencial con jitter
Reintentar instantáneamente es agresivo. Si una base de datos downstream ya está jadeando bajo la carga, golpearla de nuevo cada dos segundos desde cincuenta workers acabará con ella. Necesitas retroceder y darle espacio al sistema para recuperarse.
Usa exponential backoff. En el primer fallo, espera un segundo. En el segundo, espera dos. Luego cuatro, luego ocho, hasta un límite razonable como cinco minutos. Pero el tiempo por sí solo no es suficiente. Si cada trabajo fallido utiliza exactamente el mismo intervalo, todos colisionarán cuando el backoff expire. Esa onda sincronizada, a veces llamada thundering herd (manada atronadora), puede abrumar a un servicio en recuperación.
Añade jitter. Toma tu retraso calculado y desfásalo con un porcentaje aleatorio, tal vez del diez al veinte por ciento. Cuatro segundos se convierten en 4.2 o 4.7. Esta simple aleatoriedad dispersa el pico de reintentos y evita que tu infraestructura reciba golpes en forma de olas.
Patrón 3: Diseña para la idempotencia
Aquí es donde la infraestructura de colas se encuentra con la lógica de negocio. Imagina un trabajo que cobra a un cliente a través de un proveedor de pagos. El worker publica el cargo con éxito, pero la conexión se cae antes de que pueda registrar el éxito en tu base de datos o confirmar el trabajo. La cola ve un fallo. Reintenta. El cliente es cobrado dos veces.
En Node.js, evita esto haciendo que cada efecto secundario sea idempotente. Genera una clave de idempotencia a partir del ID del trabajo o de un identificador específico del negocio. Antes de crear el cargo, enviar el correo electrónico o ajustar el inventario, comprueba si el trabajo ya se realizó. Pasa esa clave a través de todo el proceso hasta tu base de datos y cualquier API de terceros que la acepte. Estructura tus trabajos de modo que ejecutar la misma carga útil diez veces produzca el mismo resultado que ejecutarla una sola vez. Este único hábito elimina toda una categoría de errores financieros y de integridad de datos.
Patrón 4: Trata tu Dead-Letter Queue como un dashboard
Una DLQ no es un cementerio donde los trabajos fallidos van a ser olvidados. Es una herramienta operativa y debería ser una de tus superficies más vigiladas.
Configura alertas que se activen cuando la profundidad de la DLQ aumente. Incluso un solo mensaje en la DLQ suele significar que tu lógica de validación está rota, que un esquema upstream ha cambiado o que un servicio downstream está enviando basura que ya no reconoces. Estas son exactamente las señales que quieres detectar antes de que los clientes empiecen a quejarse. Construye un dashboard que te permita inspeccionar el payload bruto, el stack trace y el timestamp. Ten preparado un runbook: inspecciona el fallo, aplica el parche al código y luego vuelve a enviar los mensajes en el orden correcto. Si tu implementación de colas lo permite, alerta tanto por la tasa de crecimiento como por el recuento absoluto, ya que un despliegue erróneo puede inundar la DLQ en cuestión de minutos.
Patrón 5: En caso de duda, haz que el proceso falle
Node.js se ejecuta en un único event loop dentro de un isolate de V8. Un rechazo de promesa no manejado o un
