βοΈ Laravel Queues: Rate-Limiting jobs

Search for a command to run...

No comments yet. Be the first to comment.
Have you ever wished some queued jobs would be processed way ahead others? For example, if you want your VIP client's transanctions to be completed before everyone else's, this article might help you. Simple approach β¨ If you have only a few tiers of...
Flag emojis are a fun and visual way to represent countries and regions. These emojis are part of the Unicode standard and are created using a pair of regional indicator symbols. In this article, we will explore how to convert 2-letter ISO 3166-1 cou...

If youβre like many developers, you know the frustration of being stuck on an older PHP version. Upgrading to the latest and greatest can be a daunting task, especially when you're dealing with legacy code and tight deadlines π€¬. But fear not! Symfon...

Have you ever wished some queued jobs would be processed way ahead others? For example, if you want your VIP client's transanctions to be completed before everyone else's, this article might help you. Simple approach β¨ If you have only a few tiers of...

As a part of your digital identity, you may want to use a custom domain for your personal email address. In this article I'll show you how to easily set up email receiving and sending without breaking a bank. Why do I need a custom email domain in th...

When reviewing a Pull Request that touched composer.json and composer.lock files you might notice that the diffs are usually pretty huge and it's almost impossible to tell what exactly happened there - which packages got added, updated, downgraded or...

Hi guys, today I'd like to talk a bit about rate-limiting Laravel queued jobs π¨π»βπ».
This might be a common scenario, where we want to make sure jobs are not processed too fast in order to not overload other system resources.
π€ In one of my projects there was a case where certain type of jobs had to be processed at specific rate as they had to call a 3rd party API which was rate-limited.
Let's explore different approaches we could take to solve this problem.
Laravel provides a built-in rate limiting feature that you can use to limit the rate at which jobs are processed.
You can define rate limiters using the RateLimiter::for() method and apply them to your jobs using middleware or directly within your job classes.
Rate limiters can be configured based on various criteria such as the number of attempts per minute, per user, or per resource.
Simply add this to your job class:
/**
* The rate limiting middleware for the job.
*/
public function middleware(): array
{
return [
new \Illuminate\Queue\Middleware\RateLimitedWithRedis('process-job', 10, 60) // Example: 10 jobs per minute
];
}
Pros:
Cons:
If Laravel's built-in rate limiting does not meet your requirements, you can implement custom rate limiting logic within your application thanks to Job Middlewares.
Instead of putting the job back to the queue and decreasing $maxAttempts, you could make the job wait a bit before proceeding to processing it.
Pros:
If you are not satisfied with above solutions an you'd like to fetch the job from the queue only when there is quota available in your rate limiter logic, the easiest way to do so is to create a custom Queue Worker:
<?php
namespace App\Queue;
use Illuminate\Queue\Worker;
use Illuminate\Queue\WorkerOptions;
class RateLimitedWorker extends Worker
{
/**
* Pop the next job off of the queue with rate limiting.
*
* @param string|null $connectionName
* @param string|null $queue
* @param \Illuminate\Queue\WorkerOptions $options
* @return \Illuminate\Contracts\Queue\Job|null
*/
public function pop($connectionName = null, $queue = null, WorkerOptions $options = null)
{
// Implement rate limiting logic here before popping the job
// For example, use Laravel's rate limiting feature or any custom logic
return parent::pop($connectionName, $queue, $options);
}
}
Registering this worker as default application queue worker in your AppServiceProvider will apply this logic to all queues and connections - you can conditionally run the custom logic on certain queues then:
$this->app->extend('queue.worker', function ($worker, $app) {
return new RateLimitedWorker(
$app['queue'],
$app['events'],
$app['queue.failer'],
$app['cache.store']
);
});
The simplest way to rate-limit such jobs is using a sleep() and calculating the number of processes vs allowed limit. More elegant solution would be to use Laravel's RateLimit component. Just make sure you always take into account that there might be many concurrent workers running at any point of time, so use some kind of distributed lock for that. Cheers!
β That's a lot of code...
Pros:
Cons:
Queue::popUsing()If you don't want to extend framework's classes, you might want to use composition over inheritance to customize the way your app picks up the jobs from the queue:
<?php
use Illuminate\Support\Facades\Queue;
Queue::popUsing(function ($connection, $queue) {
// Implement rate limiting logic here before popping the job
// For example, use Laravel's rate limiting feature or any custom logic
return Queue::getConnection($connection)->pop($queue);
});
Just put this code in one of your service providers and enjoy avoiding nasty inheritance. Well, that didn't sound right.
Pros:
Cons:
Each option offers flexibility and customization to suit different use cases and requirements. Depending on your specific needs, you can choose the approach that best fits your application architecture and rate limiting requirements.
In my case, none of them were good enough.
That's because I looked at it from the wrong angle - the fact that the 3rd party API has some limits shouldn't be the queue's concern but should rather be handled in the SDK or the client class that connects to it and solved there. You might one day call this API directly from a request or CLI and you'd have to rewrite the whole thing all over again.
One way to achieve it is to use Guzzle Rate Limiter Middleware which waits for the quota to replenish before calling the API another time. Simply plug it wherever you are hitting the external API and boom, job done! π€π»
See you again soon!