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

matt_at_senses's avatar

Queue Running Order

We have a debate in the office!!! It appears there is a bug in queue logic. Its best explained by example:

See image: https://i.imgur.com/UBl2M2p.png

I added 19 jobs to the queue. (all at 9:00) Job 2 was intentionally delayed by an hour. (10:00)

So, all being normal, the order the jobs were executed would be: 1,3,4,5,6,7,8,9,10,11,12,13,13,14,15,16,17,18,19,2

However lets say the queue was off, no workers running, and we turned them on at 11:00.

The order it would run would be: 1,2,3,4,5,6,7,8,9,10,11,12,13,13,14,15,16,17,18,19

For me, that is wrong, you should respect what would/should have happened.

Looking at the database queue code (using as an example, assuming redis is the same)

protected function getNextAvailableJob($queue)
    {
        $job = $this->database->table($this->table)
                    ->lock($this->getLockForPopping())
                    ->where('queue', $this->getQueue($queue))
                    ->where(function ($query) {
                        $this->isAvailable($query);
                        $this->isReservedButExpired($query);
                    })
                    ->orderBy('id', 'asc')
                    ->first();
        return $job ? new DatabaseJobRecord((object) $job) : null;
    }

In my opinion this should be:

protected function getNextAvailableJob($queue)
    {
        $job = $this->database->table($this->table)
                    ->lock($this->getLockForPopping())
                    ->where('queue', $this->getQueue($queue))
                    ->where(function ($query) {
                        $this->isAvailable($query);
                        $this->isReservedButExpired($query);
                    })
                    ->orderBy('available_at', 'asc')                         // change on this line to available at
                    ->first();
        return $job ? new DatabaseJobRecord((object) $job) : null;
    }

Am I right in this is the behaviour and opinions on whether this change is correct also does anyone know if this is the same situation / setup for redis.

Thanks

0 likes
3 replies
LaryAI's avatar
Level 58

To address the issue you're experiencing with the queue running order, you are correct in your observation that the jobs should be ordered by their available_at timestamp rather than their id. This ensures that delayed jobs are processed in the correct order, respecting their intended execution times.

Here's a revised version of the getNextAvailableJob method that orders the jobs by the available_at timestamp:

protected function getNextAvailableJob($queue)
{
    $job = $this->database->table($this->table)
        ->lock($this->getLockForPopping())
        ->where('queue', $this->getQueue($queue))
        ->where(function ($query) {
            $this->isAvailable($query);
            $this->isReservedButExpired($query);
        })
        ->orderBy('available_at', 'asc')  // Change to order by available_at
        ->first();

    return $job ? new DatabaseJobRecord((object) $job) : null;
}

Explanation:

  1. Locking: The lock method ensures that the job is locked for processing to prevent race conditions.
  2. Queue Filtering: The where('queue', $this->getQueue($queue)) ensures that only jobs from the specified queue are considered.
  3. Availability Check: The isAvailable and isReservedButExpired methods filter out jobs that are not available for processing.
  4. Ordering: The critical change is the orderBy('available_at', 'asc') line, which orders the jobs by their available_at timestamp in ascending order. This ensures that jobs are processed in the order they become available, respecting any delays.

Redis Consideration:

For Redis, the queue implementation might differ, but the principle remains the same. You need to ensure that jobs are ordered by their availability time. If you're using Laravel's Redis queue driver, you might need to customize the Redis queue implementation similarly to how you did for the database queue.

Conclusion:

Your proposed change is correct and should ensure that jobs are processed in the correct order, respecting their available_at timestamps. This change should be applied to any queue driver that does not already respect the available_at timestamp to ensure consistent behavior across different queue implementations.

martinbean's avatar

For me, that is wrong, you should respect what would/should have happened.

@matt@senses.co.uk Not really, a queue is not a scheduling system. A queue is a stack of jobs, that get ran as and when there is capacity to do so. They‘re for ensuring exactly-once delivery; not for ensuring delivery at a specific time and date.

Also, were you aware the website in your username (senses.co.uk) seems to be running malicious code? I clicked the hamburger menu icon and it opened a shady dating website in a new tab. I refreshed the page, and got a fake “McAfee has detected viruses” notification on the page. I’ve tested this on multiple devices, so it’s not that I’m using a particular machine that’s infected with something.

kima's avatar

I agree with martin. if you what to ensure that a job (id 2 in your case) would run after the rest, you may consider use Bus::chain()

Please or to participate in this conversation.