How to prevent overlapping of jobs in Laravel
When you are using Laravel queues, you might bump into the situation where you want to prevent overlapping of jobs that handles sensitive information. This could be to provide data integrity of important information such as the user’s wallet balance.
Let’s say the user’s wallet balance is being updated by a job called App\Jobs\UpdateUserWallet
and this job will get invoked each time the user does a transaction from his/her account. How can you make sure that the wallet balance of the user won’t get updated by two different job instances at the same time? How can you maintain the integrity of the user’s balance in such a case?
Well, Laravel is being shipped with a handy job middleware called WithoutOverlapping
just for this purpose.
- The
WithoutOverlapping
middleware - Behind the scenes
- Release overlapping jobs back to queue
- Remove any overlapping jobs
The WithoutOverlapping
middleware
The WithoutOverlapping
middleware provided by Laravel can be used to prevent overlapping of jobs based on an arbitrary key. For instance, in our case, it would be the user’s id
for which we are updating the wallet’s balance.
This can prevent modifying the wallet’s balance by two different instances of the same job by preventing them from overlapping each other. Meaning, if, for example, App\Jobs\UpdateUserWallet
is dispatched and if you’re using this middleware, it won’t allow another instance of the job to be dispatched until the first job is completed.
You can achieve this by returning the WithoutOverlapping
middleware from the middleware
method of your job.
So, if we were to make use of WithoutOverlapping
in our previous example, we can do it using $this->user->id
as an arbitrary key like so.
<?php
namespace App\Jobs;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Queue\Middleware\WithoutOverlapping;
class UpdateUserWallet implements ShouldQueue
{
/**
* The user instance.
*
* @var \App\Models\User
*/
protected $user;
/**
* Create a new job instance.
*
* @param App\Models\User $user
* @return void
*/
public function __construct(User $user)
{
$this->user = $user;
}
// commented `handle` method for brevity
public function middleware()
{
return [new WithoutOverlapping($this->user->id)];
}
}
Behind the scenes
Laravel does this by acquiring atomic lock on the job through the WithoutOverlapping
middleware. You can see this by going to Illuminate\Queue\Middleware\WithoutOverlapping
’s handle
method which looks like the following.
$lock = Container::getInstance()->make(Cache::class)->lock(
$this->getLockKey($job), $this->expiresAfter
);
So, this middleware will only work with cache drivers that support atomic lock. Drivers such as memcached
, redis
, dynamodb
, database
, file
, and array
can be used seamlessly with this middleware.
Apart from this, there are a couple of more things that you can do with this middleware.
Release overlapping jobs back to queue
If there are, for instance, any overlapping jobs that occur, they can be released back onto the queue by using the releaseAfter
method where you’d need to specify the number of seconds that must elapse before the released job will be attempted again like so.
public function middleware()
{
return [(new WithoutOverlapping($this->user->id))->releaseAfter(30)];
}
This will release back any overlapping jobs onto the queue 30 seconds later once the ongoing job is processed.
Remove any overlapping jobs
If you don’t want any overlapping jobs to be released back onto the queue, you can use the dontRelease
method like so.
public function middleware()
{
return [(new WithoutOverlapping($this->user->id))->dontRelease()];
}
Like this article?
Buy me a coffee👋 Hi there! I'm Amit. I write articles about all things web development. You can become a sponsor on my blog to help me continue my writing journey and get your brand in front of thousands of eyes.