laravelmastery
SnippetsArticlesSeriesNews

Queues in production · Lesson 2 of 3

Queues in production · Lesson 2

Unique jobs and WithoutOverlapping

When it goes wrongQueues

A failed payment can be replayed from the same payload. Two clicks can still dispatch two jobs for one order. You either want the second dispatch dropped, or you want it to wait until the first has finished. Those are different tools.

Video

01 Drop a duplicate, or make it wait

ShouldBeUnique drops a second dispatch while the first job is still queued or running. uniqueId() decides what “the same work” means.

MarkOrderPaid.phpPHP
class MarkOrderPaid implements ShouldQueue, ShouldBeUnique
{
    use Queueable;

    public function uniqueId(): string
    {
        return (string) $this->orderId;
    }
}

WithoutOverlapping does not drop the second job. It holds a lock and releases the second job back onto the queue until the first one finishes. That is the right choice when both runs must happen, just not at the same time.

The snippet on stopping the same job running twice shows the lock, the pause, and the expiry.

Stop the same job running twice.

Use ShouldBeUnique when a second copy is useless. Use WithoutOverlapping when the second copy must run, only later.

02 Hold the second job until the first has finished

WithoutOverlapping is middleware. The key is the order id, the same value uniqueId() used. releaseAfter() is how soon the waiting job may be tried again. expireAfter() is when a lock left by a dead worker is dropped. It must be longer than the job timeout, or a killed worker blocks every later run until the lock happens to expire.

Example.phpPHP
class Example
{
    public function middleware(): array
    {
        return [
            (new WithoutOverlapping((string) $this->orderId))
                ->releaseAfter(15)
                ->expireAfter(90),
        ];
    }
}

uniqueFor on a ShouldBeUnique job is a different clock: it is how long the “drop the duplicate” lock is held, and the lock also clears when the job finishes or exhausts its tries. Pick one tool. A job that is both unique and overlapping is two policies for one order, and the second dispatch can be dropped when you meant it to wait. The middleware is the one that matches “both clicks must be paid, just not together”.

Recap

You can drop a duplicate dispatch with ShouldBeUnique, or make it wait with WithoutOverlapping.

  • uniqueId() is what 'the same work' means.
  • WithoutOverlapping does not drop the second job. It releases it back.
  • expireAfter() must outlast the job, or a dead worker blocks the order.

Check yourself · 3 questions

Question 1 of 3Both clicks must record something, just not at the same time. Which tool?

One worker at a time, or the second dispatch is dropped. Next, the PDF and the email that follow a paid order.

← PreviousFailed jobs you can replay

On this page

  • Drop a duplicate, or make it wait
  • Hold the second job until the first has finished
  • Recap
  • Check yourself

Queues in production

  • 1 Overview

Hand the work off

  • 2 Keep the request short
  • 3 The job class
  • 4 Dispatch from actions, not controllers

When it goes wrong

  • 5 Retries, backoff and timeouts
  • 6 Failed jobs you can replay
  • 7 Unique jobs and WithoutOverlapping
  • 8 Batches and then()

Run it for real

  • 9 Make the job safe to run twice
  • 10 Horizon in production

Queues in production

Lessons

  1. 01Overview

Hand the work off

  1. 02Keep the request short
  2. 03The job class
  3. 04Dispatch from actions, not controllers

When it goes wrong

  1. 05Retries, backoff and timeouts
  2. 06Failed jobs you can replay
  3. 07Unique jobs and WithoutOverlapping
  4. 08Batches and then()

Run it for real

  1. 09Make the job safe to run twice
  2. 10Horizon in production

Get the Friday email

One snippet and the week’s Laravel news, in your inbox every Friday morning. Unsubscribe whenever you like.

laravelmastery

Practical Laravel learning for working developers, made in the UK.

Learn

  • Snippets
  • Articles
  • Series

Stay current

  • News
  • Friday email
  • RSS feed

Company

  • About
  • Privacy
  • Terms
© 2026 Laravel Mastery. Independent and not affiliated with Laravel Holdings Inc.Designed and built by Half Shell Studios

We remember the videos you watch, where you left off, and which series lessons you mark as read, against this connection’s address. Nothing is stored until you agree.