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

# Ausführungssteuerung von Queue-Jobs

> Erläutert, wie Sie mit ShouldBeUnique, ShouldBeUniqueUntilProcessing und DebounceFor mehrfache Ausführungen und aufeinanderfolgende Dispatches von Queue-Jobs steuern.

## Überblick

Die Queue-Funktionen von Laravel bieten zwei Arten der Ausführungssteuerung: **Deduplizierung (Unique)** und **Debouncing**. Beide dienen dazu, „bei mehrfachem Dispatchen desselben Jobs unnötige Ausführungen zu vermeiden", verhalten sich jedoch unterschiedlich.

| Funktion                | Interface / Attribut            | Zweck                                                                         |
| ----------------------- | ------------------------------- | ----------------------------------------------------------------------------- |
| Unique Jobs             | `ShouldBeUnique`                | Sicherstellen, dass in der Queue nur ein Exemplar desselben Jobs existiert    |
| Unique Until Processing | `ShouldBeUniqueUntilProcessing` | Eindeutigkeitsbeschränkung nur bis zum Beginn der Verarbeitung                |
| Debounced Jobs          | `#[DebounceFor]`                | Bei mehreren Dispatches in kurzer Zeit nur den zuletzt eingereihten ausführen |

<Warning>
  Unique Jobs und Debounced Jobs sind **gegenseitig ausschließend**. Implementieren Sie in Jobs, die das Attribut `DebounceFor` verwenden, nicht `ShouldBeUnique`.
</Warning>

***

## Unique Jobs — `ShouldBeUnique`

Solange ein identischer Job in der Queue vorhanden ist, werden weitere Dispatches ignoriert.

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

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

class UpdateSearchIndex implements ShouldQueue, ShouldBeUnique
{
    // Zusätzliche Methoden sind nicht erforderlich
}
```

Solange `UpdateSearchIndex` in der Queue liegt (oder gerade verarbeitet wird), werden Versuche, denselben Job zu dispatchen, ignoriert.

### Eindeutigkeitsbeschränkung mit einem Schlüssel einschränken — `UniqueFor` + `uniqueId()`

Wenn Sie in derselben Job-Klasse „Update von Produkt A" und „Update von Produkt B" als separate Jobs behandeln möchten, definieren Sie den Schlüssel über die Methode `uniqueId()`.

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

namespace App\Jobs;

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

#[UniqueFor(3600)] // Sperre wird nach einer Stunde automatisch aufgehoben
class UpdateSearchIndex implements ShouldQueue, ShouldBeUnique
{
    public function __construct(public readonly int $productId)
    {
    }

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

* Der von `uniqueId()` zurückgegebene Wert dient als Schlüssel für den Cache-Lock.
* Wird `#[UniqueFor(Sekunden)]` angegeben, wird die Sperre nach Ablauf dieser Sekunden automatisch aufgehoben (Failsafe für Jobs, die nicht verarbeitet werden).

### Cache-Treiber angeben — `uniqueVia()`

Wenn Sie einen anderen als den Standard-Cache-Treiber verwenden möchten, implementieren Sie `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 benötigen einen Cache-Treiber, der atomare Locks unterstützt (`redis`, `database`, `memcached`, `dynamodb`, `file`, `array`).
</Info>

***

## `ShouldBeUnique` vs. `ShouldBeUniqueUntilProcessing`

Der Lock von `ShouldBeUnique` bleibt bestehen, **bis der Job abgeschlossen oder das Retry-Limit erreicht ist**. Das kann in bestimmten Fällen zum Problem werden.

**Beispiel:** In der Queue liegt ein `UpdateSearchIndex(product_id: 42)`, und Sie möchten unmittelbar nach Beginn der Verarbeitung denselben Job erneut dispatchen. Mit `ShouldBeUnique` gelangt der zweite Job erst nach Abschluss der Verarbeitung in die Queue.

In diesem Fall verwenden Sie `ShouldBeUniqueUntilProcessing`. Da der Lock **unmittelbar vor Beginn der Verarbeitung** freigegeben wird, ist ein erneuter Dispatch möglich, sobald der Worker den Job entnimmt.

```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 erworben
    D->>Q: dispatch() — Lock vorhanden → ignoriert

    note over Q,W: Bei ShouldBeUnique
    W->>Q: Job entnommen
    W->>W: In Verarbeitung (Lock gehalten)
    D->>Q: dispatch() — Lock vorhanden → ignoriert
    W->>W: Verarbeitung abgeschlossen — Lock freigegeben
    D->>Q: dispatch() — jetzt erst einreihbar

    note over Q,W: Bei ShouldBeUniqueUntilProcessing
    W->>Q: Job entnommen — Lock freigegeben
    D->>Q: dispatch() — kein Lock → einreihbar
    W->>W: In Verarbeitung
```

### Zusammenfassender Vergleich

|                                             | `ShouldBeUnique`                            | `ShouldBeUniqueUntilProcessing`                         |
| ------------------------------------------- | ------------------------------------------- | ------------------------------------------------------- |
| Zeitpunkt der Lock-Freigabe                 | Nach Abschluss / Fehlschlag                 | Unmittelbar vor Verarbeitungsbeginn                     |
| Doppelter Dispatch während der Verarbeitung | Ignoriert                                   | Einreihbar                                              |
| Anwendungsfall                              | Parallele Ausführung vollständig verhindern | Nach der Verarbeitung sofort den nächsten Job einreihen |

***

## Debounced Jobs — `#[DebounceFor]`

<Info>
  Das Attribut `DebounceFor` ist eine in Laravel 13 hinzugefügte Funktion.
</Info>

Wird derselbe Job in kurzer Zeit viele Male dispatched, wird **nur der zuletzt eingereihte Job** ausgeführt. Es handelt sich um dasselbe Konzept wie das Debouncing im Web-Frontend.

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

namespace App\Jobs;

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

#[DebounceFor(30)] // Innerhalb von 30 Sekunden erneut dispatchen wird ignoriert (nur der zuletzt eingereihte Job wird ausgeführt)
class UpdateSearchIndex implements ShouldQueue
{
    use Queueable;

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

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

* Der von `debounceId()` zurückgegebene Wert identifiziert den Job (das Debouncing wird pro Produkt-ID unabhängig angewendet).
* Selbst wenn innerhalb von 30 Sekunden 10-mal mit derselben `productId` dispatched wird, wird nur der letzte Job ausgeführt.

### `maxWait` — Obergrenze für die Wartezeit

Bei häufig aktualisierten Daten könnte das Debouncing endlos weitergehen und der Job nie ausgeführt werden. Mit `maxWait` legen Sie eine maximale Verzögerung fest.

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

In diesem Beispiel wird der Job spätestens 120 Sekunden nach dem ersten Dispatch garantiert ausgeführt (selbst wenn das 30-Sekunden-Debouncing andauert, kommt es nach 120 Sekunden zum Timeout).

### Cache-Treiber angeben — `debounceVia()`

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

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

### `JobDebounced`-Event

Ein Job, der von einem nachfolgenden Dispatch überschrieben wurde, wird aus der Queue entfernt und löst das Event `Illuminate\Queue\Events\JobDebounced` aus. Indem Sie dieses Event abonnieren, können Sie zurückgesetzte Jobs verfolgen und überwachen.

***

## Welche Variante sollten Sie wählen?

```mermaid theme={null}
flowchart TD
    A["Der gleiche Job kann mehrfach dispatched werden"] --> B{"Bei kurzen Dispatch-Serien<br>nur den letzten Job ausführen?"}
    B -->|Ja| C["#[DebounceFor]"]
    B -->|Nein| D{"Nur ein Exemplar<br>in der Queue erlauben?"}
    D -->|Ja| E{"Auch während der Verarbeitung<br>Duplikate verhindern?"}
    E -->|Ja| F["ShouldBeUnique"]
    E -->|Nein| G["ShouldBeUniqueUntilProcessing"]
    D -->|Nein| H["Normaler Job"]
```

| Anwendungsfall                                                               | Empfehlung                      |
| ---------------------------------------------------------------------------- | ------------------------------- |
| Zwei oder mehr identische Verarbeitungen in der Queue ergeben keinen Sinn    | `ShouldBeUnique`                |
| Parallele Ausführung einschließlich der Verarbeitung verhindern              | `ShouldBeUnique`                |
| Sobald der Worker den Job entnimmt, den nächsten einreihen                   | `ShouldBeUniqueUntilProcessing` |
| Auch wenn Nutzer den Speichern-Button mehrfach drücken, nur einmal ausführen | `#[DebounceFor]`                |
| Bei jeder Modelländerung Suchindex neu aufbauen (bei Massenupdates)          | `#[DebounceFor]` + `maxWait`    |

***

## Interne Implementierung

### Lock-Mechanismus von Unique Jobs

Wenn ein `ShouldBeUnique`-Job dispatched wird, holt Laravel intern einen [atomaren Lock](/de/cache#atomare-operationen-locks) über den Cache. Der Lock-Schlüssel hat folgendes Format:

```
laravel_unique_job:{Job-Klassenname}:{uniqueId()}
```

Kann der Lock nicht erworben werden (weil ein anderer Job ihn hält), wird der Job nicht in die Queue aufgenommen.

### Implementierung von Debounced Jobs

`DebounceFor` verwendet intern einen Cache-Eintrag, der das „Debounce-Fenster" verwaltet. Bei jedem neuen Dispatch geschieht Folgendes:

1. Der bestehende Job wird aus der Queue entfernt (`JobDebounced`-Event wird ausgelöst).
2. Der neue Job wird in die Queue aufgenommen (mit der Verzögerung der Debounce-Sekunden).
3. Der Timer im Cache wird zurückgesetzt.

Ist `maxWait` angegeben, wird zusätzlich der Zeitstempel des ersten Dispatches erfasst, um zu verhindern, dass das Debouncing die `maxWait`-Sekunden ab diesem Zeitpunkt überschreitet.

***

## Weiterführende Links

* [Offizielle Laravel-Dokumentation — Unique Jobs](https://laravel.com/docs/queues#unique-jobs)
* [Offizielle Laravel-Dokumentation — 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

- [Laravel-Updates April 2026](/de/blog/changelog/202604.md)
- [Queues und Jobs](/de/queues.md)
- [Upgrade-Anleitung von Laravel 12 auf 13](/de/blog/upgrade-12-to-13.md)
- [Laravel Horizon](/de/horizon.md)
- [Einführung in Laravel Nightwatch](/de/blog/nightwatch-introduction.md)
