Développement Web

Queues et jobs Laravel 11 : traitement asynchrone — tutoriel pas à pas

9 min de lecture

📍 Guide de référence de cette série : Laravel 11 et PHP 8.4 : installer l’environnement et maîtriser la nouvelle architecture

Toute opération longue — envoyer un email, redimensionner une image, appeler une API externe, générer un PDF — ne doit pas bloquer la réponse HTTP. Faire attendre l’utilisateur 10 secondes pendant qu’un email part est une mauvaise expérience ; faire attendre le serveur pour que le cycle requête/réponse reste rapide est une bonne architecture. Les queues Laravel résolvent ce problème : elles déportent le travail dans des workers asynchrones qui traitent les tâches en arrière-plan, sans impact sur les temps de réponse de l’API. Ce tutoriel construit un système complet de traitement asynchrone, de la configuration du driver jusqu’à la supervision en production avec Supervisor.

Prérequis

  • Projet Laravel 11 — guide d’installation
  • Base de données configurée pour le driver database (alternative : Redis installé pour le driver redis)
  • Temps estimé : 45 à 60 minutes

Étape 1 — Choisir et configurer le driver de queue

Laravel 11 supporte six drivers de queue : database (jobs stockés en table SQL), redis (in-memory, haute performance), sqs (Amazon SQS), beanstalkd, sync (exécution synchrone, parfait pour les tests) et null (jobs ignorés silencieusement). Pour un démarrage sans dépendances supplémentaires, le driver database est le plus accessible. Pour la production à fort volume, redis est recommandé.

Modifiez votre .env pour définir le driver :

# Driver database (développement et production légère)
QUEUE_CONNECTION=database

# Driver Redis (production à volume élevé — nécessite php8.4-redis et un serveur Redis)
# QUEUE_CONNECTION=redis
# REDIS_HOST=127.0.0.1
# REDIS_PORT=6379

Pour le driver database, créez la table de jobs :

# Crée la migration pour la table jobs (et optionnellement la table failed_jobs)
php artisan make:queue-table

# Exécuter les migrations
php artisan migrate

La sortie attendue : create_jobs_table .... DONE et create_failed_jobs_table .... DONE. La table jobs stocke les jobs en attente (payload sérialisé, nombre de tentatives, date de disponibilité). La table failed_jobs conserve les jobs qui ont épuisé leurs tentatives — précieux pour le débogage.

Étape 2 — Créer votre premier Job

Un Job Laravel est une classe PHP qui implémente l’interface ShouldQueue et utilise le trait Queueable. Toute la logique de la tâche asynchrone réside dans la méthode handle(). Créons un job qui envoie un email de bienvenue après l’inscription d’un utilisateur :

php artisan make:job SendWelcomeEmail
<?php
// app/Jobs/SendWelcomeEmail.php

namespace AppJobs;

use AppModelsUser;
use IlluminateContractsQueueShouldQueue;
use IlluminateFoundationQueueQueueable;
use IlluminateMailMailable;
use IlluminateSupportFacadesMail;

class SendWelcomeEmail implements ShouldQueue
{
    use Queueable;

    // Nombre maximum de tentatives si le job échoue
    public int $tries = 3;

    // Timeout en secondes avant que le job soit considéré comme bloqué
    public int $timeout = 30;

    // Attente exponentielle entre les tentatives (1min, 5min, 10min)
    public function backoff(): array
    {
        return [60, 300, 600];
    }

    public function __construct(
        public readonly User $user,
    ) {}

    public function handle(): void
    {
        Mail::to($this->user->email)->send(new WelcomeEmail($this->user));
    }

    // Appelé quand toutes les tentatives sont épuisées
    public function failed(Throwable $exception): void
    {
        Log::error("SendWelcomeEmail a échoué pour {$this->user->email}", [
            'exception' => $exception->getMessage(),
        ]);
    }
}

Trois propriétés importantes à comprendre dans ce Job. $tries = 3 indique que Laravel retente le job trois fois en cas d’échec (exception non attrapée dans handle()). $timeout = 30 définit le temps maximum d’exécution — si le job dépasse 30 secondes, le worker le marque comme failed. La méthode backoff() définit une attente exponentielle entre chaque tentative : 60 secondes, puis 300, puis 600 — évitant de bombarder un service externe déjà en difficulté. La méthode failed() est le handler de dernier recours : appelé une seule fois après tous les échecs, elle permet de logger, notifier ou déclencher une compensation.

Étape 3 — Dispatcher un Job

Dispatcher un Job signifie l’envoyer dans la queue pour traitement asynchrone. Laravel offre plusieurs méthodes de dispatch selon le cas d’usage :

<?php
// Dans un contrôleur, après l'inscription
public function register(StoreUserRequest $request): JsonResponse
{
    $user = User::create($request->validated());

    // Dispatch standard — exécuté dès qu'un worker est disponible
    SendWelcomeEmail::dispatch($user);

    // Dispatch différé — exécuté dans 5 minutes
    SendWelcomeEmail::dispatch($user)->delay(now()->addMinutes(5));

    // Dispatch conditionnel — uniquement si l'email est vérifié
    SendWelcomeEmail::dispatchIf($user->hasVerifiedEmail(), $user);

    // Dispatch sur une queue spécifique (priorité haute)
    SendWelcomeEmail::dispatch($user)->onQueue('high');

    // Dispatch synchrone — pour les tests unitaires (pas de queue)
    SendWelcomeEmail::dispatchSync($user);

    return response()->json(['message' => 'Inscription réussie.'], 201);
}

La réponse HTTP est retournée immédiatement après dispatch() — l’utilisateur ne attend pas que l’email soit envoyé. En arrière-plan, le worker (que vous lancerez à l’étape suivante) récupère le job dans la table jobs et exécute handle(). Si l’email échoue, l’utilisateur ne le voit pas — le job est retenté selon la politique backoff().

Étape 4 — Lancer le Queue Worker

Le Queue Worker est un processus PHP persistant qui surveille la queue et exécute les jobs dès qu’ils sont disponibles. En développement, vous le lancez manuellement dans un terminal séparé :

# Démarrer le worker (surveille la queue par défaut)
php artisan queue:work

# Worker avec priorité : traite "high" avant "default"
php artisan queue:work --queue=high,default

# Worker limité (utile pour les VPS avec peu de RAM)
php artisan queue:work --max-jobs=500 --max-time=3600

# Voir les jobs échoués
php artisan queue:failed

# Réessayer tous les jobs échoués
php artisan queue:retry all

# Réessayer un job spécifique (par UUID)
php artisan queue:retry 5e3d8c2a-1234-5678-abcd-ef0123456789

Le signal que le worker fonctionne : vous verrez dans le terminal les messages [2026-05-15 10:00:00] Processing: AppJobsSendWelcomeEmail suivi de Processed: AppJobsSendWelcomeEmail. En cas d’échec, le message Failed: AppJobsSendWelcomeEmail apparaît et le job est enregistré dans failed_jobs.

Étape 5 — Superviser les workers en production avec Supervisor

En production, le processus queue:work doit rester actif en permanence, même après un crash ou un redémarrage du serveur. Supervisor est un outil de gestion de processus Linux conçu exactement pour ça. Installez-le sur Ubuntu/Debian :

sudo apt-get install -y supervisor

Créez un fichier de configuration Supervisor pour votre application :

# /etc/supervisor/conf.d/laravel-worker.conf

[program:laravel-worker]
process_name=%(program_name)s_%(process_num)02d
command=php /var/www/mon-projet/artisan queue:work database --queue=high,default --sleep=3 --tries=3 --max-time=3600
autostart=true
autorestart=true
stopasgroup=true
killasgroup=true
user=www-data
numprocs=2
redirect_stderr=true
stdout_logfile=/var/www/mon-projet/storage/logs/worker.log
stopwaitsecs=3600

Activez et démarrez les workers :

sudo supervisorctl reread
sudo supervisorctl update
sudo supervisorctl start laravel-worker:*

# Vérifier le statut
sudo supervisorctl status

Le paramètre numprocs=2 lance deux workers en parallèle, doublant le débit de traitement. autorestart=true garantit que Supervisor relance le processus s’il s’arrête. --max-time=3600 redémarre le worker toutes les heures, libérant la mémoire qui s’accumule sur les processus PHP longue durée. Après chaque déploiement, signaler aux workers de redémarrer proprement : php artisan queue:restart.

Étape 6 — Job Batching : grouper des tâches et suivre leur progression

Le batching permet d’exécuter un groupe de jobs en parallèle et de réagir à leur completion collective. C’est parfait pour les imports CSV, les traitements de masse ou les pipelines de génération de rapports.

# Créer la table de batches
php artisan make:queue-batches-table
php artisan migrate
<?php
use IlluminateSupportFacadesBus;

$batch = Bus::batch([
    new ProcessInvoice($invoices[0]),
    new ProcessInvoice($invoices[1]),
    new ProcessInvoice($invoices[2]),
])->then(function (IlluminateBusBatch $batch) {
    // Toutes les factures traitées avec succès
    Log::info("Batch {$batch->id} terminé : {$batch->totalJobs} factures traitées.");
})->catch(function (IlluminateBusBatch $batch, Throwable $e) {
    // Au moins un job a échoué
    Log::error("Erreur dans le batch {$batch->id} : {$e->getMessage()}");
})->finally(function (IlluminateBusBatch $batch) {
    // Toujours exécuté, succès ou échec
    Log::info("Batch {$batch->id} finalisé. Annulé: {$batch->cancelled()}");
})->dispatch();

Erreurs fréquentes

Erreur Cause Solution
Jobs non exécutés (QUEUE_CONNECTION=sync) Le driver sync exécute les jobs immédiatement mais de façon synchrone dans le processus HTTP — il n’y a pas de queue réelle Passer à database ou redis en production
Job bloqué et jamais traité queue:work non lancé ou Supervisor arrêté Vérifier supervisorctl status ou lancer queue:work manuellement
Model non sérialisable dans le payload Le modèle Eloquent transmis au Job contient des closures ou des ressources non sérialisables Passer uniquement l’ID du modèle dans le constructeur et le recharger dans handle()
Jobs en double dans la queue Dispatch effectué deux fois (redoublement de requête) Implémenter ShouldBeUnique avec une méthode uniqueId()

FAQ

Quelle différence entre queue:work et queue:listen ?
queue:work charge le framework une seule fois et traite les jobs en mémoire — plus rapide, mais nécessite queue:restart après chaque déploiement pour voir le nouveau code. queue:listen recharge le framework à chaque job — plus lent mais reflète immédiatement les changements de code.

Redis ou database pour les queues en production ?
Redis est recommandé dès que vous avez plus de quelques dizaines de jobs par minute. Redis garde les jobs en mémoire (lecture/écriture en microsecondes) là où le driver database lit et écrit sur disque. Pour les applications légères (quelques emails par jour), le driver database est largement suffisant.

Comment monitorer les queues sans Supervisor ?
Laravel Horizon (disponible pour Redis uniquement) offre un dashboard visuel pour surveiller les workers, les jobs, les temps d’exécution et les échecs. Installez-le avec composer require laravel/horizon et accédez-y via /horizon.

Tutoriels frères dans cette série

Pour aller plus loin

Service ITSkillsCenter

Site ou application web sur mesure

Conception Pro + Nom de domaine 1 an + Hébergement 1 an + Formation + Support 6 mois. Accès et code livrés. À partir de 350 000 FCFA.

Demander un devis
Publicité