BookinglyTech News
Infraestructura

Registrar los fallos de un cron worker sin perder el rastro de cada reintento

Tratar cada ejecución programada como una fila de evidencia en Postgres, con identificadores separados para trabajo, intento y operación, en lugar de un last_error que se sobrescribe en cada fallo.

3 min de lecturaDev.to0 vistas

Cada ejecución de un worker programado debería guardarse como un registro de evidencia duradero, no como una línea de log. En la práctica eso significa separar tres identificadores —el trabajo lógico, cada intento individual y la operación de cliente que lo originó— y escribir cada transición de estado en Postgres con un resumen de error saneado más un puntero a la telemetría detallada. Los reintentos añaden filas; no pisan las anteriores.

Por qué una columna last_error no basta

Un único registro mutable del trabajo se rompe en cuanto hay un segundo intento: actualizar el campo destruye el anterior. Los logs tampoco sirven por sí solos, porque la retención cambia de un sistema a otro, los relojes no coinciden y hay mensajes que se emiten antes de que la transacción que reclama el trabajo haya hecho commit.

El texto repasa los modos de fallo que justifican el modelo. Si el worker muere a mitad de proceso, la ausencia de un log de éxito no es prueba de fallo: hay que registrar el lease y las marcas de tiempo, y clasificar el intento solo cuando se conoce el resultado. Si dos workers reclaman el mismo trabajo, contar líneas de log como ejecuciones lleva a confundir duplicados con reintentos, así que hacen falta identificadores de intento distintos y metadatos del claim. Si la captura de errores no está disponible, conviene persistir un resumen local acotado y exportar el detalle por separado en vez de meter la telemetría en la misma transacción del job. Y si el cliente pide el borrado de sus datos, no se pueden conservar snapshots opacos indefinidamente: la evidencia operativa se separa del payload y se le aplica una retención explícita.

Sobre el saneado, la referencia es la guía de logging de OWASP, que advierte contra registrar tokens de acceso, contraseñas, datos personales sensibles o cadenas de conexión a base de datos. Un secreto volcado en la tabla de intentos deja de ser un secreto y pasa a ser un pasivo.

Qué lleva cada fila

El registro de intento debería incluir marcas de tiempo, número de intento, resultado, clase de error, un mensaje acotado y una clave de correlación hacia la telemetría detallada. Con eso se responde a la pregunta que importa durante un incidente: qué se ejecutó para este tenant, qué falló, qué se reintentó y en qué quedó todo.

El borrado entra justo aquí. El artículo 17 del RGPD define el derecho de supresión bajo determinados supuestos y también lista excepciones, pero no fija un periodo de retención universal que un equipo de ingeniería pueda deducir por su cuenta. Lo que sí exige es que el diseño tenga clasificación de datos, retención y comportamiento de borrado que legal y seguridad puedan evaluar, con los campos que identifican a una persona documentados y las peticiones de supresión llegando a cada almacén.

Queda una frontera que conviene hacer explícita: BullMQ, Agenda o un cron apoyado en Postgres pueden ocupar el lado del dispatch de esta arquitectura, pero el contrato de evidencia de incidentes no debería depender del objeto de error de una librería concreta. Se normaliza un registro común y pequeño en el límite del worker, y el detalle específico se conserva solo en la telemetría enlazada y cuando la política lo permita. Así, cambiar de scheduler no obliga a reescribir el procedimiento de incidentes de la empresa.

El ejemplo de código que acompaña al artículo es Python aunque el worker real corra en Node.js, y lo relevante no es el lenguaje sino la forma de la transacción: crear el intento inmutable, ejecutar el handler y cerrar ese mismo intento con éxito o con un fallo saneado. El enlace entre job lógico, intento y operación de cliente es lo que convierte un montón de excepciones en algo que se puede auditar.