f in x
Laravel Queues: processi in background e gestione degli errori con i Job
> cd .. / HUB_EDITORIALE > Visualizza in Inglese
Sviluppo di siti web

Laravel Queues: processi in background e gestione degli errori con i Job

[2026-06-11] Author: Ing. Calogero Bono

Il tuo e-commerce impiega 8 secondi per mostrare il riepilogo dell'ordine? Il pannello di amministrazione si blocca mentre invii 200 email di notifica? È il classico sintomo di un problema che abbiamo visto decine di volte: stai eseguendo operazioni pesanti nel ciclo richiesta-risposta. Un sito non è una vetrina da ammirare: è uno strumento che deve vendere. Se il cliente aspetta, perdi. Noi, di Meteora Web, abbiamo costruito piattaforme che gestiscono migliaia di job al giorno senza far aspettare nessuno. In questa guida ti spieghiamo come funzionano le code di Laravel, come impostarle e, soprattutto, come gestire i fallimenti senza perdere dati.

Perché mettere in coda un lavoro

Immagina una cucina di ristorante. Il cameriere prende l'ordine (la richiesta HTTP) e lo passa in cucina (il job). Il cliente non aspetta che il piatto sia pronto: torna al tavolo, beve il suo vino, e intanto la cucina lavora in parallelo. Lo stesso vale per un'applicazione web. Un job in coda permette di:
Migliorare l'esperienza utente: la pagina risponde subito, anche se dietro le quinte sta girando un'elaborazione lunga (invio email, generazione PDF, elaborazione immagini).
Scalare orizzontalmente: puoi aggiungere più worker (lavoratori) per processare più job contemporaneamente, senza toccare il server web.
Gestire i picchi di carico: la coda si accumula e i worker svuotano il backlog, senza far crashare il sito.

Noi lo misuriamo in secondi di risposta: un job di invio email può passare da 3 secondi a 3 millisecondi per l'utente. Il resto viene dopo, in coda. E il cliente non se ne accorge.

Le connessioni: quale scegliere

Laravel supporta diversi driver per le code. La scelta dipende dal volume, dal budget e dall'infrastruttura.

Sponsored Protocol

Database

Il driver database usa una tabella MySQL/PostgreSQL. È il più semplice: nessun servizio esterno, solo un worker PHP. Ottimo per progetti piccoli o medi (fino a poche migliaia di job al giorno). Noi lo usiamo nei primi mesi di un progetto, quando il volume è basso e vogliamo tenere i costi a zero.
Svantaggio: scrive sul database a ogni ciclo, può diventare collo di bottiglia. Per carichi alti, meglio passare a Redis.

Redis

Driver performante: le code sono liste in memoria, velocissime. Con Horizon (il pannello di monitoring ufficiale per Redis) hai metriche, restart automatici, bilanciamento. Noi lo raccomandiamo per progetti in produzione con volumi medi/alti. Richiede Redis installato e configurato.

Amazon SQS

Soluzione serverless: nessun daemon da mantenere, paghi per uso. Ideale se sei già su AWS. Lo svantaggio è la latenza maggiore (polling ogni X secondi) e la dipendenza da un servizio esterno.

Regola pratica: inizia con database, passa a Redis quando il carico cresce. Non sottovalutare la configurazione del worker in produzione con Supervisor per mantenere il worker sempre attivo.

Creare e spedire un Job

Un job in Laravel è una classe che implementa l'interfaccia ShouldQueue.

php artisan make:job ProcessOrder

La struttura base:

<?php

namespace App\Jobs;

use App\Models\Order;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;

class ProcessOrder implements ShouldQueue
{
    use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;

    public $order;

    public function __construct(Order $order)
    {
        $this->order = $order;
    }

    public function handle()
    {
        // Logica pesante: calcoli, email, notifiche
        $this->order->markAsProcessed();
        // Invia email di conferma
        Mail::to($this->order->user)->send(new OrderConfirmation($this->order));
    }
}

Per spedirlo in coda:

Sponsored Protocol

ProcessOrder::dispatch($order);

Se vuoi eseguirlo subito senza coda (utile per test):

ProcessOrder::dispatchSync($order);

Oppure con ritardo:

ProcessOrder::dispatch($order)->delay(now()->addMinutes(10));

Dati da serializzare

Quando un job viene messo in coda, Laravel serializza le proprietà pubbliche e usa SerializesModels per i modelli Eloquent. Questo significa che il modello viene recuperato dal database al momento dell'esecuzione, non al momento della dispatch. Così eviti che i dati diventino obsoleti.

Il worker: chi esegue i lavori

Il worker è un processo PHP che ascolta la coda ed esegue i job. Lo avvii con:

php artisan queue:work

Opzioni fondamentali:
- --queue=high,default,low: priorità delle code
- --tries=3: numero di tentativi massimi
- --delay=5: secondi di attesa prima di riprovare un job fallito
- --sleep=3: secondi di pausa quando non ci sono job (per risparmiare CPU)

In produzione, non lanciare queue:work a mano. Usa Supervisor per mantenerlo sempre attivo. Un esempio di configurazione:

[program:laravel-worker]
process_name=%(program_name)s_%(process_num)02d
command=php /var/www/html/artisan queue:work redis --sleep=3 --tries=3 --max-time=3600
autostart=true
autorestart=true
user=forge
numprocs=4
redirect_stderr=true
stdout_logfile=/var/log/worker.log

Con numprocs=4 hai 4 worker in parallello sulla stessa coda. Aumentali quando il carico sale.

Sponsored Protocol

Gestire i fallimenti: tentativi, backoff e dead letter

I job falliscono. Può essere un errore temporaneo (database down, API esterna timeout) o permanente (dati corrotti, logica sbagliata). Laravel fornisce strumenti per gestire entrambi i casi.

Tentativi e backoff

Imposta $tries nella classe job per il massimo numero di tentativi:

public $tries = 5;

Il backoff (intervallo tra un tentativo e l'altro) può essere fisso o con backoff dinamico se usi il trait InteractsWithQueue:

public function backoff()
{
    return [10, 30, 60, 120, 300]; // secondi
}

Il primo tentativo dopo 10 secondi, poi 30, 60, ecc. Utile per API che impongono rate limiting.

Max exceptions

Se un job lancia eccezioni ma vuoi che venga ritentato solo dopo un certo numero di fallimenti specifici, usa $maxExceptions. Per esempio, se un servizio esterno è giù, non ha senso riprovare all'infinito.

RetryUntil

Puoi impostare una scadenza temporale massima per un job, dopo la quale viene definitivamente marcato come fallito:

public function retryUntil()
{
    return now()->addMinutes(30);
}

Utile per job sensibili al tempo (es. conferma prenotazione).

Il metodo failed()

Sovrascrivi il metodo failed per eseguire azioni quando un job esaurisce i tentativi (es. notificare l'amministratore, registrare un log, scrivere in una tabella di errori).

public function failed(\Throwable $exception)
{
    Log::error('Order processing failed: ' . $this->order->id);
    // Invia una notifica a Slack o email
    Notification::send(User::admin(), new JobFailedNotification($this->order));
}

Tabella failed_jobs

Per default, i job falliti vengono salvati nella tabella failed_jobs. Puoi visualizzarli con:

Sponsored Protocol

php artisan queue:failed

E ritentare tutti i falliti:

php artisan queue:retry all

Oppure eliminarli:

php artisan queue:flush

Questa tabella è il tuo "dead letter queue" di Laravel. Monitorala regolarmente. Noi, di Meteora Web, abbiamo visto aziende perderci ordini perché non controllavano i job falliti. Un crontab che invia un report giornaliero dei failed jobs è una buona pratica.

Middleware per job: log, rate limit, blocchi

Laravel permette di applicare middleware ai job, esattamente come per le route. Alcuni utili built-in:

  • ThrottlesExceptions: evita che uno stesso job fallisca ripetutamente a causa di eccezioni temporanee (es. 429 Too Many Requests).
  • RateLimited: limita il numero di job eseguiti per unità di tempo, pratico per API con limiti.
  • WithoutOverlapping: impedisce che lo stesso job (basato su chiave) venga eseguito concorrentemente. Utile per processi che non devono sovrapporsi, come la sincronizzazione con un sistema esterno.

Esempio con ThrottlesExceptions:

use Illuminate\Queue\Middleware\ThrottlesExceptions;

public function middleware()
{
    return [
        (new ThrottlesExceptions(5, 10))->backoff(2),
    ];
}

Permette 5 tentativi in 10 minuti, con backoff di 2 secondi tra tentativi. Questo evita di sovraccaricare un servizio instabile.

Monitoraggio in produzione con Horizon

Se usi Redis, Laravel Horizon è un must. Fornisce una dashboard con metriche in tempo reale: job in esecuzione, completati, falliti. Puoi riavviare i worker, vedere il tempo medio di esecuzione, e impostare bilanciamento automatico tra code con priorità. Noi lo installiamo in ogni progetto che supera le centinaia di job al giorno. La configurazione di Horizon nel file config/horizon.php permette di definire "supervisori" e "code” con pesi diversi.

Sponsored Protocol

Errori comuni e come evitarli

  • Mancata configurazione del worker in produzione: se usi solo queue:work in un terminale, il worker muore appena chiudi la sessione. Usa sempre Supervisor.
  • Ignorare i job falliti: un job fallito non viene riprocessato se non lo ritenti. Imposta un alert (email, Slack) per i fallimenti.
  • Serializzare oggetti non serializzabili: evita di passare risorse (file handle, connessioni) come proprietà dei job. Passa ID e recupera i modelli all'interno del handle().
  • Non testare i job in isolamento: usa Queue::fake() nei test per assicurarti che il job venga dispatched, ma testa anche l'esecuzione effettiva con un test di integrazione.
  • Confondere dispatch e dispatchNow: dispatchNow esegue il job sincrono, bypassando la coda. Usalo solo per test o in contesti dove la coda non è disponibile.

In sintesi — cosa fare adesso

  1. Identifica le operazioni lente nella tua applicazione (invio email, elaborazione file, chiamate API esterne).
  2. Crea un job per ciascuna e sposta la logica nel metodo handle().
  3. Scegli il driver di coda: database per iniziare, Redis per produzione.
  4. Configura Supervisor per mantenere attivo il worker.
  5. Imposta $tries e backoff su ogni job.
  6. Monitora la tabella failed_jobs e imposta notifiche per i fallimenti.
  7. Valuta Horizon se usi Redis per un controllo più granulare.

Un sito non è un'abitudine, è uno strumento. Se le code sono ben configurate, il tempo di risposta scende, l'utente è contento e tu hai un'applicazione che scala. Noi abbiamo visto clienti passare da un tempo di caricamento di 12 secondi a meno di 1 secondo spostando tre job in coda. I numeri parlano.

Ing. Calogero Bono

> AUTHOR_EXTRACTED

Ing. Calogero Bono

Ingegnere Informatico, co-fondatore di Meteora Web. Esperto in architetture software, sicurezza informatica e sviluppo sistemi scalabili.
[ Read Full Dossier ]

> METEORA_WEB // WEB AGENCY

Costruiamo la presenza digitale che la tua azienda merita.

Siti web, social, pubblicità online, e-commerce e hosting performante: ingegnerizzati con metodo da ingegneri informatici a Sciacca, per tutta Italia.

> MW_JOURNAL

> READ_ALL()