En los entornos distribuidos, la ejecución de tareas programadas está expuesta a fallos transitorios. db-scheduler para Java proporciona un marco robusto para gestionar estos escenarios mediante políticas de rientento y mecanismos de manejo de errores persistentes. A continuación, se detallan las configuraciones óptimas y los mecanismos internos para garantizar la resiliencia de los jobs.
Políticas de Reintento Nativas
La interfaz FailureHandler permite definir el comportamiento post-fallo de manera declarativa, adaptándose a distintos perfiles de error.
Retardo Constante (OnFailureRetryLater)
Ideal para errores de red momentáneos o bloqueos de base de datos de corta duración. Reintenat la ejecución tras un periodo inmutable.
// Reintento tras 90 segundos de espera
FailureHandler handler = new FailureHandler.OnFailureRetryLater(Duration.ofSeconds(90));
Retroceso Exponencial (ExponentialBackoffFailureHandler)
Mitiga la sobrecarga del sistema y evita el efecto "thundering herd" incrementando el tiempo de espera de forma progresiva tras cada fallo consecutivo.
// Espera inicial de 500ms, multiplicador de 1.8
FailureHandler backoff = new FailureHandler.ExponentialBackoffFailureHandler(
Duration.ofMillis(500), 1.8
);
Acotación de Reintentos (MaxRetriesFailureHandler)
Envuelve otra política para establecer un techo de ejecuciones. Al agotarse el límite, delega en un callback para ejecutar lógica de compensación o alertado.
FailureHandler limited = new FailureHandler.MaxRetriesFailureHandler(
5,
new FailureHandler.OnFailureRetryLater(Duration.ofSeconds(30)),
(execution, ops) -> {
// Re-programar para el día siguiente o enviar alerta
ops.reschedule(execution.taskInstance, execution.getExecutionTime().plus(Duration.ofDays(1)));
}
);
Re-programación basada en Plan (OnFailureReschedule)
Para tareas recurrentes, esta estrategia ignora el retardo arbitrario y alinea el próximo intento con el cronograma original (Cron o Schedule) de la tarea, manteniendo la cadencia esperada.
Configuración Global y por Tarea
Es posible establecer una red de seguridad global a nivel de Scheduler y sobreescribirla para jobs específicos que requieran tolerancias distintas.
Scheduler taskScheduler = SchedulerBuilder
.create(hikariDataSource)
.defaultFailureHandler(new FailureHandler.MaxRetriesFailureHandler(
4, new FailureHandler.ExponentialBackoffFailureHandler(Duration.ofSeconds(15))
))
.startTasks(
// Hereda la política global de retroceso exponencial
dailyCleanupTask,
// Sobreescribe con un retardo fijo para sincronizaciones críticas
criticalSyncTask.onFailure(new FailureHandler.OnFailureRetryLater(Duration.ofMinutes(2)))
)
.build();
Enrutamiento de Excepciones
No todos los errores merecen un reintento. Clasificar las excepciones dentro del método de ejecución optimiza los recursos del clúster y evita bucles infinitos en fallos deterministas.
@Override
public void execute(TaskInstance<Payload> taskInstance, ExecutionContext ctx) throws Exception {
try {
processPayload(taskInstance.getData());
} catch (java.net.SocketTimeoutException ex) {
// Fallo transitorio: el FailureHandler configurado gestionará el reintento
throw new TransientFailureException("Error de red temporal", ex);
} catch (IllegalArgumentException ex) {
// Fallo determinista: se lanza una excepción que indica que no debe reintentarse
throw new PermanentFailureException("Datos de entrada inválidos", ex);
}
}
Persistencia de Estado
Uno de los diferenciadores clave de esta librería es que el estado de los reintentos (incluyendo el conteo actual y el próximo timestamp de ejecución) se serializa directamente en la tabla de la base de datos. Si el nodo JVM cae durante un ciclo de retroceso exponencial, otro nodo del clúster retomará el reintento exactamente donde se quedó, sin perder el contexto de la política aplicada ni duplicar ejecuciones.