0

I have Nest.js application which is adds jobs to Bull queue with certain delay:

this.appQueue.add(
  {
    message: data,
  },
  {
    delay: APP_DELAY,
  },
);

Now this application doesn't have any consumer (ie. class decorated with @Processor() decorator and method decorated with @Process() method. Instead there is another application (actually instance of the same partial application created with NestFactory.createApplicationContext(AppModule)) which will be triggered only if there are waiting jobs in queue and it's task will be to process those jobs:

async function bootstrap(): Promise<void> {
  const app = await NestFactory.createApplicationContext(AppModule);
  const appConsumer = app.get(AppConsumer);

  appConsumer.once(AppConsumerEvents.WorkCompleted, async () => {
    process.exit(0);

    await app.close();
  });
}

bootstrap();
// file with consumer class
@Processor(APP_QUEUE_NAME)
export class AppConsumer extends EventEmitter {
  public constructor(
    @InjectQueue(APP_QUEUE_NAME)
    private readonly appQueue: Queue<IAppQueueData>,
    private readonly appService: AppService,
  ) {
    super();

    this.init();
  }

  private async init(): Promise<void> {
    const waitingCount = await this.appQueue.getWaitingCount();
    const delayed = await this.appQueue.getDelayedCount();

    console.log(waitingCount, delayed);

    if (waitingCount === 0) {
      this.emit(AppConsumerEvents.WorkCompleted);
    }
  }

  @Process()
  public processJobs(job: Job<IAppQueueData>): Promise<void> {
    const { namespace, token, message } = job.data;
    console.log(`Job ${job.id} processed with worker ${ID}`);

    return this.appService.publishMessage(message, token, namespace);
  }

  @OnGlobalQueueDrained()
  public onGlobalQueueDrained(): void {
    this.emit(AppConsumerEvents.WorkCompleted);
  }
}

Now the problem is: because main application doesn't have any consumer running, jobs added with some delay stays as delayed in queue, until there is some consumer running which promotes jobs from delayed to waiting after specified amount of time passes (this isn't explicity stated anywhere in docs, but it looks like it is working like that). If I define consumer within main app then it is promoting jobs from delayed to waiting, but at same time it is processing them, so they end up in "completed" state in queue. I made a temporary solution where in main application I created a cron job which in specified time interval gets all delayed jobs, checks if job can be promoted, and promotes them.

@Cron(CronExpression.EVERY_30_SECONDS)
  public async checkIfJobsShouldBePromoted(): Promise<void> {
    const delayedJobs = await this.appQueue.getDelayed();

    if (delayedJobs.length) {
      for (const job of delayedJobs) {
        if (Date.now() > job.timestamp + APP_QUEUE_DELAY) {
          console.log(`job ${job.id} is ready to be promoted!`);
          job.promote();
        } else {
          console.log(`job ${job.id} not ready to be promoted yet :(`);
        }
      }
    }
  }

It works fine - but I'd like another solution, without doing things manually. Ideally I'd like to define a consumer which only responsibility would be promoting jobs to waiting state but without processing them. Reading Nest.js and Bull docs I can't find such option. Can this be achieved?

Thanks in advance.

Furman
  • 2,017
  • 3
  • 25
  • 43

0 Answers0