You are currently viewing 3 Ways to Run NestJS Cron Jobs When Running Multiple Instances

3 Ways to Run NestJS Cron Jobs When Running Multiple Instances

  • Post category:Articles

Managing cron jobs in a multi-instance environment can be challenging. Recently, I faced this issue on FindChildcare.ca where I am running multiple instances of a NestJS application. Here are three methods to handle this effectively:

1. Using Named Instances

This method involves naming your primary and replica instances and adding conditional checks to ensure the cron job runs only on your primary instance. This approach is simple and straightforward.

Steps to Implement:

1. Update ecosystem.config.js

module.exports = {
  apps: [
    {
      name: 'primary',
      exec_mode: 'cluster',
      instances: '1', // Ensures only one instance of primary
      script: 'dist/main.js',
      // other configurations...
    },
    {
      name: 'replica',
      exec_mode: 'cluster',
      instances: '-1', // Utilizes remaining cores for replicas
      script: 'dist/main.js',
      // other configurations...
    },
  ],
};
Code language: JavaScript (javascript)

2. Conditional Check in Cron Job:

In your cron job handler method, add a simple check:

import { Injectable } from '@nestjs/common';
import { Cron } from '@nestjs/schedule';

@Injectable()
export class CronService {
  @Cron('45 * * * * *') // Runs every minute at 45 seconds
  handleCron() {
    if (process.env.name !== 'primary') {
      console.log('Not running cron job on ' + process.env.name);
      return;
    }
    console.log('Running cron job on primary instance');
    // Your cron job logic here
  }
}
Code language: JavaScript (javascript)

If you want, set the instance name in an environment variable and access it via the NestJS Config module.

Pros:

  • Simple to implement.
  • Clear separation of primary and replica roles.

Cons:

  • Manual setup of instance names and configurations.

2. Expose Via an API

This method involves exposing an endpoint to trigger the cron job. The request will be routed to only one instance, ensuring the job is processed only once.

Steps to Implement:

1. Create an Endpoint:

import { Controller, Post } from '@nestjs/common';
import { CronService } from './cron.service';

@Controller('cron')
export class CronController {
  constructor(private readonly cronService: CronService) {}

  @Post('run')
  runCronJob() {
    this.cronService.handleCron();
    return 'Cron job triggered';
  }
}
Code language: JavaScript (javascript)

2. Secure the Endpoint:

Implement authentication, authorization, and validation for API calls to ensure only authorized clients can trigger the cron job.

Pros:

  • Most flexible approach.
  • Allows manual triggering if required.

Cons:

  • Requires additional code to set up the endpoint.
  • Added complexity for handling authentication and authorization.

3. Use Database Locking

This method involves using a database to manage locks. When a cron job is triggered, it saves the request to the database and acquires a lock on a specific row or table. Other instances will either time out or see that the job is already being processed.

Steps to Implement:

1. Save Cron Request and Acquire Lock

import { Injectable } from '@nestjs/common';
import { Cron } from '@nestjs/schedule';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { CronJob } from './cron-job.entity';

@Injectable()
export class CronService {
  constructor(
    @InjectRepository(CronJob)
    private readonly cronJobRepository: Repository<CronJob>,
  ) {}

  @Cron('45 * * * * *') // Runs every minute at 45 seconds
  async handleCron() {
    const cronJob = await this.cronJobRepository.findOne({
      where: { id: 1 },
      lock: { mode: 'pessimistic_write' },
    });

    if (cronJob.isRunning) {
      console.log('Cron job is already running');
      return;
    }

    cronJob.isRunning = true;
    await this.cronJobRepository.save(cronJob);

    try {
      console.log('Running cron job');
      // Your cron job logic here
    } finally {
      cronJob.isRunning = false;
      await this.cronJobRepository.save(cronJob);
    }
  }
}
Code language: JavaScript (javascript)

2. Cron Job Entity:

import { Entity, Column, PrimaryGeneratedColumn } from 'typeorm';

@Entity()
export class CronJob {
  @PrimaryGeneratedColumn()
  id: number;

  @Column({ default: false })
  isRunning: boolean;
}
Code language: JavaScript (javascript)

Pros:

  • Reliable and ensures only one instance processes the cron job.
  • Suitable for distributed systems.

Cons:

  • Requires database setup and maintenance.
  • Slightly more complex to implement.

Conclusion

Each method has its pros and cons, and the best choice depends on your specific use case and infrastructure. Whether you opt for named instances, exposing an API, or using database locking, these strategies will help you manage cron jobs effectively in a multi-instance NestJS application.

Have any questions, want to share your thoughts or just say Hi? I’m always excited to connect! Follow me on Twitter or LinkedIn for more insights and discussions. If you’ve found this valuable, please consider sharing it on your social media. Your support through shares and follows means a lot to me!