> ## Documentation Index
> Fetch the complete documentation index at: https://kawax.biz/llms.txt
> Use this file to discover all available pages before exploring further.

# Uitvoeringscontrole van queue-jobs

> Hoe je met ShouldBeUnique, ShouldBeUniqueUntilProcessing en DebounceFor dubbele uitvoering en opeenvolgende dispatches van queue-jobs beheerst.

## Overzicht

De queuefunctionaliteit van Laravel biedt twee soorten uitvoeringscontrole voor jobs: **deduplicatie (Unique)** en **debounce (Debounce)**. Beide zijn mechanismen om "onnodige uitvoeringen te besparen wanneer dezelfde job herhaaldelijk wordt gedispatcht", maar het gedrag verschilt.

| Functie                 | Interface / attribute           | Doel                                                                    |
| ----------------------- | ------------------------------- | ----------------------------------------------------------------------- |
| Unique Jobs             | `ShouldBeUnique`                | Zorgt dat er maar één identieke job in de queue staat                   |
| Unique Until Processing | `ShouldBeUniqueUntilProcessing` | Houdt de uniciteitsbeperking alleen aan tot het begin van de verwerking |
| Debounced Jobs          | `#[DebounceFor]`                | Voert bij snel opeenvolgende dispatches alleen de laatste uit           |

<Warning>
  Unique Jobs en Debounced Jobs sluiten elkaar **uit**. Implementeer geen `ShouldBeUnique` op een job die het `DebounceFor`-attribute gebruikt.
</Warning>

***

## Unique Jobs — `ShouldBeUnique`

Zolang dezelfde job in de queue staat, worden extra dispatches genegeerd.

```php theme={null}
<?php

use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Contracts\Queue\ShouldBeUnique;

class UpdateSearchIndex implements ShouldQueue, ShouldBeUnique
{
    // Geen extra methodes nodig
}
```

Zolang `UpdateSearchIndex` in de queue staat (of wordt verwerkt), wordt een poging om dezelfde job te dispatchen genegeerd.

### De uniciteitsbeperking verfijnen met een sleutel — `UniqueFor` + `uniqueId()`

Wil je binnen dezelfde jobklasse "de update van product A" en "de update van product B" als aparte jobs behandelen, dan definieer je een sleutel met de `uniqueId()`-methode.

```php theme={null}
<?php

namespace App\Jobs;

use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Contracts\Queue\ShouldBeUnique;
use Illuminate\Queue\Attributes\UniqueFor;

#[UniqueFor(3600)] // De lock wordt na 1 uur automatisch vrijgegeven
class UpdateSearchIndex implements ShouldQueue, ShouldBeUnique
{
    public function __construct(public readonly int $productId)
    {
    }

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

* De waarde die `uniqueId()` teruggeeft, wordt de sleutel van de cachelock.
* Met `#[UniqueFor(seconden)]` wordt de lock na dat aantal seconden automatisch vrijgegeven (een failsafe voor het geval de job niet verwerkt is).

### Een cachedriver opgeven — `uniqueVia()`

Wil je een andere dan de standaard-cachedriver gebruiken, dan implementeer je `uniqueVia()`.

```php theme={null}
use Illuminate\Contracts\Cache\Repository;
use Illuminate\Support\Facades\Cache;

public function uniqueVia(): Repository
{
    return Cache::driver('redis');
}
```

<Info>
  Unique Jobs vereisen een cachedriver die atomic locks ondersteunt (`redis`, `database`, `memcached`, `dynamodb`, `file`, `array`).
</Info>

***

## `ShouldBeUnique` vs `ShouldBeUniqueUntilProcessing`

De lock van `ShouldBeUnique` wordt aangehouden **totdat de job is voltooid of het retrymaximum is bereikt**. Er zijn gevallen waarin dat een probleem is.

**Voorbeeld:** er staat één `UpdateSearchIndex(product_id: 42)` in de queue en je wilt direct nadat de worker de verwerking is gestart dezelfde job opnieuw dispatchen. Met `ShouldBeUnique` komt de tweede pas in de queue nadat de verwerking is voltooid.

In dat geval gebruik je `ShouldBeUniqueUntilProcessing`. Omdat de lock **vlak vóór het begin van de verwerking wordt vrijgegeven**, is een volgende dispatch mogelijk op het moment dat de worker de job oppakt.

```php theme={null}
<?php

use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Contracts\Queue\ShouldBeUniqueUntilProcessing;

class UpdateSearchIndex implements ShouldQueue, ShouldBeUniqueUntilProcessing
{
    // ...
}
```

```mermaid theme={null}
sequenceDiagram
    participant D as Dispatcher
    participant Q as Queue
    participant W as Worker

    D->>Q: dispatch() — lock verkregen
    D->>Q: dispatch() — lock aanwezig → genegeerd

    note over Q,W: Bij ShouldBeUnique
    W->>Q: Job opgepakt
    W->>W: In verwerking (lock behouden)
    D->>Q: dispatch() — lock aanwezig → genegeerd
    W->>W: Verwerking voltooid — lock vrijgegeven
    D->>Q: dispatch() — nu pas in de queue mogelijk

    note over Q,W: Bij ShouldBeUniqueUntilProcessing
    W->>Q: Job opgepakt — lock vrijgegeven
    D->>Q: dispatch() — geen lock → in de queue mogelijk
    W->>W: In verwerking
```

### Vergelijking samengevat

|                                     | `ShouldBeUnique`                            | `ShouldBeUniqueUntilProcessing`                        |
| ----------------------------------- | ------------------------------------------- | ------------------------------------------------------ |
| Moment van lockvrijgave             | Na voltooiing / falen van de verwerking     | Vlak vóór het begin van de verwerking                  |
| Dubbele dispatch tijdens verwerking | Genegeerd                                   | In de queue mogelijk                                   |
| Use case                            | Gelijktijdige uitvoering volledig voorkomen | Direct na het oppakken de volgende job kunnen plaatsen |

***

## Debounced Jobs — `#[DebounceFor]`

<Info>
  Het `DebounceFor`-attribute is een functie die in Laravel 13 is toegevoegd.
</Info>

Als dezelfde job in korte tijd massaal wordt gedispatcht, wordt **alleen de laatst gedispatchte** uitgevoerd. Hetzelfde idee als debounce in webfrontends.

```php theme={null}
<?php

namespace App\Jobs;

use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Queue\Queueable;
use Illuminate\Queue\Attributes\DebounceFor;

#[DebounceFor(30)] // Herdispatches binnen 30 seconden worden genegeerd (alleen de laatste wordt uitgevoerd)
class UpdateSearchIndex implements ShouldQueue
{
    use Queueable;

    public function __construct(public readonly int $productId)
    {
    }

    public function debounceId(): string
    {
        return (string) $this->productId;
    }
}
```

* De waarde die `debounceId()` teruggeeft, identificeert de job (per product-ID geldt een onafhankelijke debounce).
* Ook als er binnen 30 seconden tien keer met hetzelfde `productId` wordt gedispatcht, wordt alleen de laatste uitgevoerd.

### `maxWait` — de bovengrens van de maximale wachttijd

Bij data die vaak wordt bijgewerkt, kan de debounce eindeloos doorgaan en wordt de job mogelijk nooit uitgevoerd. Met `maxWait` stel je een maximale vertraging in.

```php theme={null}
#[DebounceFor(30, maxWait: 120)]
class UpdateSearchIndex implements ShouldQueue
{
    use Queueable;
    // ...
}
```

In dit voorbeeld wordt de job uiterlijk 120 seconden na de eerste dispatch gegarandeerd uitgevoerd (ook als de 30-secondendebounce aanhoudt, is er na 120 seconden een time-out).

### Een cachedriver opgeven — `debounceVia()`

```php theme={null}
use Illuminate\Contracts\Cache\Repository;
use Illuminate\Support\Facades\Cache;

public function debounceVia(): Repository
{
    return Cache::driver('redis');
}
```

### Het `JobDebounced`-event

Een job die door een latere dispatch is overschreven, wordt uit de queue verwijderd en vuurt het event `Illuminate\Queue\Events\JobDebounced` af. Door naar dit event te luisteren kun je gedebouncede jobs volgen en monitoren.

***

## Welke moet je gebruiken?

```mermaid theme={null}
flowchart TD
    A["Dezelfde job kan meerdere keren worden gedispatcht"] --> B{"Snel opeenvolgende dispatches,<br>alleen de laatste uitvoeren?"}
    B -->|Yes| C["#[DebounceFor]"]
    B -->|No| D{"Wil je maar één exemplaar<br>in de queue hebben?"}
    D -->|Yes| E{"Ook duplicaten tijdens<br>verwerking voorkomen?"}
    E -->|Yes| F["ShouldBeUnique"]
    E -->|No| G["ShouldBeUniqueUntilProcessing"]
    D -->|No| H["Gewone job"]
```

| Use case                                                              | Aanbevolen                      |
| --------------------------------------------------------------------- | ------------------------------- |
| Twee of meer identieke bewerkingen in de queue hebben geen zin        | `ShouldBeUnique`                |
| Ook tijdens de verwerking parallelle uitvoering voorkomen             | `ShouldBeUnique`                |
| Direct nadat de worker de job oppakt een volgende kunnen plaatsen     | `ShouldBeUniqueUntilProcessing` |
| Slechts één keer uitvoeren, ook als de gebruiker de opslaanknop spamt | `#[DebounceFor]`                |
| De zoekindex herbouwen bij elke modelupdate (bij massale updates)     | `#[DebounceFor]` + `maxWait`    |

***

## Interne implementatie

### Het lockmechanisme van Unique Jobs

Wanneer een `ShouldBeUnique`-job wordt gedispatcht, verkrijgt Laravel intern een [atomic lock](/nl/cache#atomaire-operaties-locks) via de cache. De locksleutel heeft dit formaat:

```
laravel_unique_job:{jobklassenaam}:{uniqueId()}
```

Als de lock niet kan worden verkregen (een andere job houdt hem al vast), wordt de job niet aan de queue toegevoegd.

### De implementatie van Debounced Jobs

`DebounceFor` gebruikt intern een cache-entry die het "debouncevenster" beheert. Bij elke nieuwe dispatch gebeurt het volgende:

1. De bestaande job wordt uit de queue verwijderd (het `JobDebounced`-event wordt afgevuurd)
2. De nieuwe job wordt aan de queue toegevoegd (met een vertraging van het aantal debounceseconden)
3. De timer in de cache wordt gereset

Als `maxWait` is opgegeven, wordt ook de timestamp van de eerste dispatch vastgelegd en wordt voorkomen dat de debounce langer dan `maxWait` seconden vanaf dat moment doorloopt.

***

## Referenties

* [Officiële Laravel-documentatie — Unique Jobs](https://laravel.com/docs/queues#unique-jobs)
* [Officiële Laravel-documentatie — Debounced Jobs](https://laravel.com/docs/queues#debounced-jobs)
* [`Illuminate\Contracts\Queue\ShouldBeUnique`](https://github.com/laravel/framework/blob/13.x/src/Illuminate/Contracts/Queue/ShouldBeUnique.php)
* [`Illuminate\Contracts\Queue\ShouldBeUniqueUntilProcessing`](https://github.com/laravel/framework/blob/13.x/src/Illuminate/Contracts/Queue/ShouldBeUniqueUntilProcessing.php)
* [`Illuminate\Queue\Attributes\DebounceFor`](https://github.com/laravel/framework/blob/13.x/src/Illuminate/Queue/Attributes/DebounceFor.php)
* [`Illuminate\Queue\Attributes\UniqueFor`](https://github.com/laravel/framework/blob/13.x/src/Illuminate/Queue/Attributes/UniqueFor.php)


## Related topics

- [Queues en jobs](/nl/queues.md)
- [Laravel-updates van juli 2026](/nl/blog/changelog/202607.md)
- [Laravel-updates van maart 2026](/nl/blog/changelog/202603.md)
- [Laravel-updates van augustus 2026](/nl/blog/changelog/202608.md)
- [Laravel-updates van april 2026](/nl/blog/changelog/202604.md)
