in Nestjs, I have this cron job that I want to execute it once if multiple instances of the app exists by using Redlock:
@Cron('*/1 * * * * *')
async test(): Promise<void> {
try {
const lockKey = 'test-cron-job-lock';
const lock = await this.redisService.acquireLock(lockKey, 1000); // 1 second
this.logger.log(`Cron job at ${this.getTimeWithoutMilliseconds()}`);
await this.redisService.releaseLock(lock);
} catch (e) {
this.logger.error('Error in test cron job');
}
}
it works, but sometimes the lockKey
gets acquired by multiple instances at same time.
App A:
[Nest] 8872 - 10/04/2024, 4:06:48 PM LOG Cron job at 2024-10-04T13:06:48Z
[Nest] 8872 - 10/04/2024, 4:06:49 PM ERROR Error in test cron job
[Nest] 8872 - 10/04/2024, 4:06:50 PM ERROR Error in test cron job
[Nest] 8872 - 10/04/2024, 4:06:51 PM LOG Cron job at 2024-10-04T13:06:51Z
[Nest] 8872 - 10/04/2024, 4:06:52 PM LOG Cron job at 2024-10-04T13:06:52Z
[Nest] 8872 - 10/04/2024, 4:06:53 PM LOG Cron job at 2024-10-04T13:06:53Z
App B:
[Nest] 8886 - 10/04/2024, 4:06:48 PM ERROR Error in test cron job
[Nest] 8886 - 10/04/2024, 4:06:49 PM LOG Cron job at 2024-10-04T13:06:49Z
[Nest] 8886 - 10/04/2024, 4:06:50 PM LOG Cron job at 2024-10-04T13:06:50Z
[Nest] 8886 - 10/04/2024, 4:06:51 PM ERROR Error in test cron job
[Nest] 8886 - 10/04/2024, 4:06:52 PM ERROR Error in test cron job
[Nest] 8886 - 10/04/2024, 4:06:53 PM LOG Cron job at 2024-10-04T13:06:53Z
As you can see both instances run the job at 2024-10-04T13:06:53Z
. why?
Generally race condition with Redlock
is rare, but since you set the cron exactly at the same time and the lock TTL is pretty short, a race once in a while is possible.
There are few ways to solve it, with pros and cons -
SET
with NX
and EX
with TTL as parameters after acquiring, something like SET "running" NX EX 10
— if the key already set, you'll get null, otherwise you'll get OK
, this is atomic operation so it's not possible to have a race, but in this case I would leave the lock and use the set alone. The EX
make sure that the key release after a while.Generally I would say that the Redlock
fits for cases when you can't predict behavior, and you want to make sure that always there's just one client having it.
But if they all start at the same time, taking about the same time to perform, and you need something fully atomic, I'd use SET key NX EX sec
with a slight jitter and you are pretty bullet prof.