Scheduling tasks at a random time in Laravel

Oct 5, 2020 by Jeroen Deviaene

A few days ago I had a special requirement for the Laravel scheduler. I needed to run a command every day, but it had to be at a different (or random) moment each day. This seemed like something that was easy enough, however the solutions I came up with were rather messy and had too many dependencies.

At first, the easiest way to accomplish this was to run another command at the start of the day that picked a time at which the aforementioned command should run. However, this needed a place to store the time somewhere and this is something I wanted to avoid.

A better solution

It took me a while, but later that day I discovered a simple and elegant way to accomplish this functionality. PHP, like most other programming languages, has a random number generated that can be manually seeded. The great thing about this is that the same seed will always return the same consecutive numbers. So for our use case: if we seed the random number generator with today’s date, we can let the “random” number generator decide how many seconds from midnight we want the command to run.

srand(intval(date('Ymd')));
$hour = floor(rand(0, 1440) / 60);
$minute = rand(0, 1440) % 60;

$schedule->command(Commands\MyCommand::class)->dailyAt("{$hour}:{$minute}");

I like this solution very much. The code is clean and there is no storage dependency.

One caveat

One small problem with this way of working is when you want to use the random number generator inside your scheduler. If you have any scheduled command that uses this, it will generate the same sequence of numbers each day.

When you are in this situation I have found two possible solutions. You could either set a new seed using hrtime as a semi-random value. Or you could execute the commands asynchronously using queues which run as a new PHP process.

Other use cases

The same logic can also be applied to database queries. Say you want to display a random product on your website each day without having to cache it for 24 hours. The Laravel query builder includes the inRandomOrder function which can be passed a seed.

Product::query()
    ->inRandomOrder(date('Ymd'))
    ->first();

Important notice
This functionality only works for MySQL databases. Other drivers do not support setting a seed for the inRandomOrder function.