redis-smq
Version:
A high-performance, reliable, and scalable message queue for Node.js.
163 lines • 7.03 kB
JavaScript
import { BackgroundJobWorkerAbstract } from '../../../abstract/background-job/background-job-worker-abstract.js';
import { async, PanicError } from 'redis-smq-common';
import { PurgeQueueJobManager } from './purge-queue-job-manager.js';
import { EBackgroundJobStatus, } from '../../../index.js';
import { _deleteMessage } from '../../../../message-manager/_/_delete-message.js';
import { BackgroundJobCanceledError } from '../../../../errors/index.js';
import { MessageBrowserFactory } from '../../../../queue-messages/message-browser-factory.js';
export class PurgeQueueJob extends BackgroundJobWorkerAbstract {
jobManager = null;
processNextJob(cb) {
async.waterfall([
(cb) => {
this.getJobManager().acquireNextJob((err, jobId) => {
if (err)
return cb(err);
if (!jobId) {
this.logger.debug(`No job available.`);
return cb(null);
}
this.logger.debug(`Worker ${this.id} acquired job: ${jobId}`);
cb(null, jobId);
});
},
(jobId, cb) => {
this.executeJob(jobId, (err) => {
if (err) {
this.logger.error(`Job ${jobId} failed:`, err);
}
else {
this.logger.info(`Job ${jobId} completed successfully`);
}
cb(err);
});
},
], cb);
}
executeJob(jobId, cb) {
const jobManager = this.getJobManager();
async.waterfall([
(next) => {
jobManager.start(jobId, this.id, next);
},
(job, next) => {
this.purgeMessages(job, next);
},
(totalPurged, next) => {
jobManager.complete(jobId, { meta: { purged: totalPurged } }, (err) => next(err));
},
], (err) => {
if (err) {
return jobManager.fail(jobId, err.message, (err) => {
if (!err) {
this.logger.debug(`Successfully marked job ${jobId} as failed`);
}
cb();
});
}
cb();
});
}
goingUp() {
return super.goingUp().concat([
(cb) => {
const redisClient = this.getRedisClient();
this.jobManager = new PurgeQueueJobManager(redisClient, this.logger);
this.jobManager.initialize(cb);
},
]);
}
getJobManager() {
if (!this.jobManager)
throw new PanicError({
message: 'A PurgeQueueJobManager instance is required.',
});
return this.jobManager;
}
purgeMessages(job, cb) {
const parsedParams = job.payload.queue;
const delay = job.delay || 5000;
const batchSize = job.batchSize || 1000;
let totalPurged = 0;
let initialTotalItems = 0;
let batchCount = 0;
this.logger.debug(`Starting batch purge for queue ${parsedParams.queueParams.name}`);
const purgeBatch = () => {
batchCount += 1;
this.logger.debug(`Processing purge batch ${batchCount} (batch size = ${batchSize}) for queue ${parsedParams.queueParams.name}`);
async.waterfall([
(next) => {
this.getJobManager().get(job.id, (err, currentJob) => {
if (err) {
return next(err);
}
if (!currentJob ||
currentJob.status === EBackgroundJobStatus.CANCELED) {
return next(new BackgroundJobCanceledError({
metadata: { jobId: job.id },
}));
}
next();
});
},
(_, next) => MessageBrowserFactory.createBrowserForQueue(job.payload.queue.queueParams, job.payload.messageType, this.logger, next),
(messageBrowser, next) => {
messageBrowser.getMessageIds(parsedParams, 1, batchSize, next);
},
(result, next) => {
const { items, totalItems } = result;
if (batchCount === 1) {
initialTotalItems = totalItems;
this.logger.debug(`Queue "${parsedParams.queueParams.name}" has ${initialTotalItems} messages to purge`);
}
if (items.length === 0) {
this.logger.debug(`No messages to purge in this batch for queue ${parsedParams.queueParams.name}`);
return next(null, false);
}
this.logger.debug(`Deleting ${items.length} messages from queue ${parsedParams.queueParams.name}`);
_deleteMessage(this.getRedisClient(), items, job.id, (err, reply) => {
if (err) {
this.logger.error(`Error deleting messages: ${err.message}`);
return next(err);
}
const successfullyDeleted = reply?.stats.success || 0;
totalPurged += successfullyDeleted;
this.logger.debug(`Batch ${batchCount}: Deleted ${successfullyDeleted} messages`);
this.getJobManager().updateProgress(job.id, totalPurged, initialTotalItems);
const shouldContinue = items.length === batchSize;
next(null, shouldContinue);
});
},
], (err, shouldContinue) => {
if (err) {
this.logger.error(`Error in purge batch: ${err.message}`);
return cb(err);
}
if (shouldContinue) {
this.logger.debug(`Got full batch (${batchSize} items), continuing with batch ${batchCount + 1}`);
if (delay)
setTimeout(() => purgeBatch(), delay);
else
purgeBatch();
}
else {
this.logger.info(`Successfully purged ${totalPurged} messages from queue "${parsedParams.queueParams.name}"`);
cb(null, totalPurged);
}
});
};
purgeBatch();
}
work(cb) {
async.series([
(cb) => {
this.getJobManager().recoverStuckJobs(cb);
},
(cb) => {
this.processNextJob(cb);
},
], (err) => cb(err));
}
}
export default PurgeQueueJob;
//# sourceMappingURL=purge-queue.job.js.map