Be part of JetBrains PHPverse 2026 on June 9 – a free online event bringing PHP devs worldwide together.

ampersand's avatar

Delay between processes in Job Queue

Hi,

I'm working on a project that use the OpenWeather API. On OpenWeather is possible to obtain an API key for free if there are no more than 60 request per minute.

For my project, I need to retrieve the current weather data for 2'000 cities each hour. So I've thought I could work with the Queue function of Laravel, which run the requests in background and retry to run the task in case of errors.

Here the question, is it possible to limit the Job Queue in order to run only one process each second (and not 3 or 4 per second)? If yes, how could I do that?

Thank you very much for the help!

Davide

0 likes
19 replies
ampersand's avatar

Hi there,

thanks for your suggestions. Here is what I've tried:

<?php

namespace App\Http\Controllers;

use App\Condition;
use App\City;
use DB;
use App\Jobs\FetchConditions;

class ConditionsController extends Controller
{
    public function fetchConditions() {
        $stations = Station::all();

        foreach($stations as $station) {
            $job = (new FetchConditions($city))->delay(1);
            $this->dispatch($job);
        }

        return 'Fetching Conditions...';
    }
}

When I run the queue, it still execute 3/4 jobs each seconds. Something wrong on my code?

TylerODonnell's avatar

The delay argument takes the number of the seconds you wish the job to be delayed by. You also may have more than one queue listener running on your server. You may want to think about making it only one so you don't hit your API limit.

ampersand's avatar

@TylerODonnell Yes I know, I've choosen one second to excecute 60 jobs in a minute (with one worker). However that isn't happening (still get 3/4 jobs excecuted each second). Any idea?

jekinney's avatar

If your delay is in a foreach loop yes you will get many jobs per second still. Think about it for a second. Your loop runs 10 times a second each delay is one second, the 10 iterations will fire after one second causing all 10 to fire microseconds from each other.

You need to add a counter (suggest a for instead of a foreach loop) to add one second to your delay for each iteration of the loop.

Or add sleep() to the bottom of your loop to delay the next iteration one second and remove the delay(1).

2 likes
ampersand's avatar

@jekinney, but that will delay the insertion of the job in the queue, I need to be able to define an execution limit on the queue side. Maybe Redis has some configuration to achieve that? I didn't found anything about it. My way to approach the problem could be most likely be the not the correct one. Maybe there are other ways than using a Queue to limit API request to 60 per minute.

ampersand's avatar

Finally I think I misunderstood how Queues works. I think there is no control on how much jobs should the queue execute each seconds. I was using the ->delay(1) wondering why didn't worked, but the reality is that this function delays only the execution of the first job in the queue. To achieve what I needed, I had to modify my previously submitted code this way:

<?php

namespace App\Http\Controllers;

use App\Condition;
use App\City;
use DB;
use App\Jobs\FetchConditions;

class ConditionsController extends Controller
{
    public function fetchConditions() {
        $stations = Station::all();
    $this->dispatch(new FetchConditions($stations));

        return 'Fetching Conditions...';
    }
}

Using the foreach in the Job class using the suggested sleep(1) method:

<?php

namespace App\Jobs;

use App\City;

class RetrieveStations extends Job
{
    protected $cities;

    /**
     * Create a new job instance.
     *
     * @return void
     */
    public function __construct ($cities)
    {
        $this->cities = $cities;
    }

    /**
     * Execute the job.
     *
     * @return void
     */
    public function handle()
    {

        foreach($this->cities as $city) {
            
        // code here

            sleep(1);
        }

    }
}

Now the job runs in background and the request to the Weather API will be execute each second.

Thank you all for the help!

d3xt3r's avatar

@ampersand Not a good practise to make your process sleep. Try this ,

class ConditionsController extends Controller
{
    public function fetchConditions()
     {
        $stations = Station::all();
        
        $i = 0;
        $numberOfJobsPerSecond = 4;
    
        foreach($stations as $station)  {
            $delay = (int) floor($i++ / $numberOfJobsPerSecond);
            $job = (new FetchConditions($city))->delay($delay);
            $this->dispatch($job);
        }
    }
}
ampersand's avatar

@d3xt3r the suggested code still execute 2-4 jobs per second, even if I define $numberOfJobsPerSecond = 1;.

alialbaker2007's avatar
Level 5

I had the very same problem but even sleep wasn't enough for me because if 10 users did say update at the same time then sleep will sleep them at the same time and the delay start at the same time therefore the execution happens at the same time. i.e too much request to the API.

What i did is this.

    public function update(Request $request, $id)
    {
            $delay = \DB::table('jobs')->count()*10;

            $queue = new Update($request->all(), $id, \Auth::user()->email);
            $this->dispatch($queue->delay($delay));
     }

in this case if the queue is empty then delay is 0. If the queue has Y items then next item will be delayed by (Y*10). 10 sec from the last item.

9 likes
Folleah's avatar

Maybe this? ^_^

$orders = \App\Models\Order::take(2)->get();
            $i = 5;
            foreach ($orders as $order){
              
                dispatch((new \App\Jobs\SendTracknumberEmail($order))->delay($i));
                $i = $i + 10;
              
            }
cesarfp's avatar

I know how to set a delay for a job, but how can I configure the delay for a queued event listener? I'm using laravel 5.5.

techriders's avatar

I've used the release() function in the Job Handler function.

In my case I needed the job to execute every 10minutes, sleep and delay didn't work as it considered that Job was running when it was just sleeping. It's good if you want to retry after certain amount when API fails.

   public function handle()
        {
        if($badAPI){
            $this->release(600); // releasing back to queue in 10 mins
        }
    }
realtebo's avatar

In situation like this, you could switch to using cron and schedule a check every minute. Inside that check, you could do the for loop with sleep

timargv's avatar

Try this

<?php

namespace App\Http\Controllers;

use App\Condition;
use App\City;
use DB;
use App\Jobs\FetchConditions;

class ConditionsController extends Controller
{
    public function fetchConditions() {
        $stations = Station::all();

	$now = now();
        foreach($stations as $station) {
            $now = $now->addSeconds(1);
            $job = (new FetchConditions($city))->delay(now );
            $this->dispatch($job);
        }

        return 'Fetching Conditions...';
    }
}

Please or to participate in this conversation.