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

# Controllo dell'esecuzione dei job in coda

> Come controllare l'esecuzione duplicata e i dispatch consecutivi dei job in coda usando ShouldBeUnique, ShouldBeUniqueUntilProcessing e DebounceFor.

## Panoramica

Le code di Laravel offrono due tipi di controllo dell'esecuzione dei job: la **deduplicazione (Unique)** e il **debounce**. Entrambi servono a "evitare esecuzioni inutili quando lo stesso job viene dispatchato più volte", ma si comportano in modo diverso.

| Funzionalità            | Interfaccia / attributo         | Scopo                                                                                |
| ----------------------- | ------------------------------- | ------------------------------------------------------------------------------------ |
| Unique Jobs             | `ShouldBeUnique`                | Mantiene sulla coda una sola istanza dello stesso job                                |
| Unique Until Processing | `ShouldBeUniqueUntilProcessing` | Mantiene il vincolo di unicità solo fino all'inizio dell'elaborazione                |
| Debounced Jobs          | `#[DebounceFor]`                | Se dispatchato consecutivamente in un breve intervallo, esegue solo l'ultima istanza |

<Warning>
  Unique Jobs e Debounced Jobs sono **mutuamente esclusivi**. Non implementare `ShouldBeUnique` sui job che usano l'attributo `DebounceFor`.
</Warning>

***

## Unique Jobs — `ShouldBeUnique`

Finché uno stesso job è presente in coda, i dispatch aggiuntivi vengono ignorati.

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

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

class UpdateSearchIndex implements ShouldQueue, ShouldBeUnique
{
    // non servono metodi aggiuntivi
}
```

Mentre `UpdateSearchIndex` è in coda (o in elaborazione), il tentativo di dispatchare lo stesso job viene ignorato.

### Restringere il vincolo di unicità tramite chiave — `UniqueFor` + `uniqueId()`

Se all'interno della stessa classe di job vuoi trattare "aggiornamento del prodotto A" e "aggiornamento del prodotto B" come job distinti, definisci una chiave con il metodo `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)] // il lock viene rilasciato automaticamente dopo 1 ora
class UpdateSearchIndex implements ShouldQueue, ShouldBeUnique
{
    public function __construct(public readonly int $productId)
    {
    }

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

* Il valore restituito da `uniqueId()` diventa la chiave del lock di cache.
* Specificando `#[UniqueFor(secondi)]`, dopo tale numero di secondi il lock viene rilasciato automaticamente (fail-safe nel caso il job non venga elaborato).

### Specificare il driver di cache — `uniqueVia()`

Se vuoi usare un driver di cache diverso da quello predefinito, implementa `uniqueVia()`.

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

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

<Info>
  Gli Unique Jobs richiedono un driver di cache che supporti i lock atomici (`redis`, `database`, `memcached`, `dynamodb`, `file`, `array`).
</Info>

***

## `ShouldBeUnique` vs `ShouldBeUniqueUntilProcessing`

Il lock di `ShouldBeUnique` viene mantenuto **fino a quando il job è completato o raggiunge il limite di retry**. Ci sono casi in cui questo diventa un problema.

**Esempio:** in coda c'è un `UpdateSearchIndex(product_id: 42)` e vuoi ridispacciare lo stesso job subito dopo che il worker inizia a processarlo. Con `ShouldBeUnique` il secondo dispatch non entra in coda fino al completamento del primo.

In questo caso usa `ShouldBeUniqueUntilProcessing`. Il lock viene **rilasciato appena prima dell'inizio dell'elaborazione**, quindi nel momento in cui il worker preleva il job è possibile un nuovo dispatch.

```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 Coda
    participant W as Worker

    D->>Q: dispatch() — acquisisce il lock
    D->>Q: dispatch() — lock presente → ignorato

    note over Q,W: caso ShouldBeUnique
    W->>Q: preleva il job
    W->>W: in elaborazione (lock mantenuto)
    D->>Q: dispatch() — lock presente → ignorato
    W->>W: elaborazione completata — lock rilasciato
    D->>Q: dispatch() — solo ora può essere accodato

    note over Q,W: caso ShouldBeUniqueUntilProcessing
    W->>Q: preleva il job — lock rilasciato
    D->>Q: dispatch() — nessun lock → può essere accodato
    W->>W: in elaborazione
```

### Riepilogo del confronto

|                                           | `ShouldBeUnique`                                 | `ShouldBeUniqueUntilProcessing`                             |
| ----------------------------------------- | ------------------------------------------------ | ----------------------------------------------------------- |
| Momento del rilascio del lock             | Dopo completamento / fallimento                  | Appena prima dell'inizio dell'elaborazione                  |
| Dispatch duplicato durante l'elaborazione | Ignorato                                         | Accodabile                                                  |
| Caso d'uso                                | Prevenire completamente l'esecuzione concorrente | Voler accodare il job successivo subito dopo l'elaborazione |

***

## Debounced Jobs — `#[DebounceFor]`

<Info>
  L'attributo `DebounceFor` è una funzionalità aggiunta in Laravel 13.
</Info>

Se lo stesso job viene dispatchato molte volte in un breve intervallo, viene eseguito **solo l'ultimo dispatch**. È lo stesso concetto del debounce del frontend web.

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

namespace App\Jobs;

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

#[DebounceFor(30)] // i ridispatch entro 30 secondi vengono ignorati (viene eseguito solo l'ultimo)
class UpdateSearchIndex implements ShouldQueue
{
    use Queueable;

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

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

* Il valore restituito da `debounceId()` identifica il job (viene applicato un debounce indipendente per ogni ID prodotto).
* Se lo stesso `productId` viene dispatchato 10 volte in 30 secondi, viene eseguito solo l'ultimo.

### `maxWait` — limite massimo del tempo di attesa

Su dati aggiornati frequentemente, il debounce potrebbe continuare all'infinito e il job non venire mai eseguito. Con `maxWait` puoi impostare il ritardo massimo.

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

In questo esempio, dal primo dispatch il job viene eseguito comunque al massimo entro 120 secondi (anche se il debounce di 30 secondi continua, va in timeout a 120 secondi).

### Specificare il driver di cache — `debounceVia()`

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

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

### Evento `JobDebounced`

I job sovrascritti da un dispatch successivo emettono l'evento `Illuminate\Queue\Events\JobDebounced` e vengono rimossi dalla coda. Ascoltando questo evento puoi tracciare e monitorare i job che hanno subito il debounce.

***

## Quale usare

```mermaid theme={null}
flowchart TD
    A["Lo stesso job può essere dispatchato più volte"] --> B{"Dispatch consecutivi in breve tempo:<br>vuoi eseguire solo l'ultimo?"}
    B -->|Sì| C["#[DebounceFor]"]
    B -->|No| D{"Vuoi che ne esista<br>solo uno in coda?"}
    D -->|Sì| E{"Vuoi evitare duplicati<br>anche durante l'elaborazione?"}
    E -->|Sì| F["ShouldBeUnique"]
    E -->|No| G["ShouldBeUniqueUntilProcessing"]
    D -->|No| H["Job normale"]
```

| Caso d'uso                                                                                    | Consigliato                     |
| --------------------------------------------------------------------------------------------- | ------------------------------- |
| Non ha senso avere più copie dello stesso job in coda                                         | `ShouldBeUnique`                |
| Vuoi evitare l'esecuzione parallela, elaborazione inclusa                                     | `ShouldBeUnique`                |
| Vuoi accodare il job successivo appena il worker preleva il precedente                        | `ShouldBeUniqueUntilProcessing` |
| Vuoi eseguire una sola volta anche se l'utente clicca ripetutamente "salva"                   | `#[DebounceFor]`                |
| Ricostruire l'indice di ricerca ad ogni update del modello (in caso di aggiornamenti massivi) | `#[DebounceFor]` + `maxWait`    |

***

## Implementazione interna

### Meccanismo di lock degli Unique Jobs

Quando viene dispatchato un job `ShouldBeUnique`, Laravel acquisisce internamente un [lock atomico](/it/cache#operazioni-atomiche-lock) sulla cache. La chiave del lock ha il seguente formato:

```
laravel_unique_job:{nome della classe del job}:{uniqueId()}
```

Se il lock non può essere acquisito (perché è già tenuto da un altro job), il job non viene aggiunto alla coda.

### Implementazione dei Debounced Jobs

`DebounceFor` internamente usa una entry di cache per gestire la "finestra di debounce". Ad ogni nuovo dispatch:

1. Rimuove il job esistente dalla coda (emette l'evento `JobDebounced`)
2. Aggiunge il nuovo job alla coda (con un ritardo pari ai secondi di debounce)
3. Resetta il timer nella cache

Se è specificato `maxWait`, viene registrato anche il timestamp del primo dispatch, così da impedire che il debounce superi `maxWait` secondi da quel momento.

***

## Riferimenti

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

- [Tecniche pratiche di Laravel Telescope](/it/blog/telescope-introduction.md)
- [Laravel Telescope](/it/telescope.md)
- [Casi d'uso pratici di Laravel Pennant](/it/blog/laravel-pennant.md)
- [Context](/it/context.md)
- [Laravel Cloud — il PaaS dedicato a Laravel](/it/blog/laravel-cloud.md)
