feature/IO-3096-GlobalNotifications - Checkpoint, finished testing queue, adjusted timeouts to be pegged to one variable.
This commit is contained in:
@@ -1,6 +1,15 @@
|
||||
const { Queue, Worker } = require("bullmq");
|
||||
const { sendTaskEmail } = require("../../email/sendemail");
|
||||
|
||||
// Base time-related constant (in milliseconds)
|
||||
const EMAIL_CONSOLIDATION_DELAY = 60000; // 1 minute (base timeout)
|
||||
|
||||
// Derived time-related constants based on EMAIL_CONSOLIDATION_DELAY / DO NOT TOUCH, these are pegged to EMAIL_CONSOLIDATION_DELAY
|
||||
const CONSOLIDATION_KEY_EXPIRATION = EMAIL_CONSOLIDATION_DELAY * 1.5; // 1.5 minutes (90s, buffer for consolidation)
|
||||
const LOCK_EXPIRATION = EMAIL_CONSOLIDATION_DELAY * 0.25; // 15 seconds (quarter of base, for lock duration)
|
||||
const RATE_LIMITER_DURATION = EMAIL_CONSOLIDATION_DELAY * 0.1; // 6 seconds (tenth of base, for rate limiting)
|
||||
const NOTIFICATION_EXPIRATION = EMAIL_CONSOLIDATION_DELAY * 1.5; // 1.5 minutes (matches consolidation key expiration)
|
||||
|
||||
let emailAddQueue;
|
||||
let emailConsolidateQueue;
|
||||
let emailAddWorker;
|
||||
@@ -36,7 +45,7 @@ const loadEmailQueue = async ({ pubClient, logger }) => {
|
||||
emailAddWorker = new Worker(
|
||||
"emailAdd",
|
||||
async (job) => {
|
||||
const { jobId, jobRoNumber, bodyShopName, body, recipients } = job.data; // Receive bodyShopName
|
||||
const { jobId, jobRoNumber, bodyShopName, body, recipients } = job.data;
|
||||
logger.logger.info(`Adding email notifications for jobId ${jobId}`);
|
||||
|
||||
const redisKeyPrefix = `email:notifications:${jobId}`;
|
||||
@@ -44,9 +53,11 @@ const loadEmailQueue = async ({ pubClient, logger }) => {
|
||||
const { user } = recipient;
|
||||
const userKey = `${redisKeyPrefix}:${user}`;
|
||||
await pubClient.rpush(userKey, body);
|
||||
await pubClient.expire(userKey, NOTIFICATION_EXPIRATION / 1000); // Set expiration
|
||||
const detailsKey = `email:recipientDetails:${jobId}:${user}`;
|
||||
await pubClient.hsetnx(detailsKey, "firstName", recipient.firstName || "");
|
||||
await pubClient.hsetnx(detailsKey, "lastName", recipient.lastName || "");
|
||||
await pubClient.expire(detailsKey, NOTIFICATION_EXPIRATION / 1000); // Set expiration
|
||||
await pubClient.sadd(`email:recipients:${jobId}`, user);
|
||||
logger.logger.debug(`Stored message for ${user} under ${userKey}: ${body}`);
|
||||
}
|
||||
@@ -54,14 +65,18 @@ const loadEmailQueue = async ({ pubClient, logger }) => {
|
||||
const consolidateKey = `email:consolidate:${jobId}`;
|
||||
const flagSet = await pubClient.setnx(consolidateKey, "pending");
|
||||
if (flagSet) {
|
||||
// Pass bodyShopName to the consolidation job
|
||||
await emailConsolidateQueue.add(
|
||||
"consolidate-emails",
|
||||
{ jobId, jobRoNumber, bodyShopName },
|
||||
{ jobId: `consolidate:${jobId}`, delay: 30000 }
|
||||
{
|
||||
jobId: `consolidate:${jobId}`,
|
||||
delay: EMAIL_CONSOLIDATION_DELAY,
|
||||
attempts: 3, // Retry up to 3 times
|
||||
backoff: LOCK_EXPIRATION // Retry delay matches lock expiration (15s)
|
||||
}
|
||||
);
|
||||
logger.logger.info(`Scheduled email consolidation for jobId ${jobId}`);
|
||||
await pubClient.expire(consolidateKey, 300);
|
||||
await pubClient.expire(consolidateKey, CONSOLIDATION_KEY_EXPIRATION / 1000); // Convert to seconds
|
||||
} else {
|
||||
logger.logger.debug(`Email consolidation already scheduled for jobId ${jobId}`);
|
||||
}
|
||||
@@ -81,7 +96,7 @@ const loadEmailQueue = async ({ pubClient, logger }) => {
|
||||
logger.logger.info(`Consolidating emails for jobId ${jobId}`);
|
||||
|
||||
const lockKey = `lock:emailConsolidate:${jobId}`;
|
||||
const lockAcquired = await pubClient.set(lockKey, "locked", "NX", "EX", 10);
|
||||
const lockAcquired = await pubClient.set(lockKey, "locked", "NX", "EX", LOCK_EXPIRATION / 1000); // Convert to seconds
|
||||
if (lockAcquired) {
|
||||
try {
|
||||
const recipientsSet = `email:recipients:${jobId}`;
|
||||
@@ -127,7 +142,7 @@ const loadEmailQueue = async ({ pubClient, logger }) => {
|
||||
await pubClient.del(`email:consolidate:${jobId}`);
|
||||
} catch (err) {
|
||||
logger.logger.error(`Email consolidation error for jobId ${jobId}: ${err.message}`, { error: err });
|
||||
throw err;
|
||||
throw err; // Trigger retry if attempts remain
|
||||
} finally {
|
||||
await pubClient.del(lockKey);
|
||||
}
|
||||
@@ -139,7 +154,7 @@ const loadEmailQueue = async ({ pubClient, logger }) => {
|
||||
connection: pubClient,
|
||||
prefix: "{BULLMQ}",
|
||||
concurrency: 1,
|
||||
limiter: { max: 1, duration: 5000 }
|
||||
limiter: { max: 1, duration: RATE_LIMITER_DURATION }
|
||||
}
|
||||
);
|
||||
|
||||
@@ -188,14 +203,11 @@ const getQueue = () => {
|
||||
* @returns {Promise<void>} Resolves when all notifications are added to the queue.
|
||||
*/
|
||||
const dispatchEmailsToQueue = async ({ emailsToDispatch, logger }) => {
|
||||
console.dir(emailsToDispatch);
|
||||
const emailAddQueue = getQueue();
|
||||
|
||||
for (const email of emailsToDispatch) {
|
||||
// Extract bodyShopName along with other fields
|
||||
const { jobId, jobRoNumber, bodyShopName, body, recipients } = email;
|
||||
|
||||
// Validate required fields, including bodyShopName
|
||||
if (!jobId || !jobRoNumber || !bodyShopName || !body || !recipients.length) {
|
||||
logger.logger.warn(
|
||||
`Skipping email dispatch for jobId ${jobId} due to missing data: ` +
|
||||
@@ -204,7 +216,6 @@ const dispatchEmailsToQueue = async ({ emailsToDispatch, logger }) => {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Include bodyShopName in the job data
|
||||
await emailAddQueue.add(
|
||||
"add-email-notification",
|
||||
{ jobId, jobRoNumber, bodyShopName, body, recipients },
|
||||
|
||||
Reference in New Issue
Block a user