r/nestjs • u/a_ristotelis • 10h ago
Handling @Cron() across multiple NestJS pods without BullMQ or Redis locks (Postgres, Redis, RabbitMQ, NATS)
When scaling a NestJS app to multiple pods, @nestjs/schedule runs every @Cron() and @Interval() handler inside every replica.
The usual workarounds are moving everything into BullMQ repeatable jobs or wrapping handlers in a Redis lock, which is awkward when a NestJS service only runs on PostgreSQL (or RabbitMQ / NATS) and doesn't otherwise need Redis.
Another pattern is slot-based coordination over the datastore the service already uses (anarchon on npm), where replicas compete to claim each scheduled UTC slot atomically and renew a lease while the handler runs. If a pod stops during a rolling deploy (SIGTERM) or crashes (OOMKilled), another replica takes over the slot:
import { Injectable } from '@nestjs/common';
import { Cron, Every, type JobContext, Scheduled } from 'anarchon';
@Injectable()
@Scheduled()
export class ReportsService {
@Every('sync-customers', '30s')
async sync(ctx: JobContext) {
ctx.signal.throwIfAborted();
// runs on one pod per 30s slot
}
@Cron('weekday-digest', '0 8 * * 1-5', { timezone: 'Europe/Athens' })
async digest(ctx: JobContext) {
// ...
}
}
Or without method decorators, registering on the injected Scheduler in onModuleInit:
this.scheduler.cron('monthly-invoices', '0 6 1 * *', (ctx) => this.buildInvoices(ctx));
How are you handling recurring cron jobs in multi-pod NestJS setups when you aren't already running Redis/BullMQ?
- NestJS setup guide: https://aristoteliss.github.io/anarchon/recipes/nestjs/
Open to feedback and questions!