laravel concurrency

Nelle moderne applicazioni web, la velocità non è un lusso, bensì un requisito imprescindibile.

Gli utenti desiderano che le dashboard si carichino istantaneamente, che le analisi si aggiornino in tempo reale e che i report vengano generati senza ritardi.

Tuttavia, molte applicazioni Laravel continuano ad avere problemi di prestazioni.

Il motivo?

La maggior parte delle operazioni viene eseguita in sequenza .

Il problema dell’esecuzione sequenziale

Una dashboard Laravel tipica potrebbe svolgere le seguenti funzioni:

  • Query di espansione
  • chiamate API
  • Aggregazione dei dati
  • Calcoli

Solitamente tutte queste attività si susseguono una dopo l’altra.

$userStats = $this->fetchUserStats($userId);
$recentOrders = $this->fetchRecentOrders($userId);
$apiData = $this->fetchExternalApiData($userId);

Se ogni task richiede:

  • 300 ms
  • 500 ms
  • 700 ms

allora il totale dell’esecuzione sarà 1,5 secondi

Quel ritardo si accumula rapidamente, soprattutto su larga scala.

Cos’è la Concurrency in Laravel (Concurrency::run)

Laravel 10 ha introdotto una potente funzionalità:

Concurrency::run()

Consente di eseguire più attività in parallelo all’interno della stessa richiesta, senza code.

Come Funziona Concurrency::run() in Laravel 11/12

use Illuminate\Support\Facades\Concurrency;

$results = Concurrency::run([
    fn () => $this->fetchUserStats($userId),
    fn () => $this->fetchRecentOrders($userId),
    fn () => $this->fetchExternalApiData($userId),
]);

Invece di aspettare ogni singola attività:

  • Tutte le attività vengono eseguite contemporaneamente
  • I risultati vengono restituiti in ordine

Impatto sulle Prestazioni: Benchmark Sequenziale vs Parallelo

Stessi task:

  • 300 ms
  • 500 ms
  • 700 ms

il tempo di esecuzione totale sarà il tempo di esecuzione più lungo: 700 ms (non 1,5 s)

Ciò significa un’accelerazione superiore al 50% , con un miglioramento immediato dell’esperienza utente.

Perché è importante

1. Dashboard più veloci

Tutti i widget vengono caricati simultaneamente anziché attendere in coda.

2. Chiamate API parallele

Basta con le richieste a catena, lente.

3. Codice più pulito

Nessuna logica asincrona complessa o configurazione di code.

4. Esperienza in tempo reale

Tutto avviene con una singola richiesta.

Casi d’uso reali

Dashboard di analisi

$data = Concurrency::run([
    fn () => Orders::today()->count(),
    fn () => Users::active()->count(),
    fn () => Revenue::monthly()->sum('amount'),
]);

Aggregazione di API esterne

$stocks = Concurrency::run([
    fn () => Http::get('https://api1.com'),
    fn () => Http::get('https://api2.com'),
    fn () => Http::get('https://api3.com'),
]);

Generazione di report

$report = Concurrency::run([
    fn () => $this->calculateSales($month),
    fn () => $this->calculateExpenses($month),
    fn () => $this->calculateProfit($month),
]);

Quando utilizzare la concorrenza

Ideale per:

  • Query di espansione
  • richieste API
  • Operazioni sui file
  • Aggregazioni

Evitare per:

  • Attività che richiedono un elevato utilizzo della CPU
  • Lavori di lunga durata

Gestire gli errori in modo sicuro

fn () => try {
    return $this->fetchApi();
} catch (\Exception $e) {
    return null;
}

Limitare le attività parallele

Un numero eccessivo di operazioni simultanee può sovraccaricare il server.

Usalo in modo strategico

Non tutto richiede la concorrenza: usatela dove la latenza è un fattore critico.

Limiti e Quando NON Usare la Concorrenza in Laravel

La concorrenza è potente, ma:

  • Non sostituisce le code
  • Non gestisce i processi in background
  • Può aumentare il consumo di risorse

Utilizza le code quando hai bisogno di un’elaborazione differita .

Cos’è Concurrency::run in Laravel?

Concurrency::run è il metodo principale della Concurrency facade che permette di eseguire più funzioni (closure) contemporaneamente in processi PHP separati e separati dal thread principale.
Il metodo attende che tutti i compiti siano completati prima di restituire un array con i risultati di ciascuna funzione.

Caratteristiche principali
Esecuzione parallela: I task non aspettano il turno dell’altro ma partono insieme.
Bloccante (Sincrono): La richiesta HTTP corrente si ferma finché l’ultimo task non ha finito.
Risultato combinato: Restituisce i valori nello stesso ordine in cui hai inserito le closure

Qual è la differenza tra Concurrency e Queue in Laravel?

La differenza principale risiede nel tempo di esecuzione e nell’obiettivo: la Concurrency facade ottimizza le prestazioni immediate della richiesta attuale (sincrona), mentre le Queue gestiscono il lavoro pesante in background liberando subito l’utente (asincrona).

Quando usare Concurrency
Usa la Concurrency facade quando hai bisogno dei risultati subito per comporre la pagina web, ma vuoi eseguire i passaggi in parallelo per risparmiare tempo.
Esempio: Chiamare contemporaneamente 3 API esterne diverse (Meteo, Biglietti, Hotel) per mostrare una dashboard di viaggio completa all’utente.

Quando usare le Queue
Usa le Queue quando il task richiede tempo e all’utente non serve vedere il risultato immediato per continuare a navigare.
Esempio: Generare un report PDF pesante, inviare una email di benvenuto o elaborare un video caricato.
Se desideri, possiamo approfondire come gestire i timeout con la Concurrency facade o come monitorare i job falliti all’interno delle Queue. Quale aspetto preferisci analizzare?

Da quale versione di Laravel è disponibile la Concurrency facade?

La Concurrency facade è disponibile a partire da Laravel 11.23.
Questa funzionalità, annunciata durante il Laracon US 2024, permette di eseguire più task (closure) in parallelo per ottimizzare le performance delle operazioni più lente, come le richieste HTTP esterne o le query pesanti e indipendenti.

Concurrency::run blocca il processo principale?

Sì, Concurrency::run blocca il processo principale finché tutti i task paralleli non hanno terminato l’esecuzione.
Anche se i singoli task all’interno dell’array girano in parallelo tra loro su processi separati, il thread principale della tua richiesta HTTP (o del comando Artisan) si mette in attesa (in stato di blocking). Riprenderà l’esecuzione solo dopo che l’ultimo dei task avrà restituito il suo risultato o sarà andato in timeout.

Come funziona il blocco nel ciclo di vita:
Il processo principale arriva a Concurrency::run().
Il processo principale si ferma e genera (spawn) i processi figli in parallelo.
I processi figli eseguono i task contemporaneamente.
Il processo principale attende che tutti i figli finiscano.
Una volta completati, raccoglie i risultati e riparte per completare il resto dello script.

L’alternativa non bloccante: Concurrency::defer
Se non vuoi bloccare il processo principale e non hai bisogno dei risultati immediati nella richiesta corrente, devi usare Concurrency::defer.
Con defer, Laravel invia immediatamente la risposta HTTP all’utente e solo dopo esegue i task in background, liberando subito il browser del visitatore.
Vuoi vedere un esempio di come configurare un timeout massimo per evitare che un task bloccato fermi il processo principale troppo a lungo, o ti interessa approfondire la gestione degli errori/eccezioni nei processi figli?

Quando non usare la concorrenza in Laravel?

Anche se la concorrenza è uno strumento potente per ottimizzare i tempi di attesa, ci sono diversi scenari in cui introduce rischi, bug o spreco di risorse.
Ecco quando non usare la Concurrency facade in Laravel:

1. Quando i task dipendono l’uno dall’altro
I task avviati in parallelo non sanno cosa stanno facendo gli altri. Se il Task B ha bisogno del risultato del Task A per funzionare, la concorrenza fallirà o produrrà errori.
Esempio da evitare: Task A crea un utente nel database e Task B genera una fattura per quell’utente.

2. Con task che durano molti minuti o ore
La concorrenza mantiene la richiesta HTTP dell’utente aperta e bloccata in attesa del risultato (Concurrency::run). Se i task sono molto lunghi, il server web (come Nginx o Apache) andrà in Gateway Timeout (504).
Cosa usare invece: Per processi lunghi usa sempre le Queue (Code) con i Worker in background.

3. Quando si modificano gli stessi dati (Race Conditions)
Se due o più task paralleli cercano di aggiornare contemporaneamente lo stesso record nel database, rischi di sovrascrivere i dati o causare un blocco del database (deadlock).
Esempio da evitare: Due task che scalano contemporaneamente il saldo dallo stesso conto bancario.

4. Per operazioni ad alto consumo di CPU o Memoria
Ogni task parallelo genera un nuovo processo PHP indipendente sul server. Se lanci troppi task pesanti insieme, saturerai rapidamente la CPU e la RAM del server, rallentando l’intera applicazione per tutti gli utenti.
Esempio da evitare: Elaborare o ridimensionare 10 immagini ad alta risoluzione contemporaneamente nello stesso momento.

5. Se superi i limiti di tabelle o API esterne (Rate Limiting)
Se esegui in parallelo molte richieste verso un’API esterna (es. Stripe, OpenAI) o troppe query complesse sul database, potresti attivare i sistemi di protezione da sovraccarico.
Il rischio: L’API esterna potrebbe bloccarti temporaneamente con un errore 429 Too Many Requests.

6. Quando lo stato dell’applicazione deve essere condiviso
I task paralleli girano in processi PHP isolati. Qualsiasi modifica fatta all’interno di una closure (es. cambiare il valore di una variabile globale, modificare una sessione o aggiornare una configurazione a runtime) non sarà visibile nel processo principale o negli altri task.