@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.
100 lines (99 loc) • 3.25 kB
JavaScript
import log4js from "log4js";
import { TimeUtils } from "./time-utils.js";
import { HashMap, Nil, option } from "scats";
import { MetricsService } from "application-metrics";
const logger = log4js.getLogger("TasksAuxiliaryWorker");
export class TasksAuxiliaryWorker {
tasksQueueDao;
manageTasksQueueService;
workerTimer = null;
metricsTimer = null;
queuesCounts = HashMap.empty;
constructor(tasksQueueDao, manageTasksQueueService) {
this.tasksQueueDao = tasksQueueDao;
this.manageTasksQueueService = manageTasksQueueService;
}
start() {
const runWorker = () => {
this.runAuxiliaryJobs();
};
this.workerTimer = setInterval(() => {
try {
runWorker();
}
catch (e) {
logger.warn("Failed to process stalled tasks", e);
}
}, TimeUtils.second * 30);
try {
runWorker();
}
catch (e) {
logger.warn("Failed to process stalled tasks", e);
}
const runMetrics = () => {
this.fetchMetrics();
};
this.metricsTimer = setInterval(() => {
try {
runMetrics();
}
catch (e) {
logger.warn("Failed to sync metrics", e);
}
}, TimeUtils.minute * 2);
try {
runMetrics();
}
catch (e) {
logger.warn("Failed to sync metrics", e);
}
}
runAuxiliaryJobs() {
try {
this.tasksQueueDao
.failStalled()
.then((res) => {
if (res.nonEmpty) {
logger.info(`Marked stalled as failed: ${res.mkString(", ")}`);
}
})
.catch((e) => {
logger.warn("Failed to process stalled tasks", e);
});
this.tasksQueueDao.resetFailed().catch((e) => {
logger.warn("Failed to reset failed tasks", e);
});
this.tasksQueueDao.clearFinished().catch((e) => {
logger.warn("Failed to clear finished tasks", e);
});
}
catch (e) {
logger.warn("Failed to process stalled tasks", e);
}
}
fetchMetrics() {
this.manageTasksQueueService
.tasksCount()
.then((tasksCounts) => {
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);
});
}
async stop() {
option(this.workerTimer).foreach((t) => clearTimeout(t));
option(this.metricsTimer).foreach((t) => clearTimeout(t));
}
}