Ctrl K
ComunidadGuías…

server/setUpQueues.js

Rama master · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
const { startHomeSuggestions } = require("./crons/homeSuggestions");
const { Queue, Worker, QueueEvents } = require("bullmq");
const { createBullBoard } = require("@bull-board/api");
const { BullMQAdapter } = require("@bull-board/api/bullMQAdapter");
const { ExpressAdapter } = require("@bull-board/express");

const { getQueueOptions } = require("./redisConnection");
const bullBoardAuth = require("./middlewares/bullBoardAuth");
const updateCharts = require("./crons/updateCharts");
const updateDashboards = require("./crons/updateDashboards");
const sendSnapshots = require("./crons/sendSnapshots");
const {
  getDatasetIntelligenceQueue,
} = require("./modules/datasetIntelligence/profileQueue");
// const updateSnapshots = require("./crons/updateSnapshots");

const WORKER_LOCK_DURATION_MS = parseInt(process.env.CB_QUEUE_LOCK_DURATION_MS, 10) || 900000;
const WORKER_LOCK_RENEW_TIME_MS = parseInt(process.env.CB_QUEUE_LOCK_RENEW_TIME_MS, 10) || 60000;
const EFFECTIVE_WORKER_LOCK_RENEW_TIME_MS = Math.min(
  WORKER_LOCK_RENEW_TIME_MS,
  Math.floor(WORKER_LOCK_DURATION_MS / 2)
);
const isQueueDebugEnabled = /^(1|true|yes|on)$/i.test(`${process.env.CB_QUEUE_DEBUG || ""}`);

function debugQueueLog(queueName, eventName, payload = null) {
  if (!isQueueDebugEnabled) {
    return;
  }

  if (payload) {
    console.log(`[queue:${queueName}] ${eventName}`, payload); // oxlint-disable-line no-console
    return;
  }

  console.log(`[queue:${queueName}] ${eventName}`); // oxlint-disable-line no-console
}

let updateChartsQueue;
let updateDashboardsQueue;
let updateMongoDBSchemaQueue;
let datasetIntelligenceQueue;

const setUpQueues = (app) => {
  const queuesToClose = [];
  const workersToClose = [];
  const queueEventsToClose = [];
  const homeSuggestions = startHomeSuggestions();
  app.set("homeSuggestionsQueue", homeSuggestions.queue);
  app.set("onboardingSuggestionsQueue", homeSuggestions.onboardingQueue);
  queuesToClose.push(homeSuggestions.queue, homeSuggestions.onboardingQueue);
  workersToClose.push(homeSuggestions.worker, homeSuggestions.onboardingWorker);

  if (isQueueDebugEnabled) {
    console.log("[setUpQueues] queue debug logging is ENABLED"); // oxlint-disable-line no-console
  }

  // set up bullmq queues

  /*
  ** Update Charts Queue
  */
  updateChartsQueue = new Queue("updateChartsQueue", getQueueOptions());
  queuesToClose.push(updateChartsQueue);
  updateChartsQueue.on("error", (error) => {
    if (error.code === "ECONNREFUSED") {
      console.error("Failed to set up the updates queue. Please check if Redis is running: https://docs.chartbrew.com/quickstart#set-up-redis-for-automatic-dataset-updates"); // oxlint-disable-line no-console
      process.exit(1);
    }
  });

  /*
  ** Update Dashboards Queue
  */
  updateDashboardsQueue = new Queue("updateDashboardsQueue", getQueueOptions());
  queuesToClose.push(updateDashboardsQueue);
  updateDashboardsQueue.on("error", (error) => {
    if (error.code === "ECONNREFUSED") {
      console.error("Failed to set up the updates queue. Please check if Redis is running: https://docs.chartbrew.com/quickstart#set-up-redis-for-automatic-dataset-updates"); // oxlint-disable-line no-console
      process.exit(1);
    }
  });

  const updateDashboardsQueueEvents = new QueueEvents(updateDashboardsQueue.name, {
    connection: updateDashboardsQueue.opts.connection,
  });
  queueEventsToClose.push(updateDashboardsQueueEvents);

  if (isQueueDebugEnabled) {
    updateDashboardsQueueEvents.on("waiting", (event) => {
      debugQueueLog(updateDashboardsQueue.name, "waiting", event);
    });
    updateDashboardsQueueEvents.on("active", (event) => {
      debugQueueLog(updateDashboardsQueue.name, "active", event);
    });
    updateDashboardsQueueEvents.on("completed", (event) => {
      debugQueueLog(updateDashboardsQueue.name, "completed", event);
    });
    updateDashboardsQueueEvents.on("failed", (event) => {
      debugQueueLog(updateDashboardsQueue.name, "failed", event);
    });
    updateDashboardsQueueEvents.on("stalled", (event) => {
      debugQueueLog(updateDashboardsQueue.name, "stalled", event);
    });
    updateDashboardsQueueEvents.on("deduplicated", (event) => {
      debugQueueLog(updateDashboardsQueue.name, "deduplicated", event);
    });
    updateDashboardsQueueEvents.on("error", (error) => {
      debugQueueLog(updateDashboardsQueue.name, "error", { message: error.message });
    });
  }

  /*
  ** Update MongoDB Schema Queue
  */
  updateMongoDBSchemaQueue = new Queue("updateMongoDBSchemaQueue", getQueueOptions());
  queuesToClose.push(updateMongoDBSchemaQueue);
  updateMongoDBSchemaQueue.on("error", (error) => {
    if (error.code === "ECONNREFUSED") {
      console.error("Failed to set up the MongoDB schema update queue. Please check if Redis is running: https://docs.chartbrew.com/quickstart#set-up-redis-for-automatic-dataset-updates"); // oxlint-disable-line no-console
      process.exit(1);
    }
  });
  // create a worker for the updateMongoDBSchemaQueue
  const updateMongoDBSchemaWorker = new Worker(updateMongoDBSchemaQueue.name, async (job) => { // eslint-disable-line
    const updateMongoDBSchema = require("./crons/workers/updateMongoSchema"); // eslint-disable-line
    await updateMongoDBSchema(job);
  }, {
    connection: updateMongoDBSchemaQueue.opts.connection,
    concurrency: 1,
    lockDuration: WORKER_LOCK_DURATION_MS,
    lockRenewTime: EFFECTIVE_WORKER_LOCK_RENEW_TIME_MS,
  });
  workersToClose.push(updateMongoDBSchemaWorker);

  /*
  ** Dashboard Snapshot Queue
  */
  const dashboardSnapshotQueue = new Queue("sendSnapshotsQueue", getQueueOptions());
  queuesToClose.push(dashboardSnapshotQueue);
  dashboardSnapshotQueue.on("error", (error) => {
    if (error.code === "ECONNREFUSED") {
      console.error("Failed to set up the dashboard snapshot queue. Please check if Redis is running: https://docs.chartbrew.com/quickstart#set-up-redis-for-automatic-dataset-updates"); // oxlint-disable-line no-console
      process.exit(1);
    }
  });
  // create a worker for the dashboardSnapshotQueue
  const sendSnapshotWorker = new Worker(dashboardSnapshotQueue.name, async (job) => { // eslint-disable-line
    const sendSnapshot = require("./crons/workers/sendSnapshot"); // eslint-disable-line
    await sendSnapshot(job);
  }, {
    connection: dashboardSnapshotQueue.opts.connection,
    concurrency: 1,
    lockDuration: WORKER_LOCK_DURATION_MS,
    lockRenewTime: EFFECTIVE_WORKER_LOCK_RENEW_TIME_MS,
  });
  workersToClose.push(sendSnapshotWorker);

  /*
  ** Update Snapshots Queue
  */
  const updateSnapshotsQueue = new Queue("updateSnapshotsQueue", getQueueOptions());
  queuesToClose.push(updateSnapshotsQueue);
  updateSnapshotsQueue.on("error", (error) => {
    if (error.code === "ECONNREFUSED") {
      console.error("Failed to set up the update snapshots queue. Please check if Redis is running: https://docs.chartbrew.com/quickstart#set-up-redis-for-automatic-dataset-updates"); // oxlint-disable-line no-console
      process.exit(1);
    }
  });
  // create a worker for the updateSnapshotsQueue
  const takeSnapshotWorker = new Worker(updateSnapshotsQueue.name, async (job) => { // eslint-disable-line
    const takeSnapshot = require("./crons/workers/takeSnapshot"); // eslint-disable-line
    await takeSnapshot(job);
  }, {
    connection: updateSnapshotsQueue.opts.connection,
    concurrency: 10,
    lockDuration: WORKER_LOCK_DURATION_MS,
    lockRenewTime: EFFECTIVE_WORKER_LOCK_RENEW_TIME_MS,
  });
  workersToClose.push(takeSnapshotWorker);

  /*
  ** Dataset Intelligence Queue
  */
  datasetIntelligenceQueue = getDatasetIntelligenceQueue();
  queuesToClose.push(datasetIntelligenceQueue);
  const datasetIntelligenceWorker = new Worker(datasetIntelligenceQueue.name, async (job) => {
    const profileDataset = require("./crons/workers/profileDataset"); // eslint-disable-line
    await profileDataset(job);
  }, {
    connection: datasetIntelligenceQueue.opts.connection,
    concurrency: 2,
    lockDuration: WORKER_LOCK_DURATION_MS,
    lockRenewTime: EFFECTIVE_WORKER_LOCK_RENEW_TIME_MS,
  });
  workersToClose.push(datasetIntelligenceWorker);

  const serverAdapter = new ExpressAdapter();
  serverAdapter.setBasePath("/apps/queues");

  createBullBoard({
    queues: [
      new BullMQAdapter(updateChartsQueue),
      new BullMQAdapter(updateDashboardsQueue),
      new BullMQAdapter(updateMongoDBSchemaQueue),
      new BullMQAdapter(dashboardSnapshotQueue),
      new BullMQAdapter(updateSnapshotsQueue),
      new BullMQAdapter(datasetIntelligenceQueue),
      new BullMQAdapter(homeSuggestions.queue),
      new BullMQAdapter(homeSuggestions.onboardingQueue),
    ],
    serverAdapter,
    options: {
      uiConfig: {
        boardTitle: "Chartbrew Jobs",
      },
    },
  });

  app.use("/apps/queues", bullBoardAuth, (req, res, next) => {
    res.setHeader("Content-Security-Policy", "default-src 'self'; img-src *;"); // Allow images to load from any source
    next();
  }, serverAdapter.getRouter());

  // set up cron jobs
  const updateChartsWorker = updateCharts(updateChartsQueue);
  const updateDashboardsWorker = updateDashboards(updateDashboardsQueue);
  workersToClose.push(updateChartsWorker, updateDashboardsWorker);
  sendSnapshots(dashboardSnapshotQueue);

  // Uncomment this to enable regular snapshot updates
  // updateSnapshots(updateSnapshotsQueue);

  let isShuttingDown = false;
  const closeQueuesAndWorkers = async (signal) => {
    if (isShuttingDown) {
      return;
    }

    isShuttingDown = true;
    homeSuggestions.task.stop();
    console.log(`${signal} received. Closing BullMQ workers and queues...`); // eslint-disable-line

    const workerResults = await Promise.allSettled(workersToClose.map((worker) => worker.close()));
    workerResults
      .filter((result) => result.status === "rejected")
      .forEach((result) => {
        console.error(`Failed to close worker: ${result.reason?.message || result.reason}`); // eslint-disable-line
      });

    const queueResults = await Promise.allSettled(queuesToClose.map((queue) => queue.close()));
    queueResults
      .filter((result) => result.status === "rejected")
      .forEach((result) => {
        console.error(`Failed to close queue: ${result.reason?.message || result.reason}`); // eslint-disable-line
      });

    const queueEventResults = await Promise.allSettled(
      queueEventsToClose.map((queueEvents) => queueEvents.close())
    );
    queueEventResults
      .filter((result) => result.status === "rejected")
      .forEach((result) => {
        console.error(`Failed to close queue events: ${result.reason?.message || result.reason}`); // eslint-disable-line
      });

    process.exit(0);
  };

  ["SIGINT", "SIGTERM", "SIGUSR2"].forEach((signal) => {
    process.on(signal, () => {
      closeQueuesAndWorkers(signal);
    });
  });
};

module.exports = {
  setUpQueues,
  getQueues: () => ({
    updateChartsQueue,
    updateDashboardsQueue,
    updateMongoDBSchemaQueue,
    datasetIntelligenceQueue,
  }),
};