@penkov/tasks_queue
Version:
A lightweight PostgreSQL-backed task queue system with scheduling, retries, backoff strategies, and priority handling. Designed for efficiency and observability in modern Node.js applications.
83 lines (82 loc) • 3.08 kB
JavaScript
import log4js from "log4js";
import { TimeUtils } from "./time-utils.js";
import { HashMap, Nil, option } from "scats";
import { MetricsService } from "application-metrics";
import { SystemClock } from "./clock.js";
const logger = log4js.getLogger("TasksAuxiliaryWorker");
export class TasksAuxiliaryWorker {
tasksQueueDao;
manageTasksQueueService;
clock;
workerTimer = null;
metricsTimer = null;
queuesCounts = HashMap.empty;
constructor(tasksQueueDao, manageTasksQueueService, clock = new SystemClock()) {
this.tasksQueueDao = tasksQueueDao;
this.manageTasksQueueService = manageTasksQueueService;
this.clock = clock;
}
start() {
this.workerTimer = setInterval(() => {
void this.runMaintenanceOnce();
}, TimeUtils.second * 30);
void this.runMaintenanceOnce();
this.metricsTimer = setInterval(() => {
void this.syncMetricsOnce();
}, TimeUtils.minute * 2);
void this.syncMetricsOnce();
}
async runMaintenanceOnce() {
try {
const failStalledPromise = this.tasksQueueDao
.failStalled(this.clock.now())
.then((res) => {
if (res.nonEmpty) {
logger.info(`Processed stalled tasks: ${res.mkString(", ")}`);
}
})
.catch((e) => {
logger.warn("Failed to process stalled tasks", e);
});
const resetFailedPromise = this.tasksQueueDao.resetFailed().catch((e) => {
logger.warn("Failed to reset failed tasks", e);
});
const clearFinishedPromise = this.tasksQueueDao
.clearFinished(undefined, this.clock.now())
.catch((e) => {
logger.warn("Failed to clear finished tasks", e);
});
await Promise.all([
failStalledPromise,
resetFailedPromise,
clearFinishedPromise,
]);
}
catch (e) {
logger.warn("Failed to process stalled tasks", e);
}
}
async syncMetricsOnce() {
try {
const tasksCounts = await this.manageTasksQueueService.tasksCount();
this.queuesCounts = tasksCounts.groupBy((c) => c.queueName);
tasksCounts.foreach((c) => {
MetricsService.gauge(`tasks_queue_${c.queueName}_${c.status}`.replace(/[^a-zA-Z0-9_:]/g, "_"), () => {
return this.queuesCounts
.get(c.queueName)
.getOrElseValue(Nil)
.find((x) => c.status === x.status)
.map((c) => c.count)
.getOrElseValue(0);
});
});
}
catch (e) {
logger.warn("Failed to sync metrics", e);
}
}
stop() {
option(this.workerTimer).foreach((t) => clearInterval(t));
option(this.metricsTimer).foreach((t) => clearInterval(t));
}
}