Vendredi soir, la boutique affiche « commande confirmée ». L'e-mail de confirmation, lui, n'est jamais parti. Lundi matin, le support découvre une table jobs vide côté application — mais centaines d'entrées dans failed_jobs, toutes liées à un timeout SMTP après la montée de version du week-end.
Ce scénario est banal : l'interface web répond vite parce que Laravel a bien dispatché le job, mais personne ne surveillait la consommation ni les échecs. Les queues ne posent pas problème quand tout va bien. Elles deviennent visibles quand un worker s'arrête, qu'un driver mal configuré bascule en sync, ou qu'un retry agressif amplifie une panne externe.
Driver, connexion et ce que Laravel exécute vraiment
Un job Laravel n'est pas magique : c'est un message sérialisé (classe, payload, tentatives) stocké dans un backend — Redis, base de données, SQS, Beanstalkd. Le worker (queue:work ou Horizon) le dépile, instancie la classe, exécute handle(), puis acquitte ou marque l'échec.
| Driver | Quand l'utiliser | Limite fréquente |
|---|---|---|
redis | Production multi-workers, faible latence | Redis doit être persistant et monitoré |
database | Petit volume, pas de broker | Contention sur la table jobs à forte charge |
sqs | Cloud AWS, découplage régional | Coût et visibilité des DLQ à configurer |
sync | Tests locaux uniquement | Exécution inline — aucune résilience |
Sur un hébergement mutualisé, Redis managé ou un petit VPS dédié au broker évite de mélanger charge web et consommation de jobs. Sur un VPS unique, documentez qui redémarre le worker après un deploy : un supervisor mal rechargé laisse la queue grossir sans erreur visible côté HTTP.
Un site qui répond en 200 ms peut être en panne fonctionnelle si ses workers sont arrêtés depuis la veille.
Retries, backoff et jobs idempotents
Laravel retente automatiquement les jobs qui lèvent une exception, selon $tries, $backoff ou $retryUntil. C'est utile pour un API tiers temporairement indisponible. C'est dangereux pour un job qui envoie de l'argent, crée une facture ou appelle un webhook non idempotent.
Règles pragmatiques :
- Backoff exponentiel plutôt que dix tentatives en dix secondes sur la même erreur structurelle.
$maxExceptionspour stopper un job défectueux avant d'épuiser la file.- Idempotence : un second passage ne doit pas doubler l'effet (clé unique en base, verrou Redis, statut « déjà traité »).
Les jobs ShouldBeUnique et les middlewares de rate limiting évitent aussi les tempêtes quand cent mille commandes déclenchent le même traitement.
Failed jobs, Horizon et alertes qui comptent
La table failed_jobs (ou l'écran Horizon) est votre file d'attente des oublis. Beaucoup d'équipes l'activent en migration puis ne la consultent jamais.
Mettre en place :
- Alerte si
failed_jobs> 0 sur les queues critiques (payments,notifications). - Dashboard Horizon ou métrique Prometheus sur
queue_size,jobs_processed,failed_jobs_total. - Procédure : qui relance (
queue:retry all), qui supprime après correction, qui trace l'incident.
Horizon centralise la supervision Redis : temps d'attente, débit, workers actifs. Sans Horizon, un script cron qui vérifie l'âge du job le plus ancien dans Redis (LLEN, LRANGE) détecte déjà un worker mort.
Déploiement, Supervisor et le piège du worker zombie
Un deploy qui remplace le code sans redémarrer les workers exécute parfois l'ancienne version des jobs pendant des heures. Laravel documente queue:restart pour signaler un graceful reload.
Checklist deploy :
php artisan queue:restartaprès mise à jour du code.- Supervisor (ou systemd) avec
autorestart=true. - Séparer les queues lourdes (
--queue=default,emails) pour ne pas bloquer les jobs urgents.
Sur Docker Compose ou Kubernetes, un worker n'est pas « juste un conteneur de plus » : il doit avoir la même version d'image que l'app, les mêmes variables d'environnement, et un health check qui vérifie qu'il consomme bien — pas seulement qu'il tourne.
Le sommet : la queue masque une panne métier
Voici ce que les tutos « Laravel queues en 5 minutes » omettent.
La queue est une promesse différée. Sans observabilité et sans culture des failed_jobs, vous externalisez le problème vers le support client ou vers un partenaire qui ne recevra jamais votre callback.
Décider et avancer sans angle mort
Cartographiez vos jobs : lesquels sont critiques, lesquels tolèrent du retard, lesquels doivent être idempotents. Choisissez un driver adapté à votre hébergement (Redis sur VPS, SQS en cloud). Configurez retries avec backoff, alertes sur les échecs, et queue:restart dans votre pipeline de deploy.
Pour comparer des offres avec Redis managé, workers dédiés ou monitoring inclus, parcourez notre annuaire et le comparateur. Les guides et articles par usage aident à dimensionner l'infra autour de votre stack PHP.
Test concret : tuez un worker en staging, dispatchéz dix jobs, vérifiez alerte et contenu de failed_jobs. Si personne n'est prévenu en quinze minutes, la queue n'est pas encore opérationnelle.
Questions fréquentes
Quel driver de queue choisir avec Laravel ?
Redis ou SQS pour la production avec plusieurs workers ; database convient aux petits volumes. Le driver sync ne doit jamais rester en production : il masque concurrence et timeouts.
Que faire des jobs qui échouent après tous les retries ?
Ils atterrissent dans failed_jobs. Alertez, analysez, corrigez, puis relancez avec queue:retry ou supprimez après traçabilité — ne les laissez pas s'accumuler.
Faut-il Horizon sur un petit VPS ?
Dès que plusieurs workers ou des jobs critiques entrent en jeu, Horizon simplifie la supervision. Sur un seul process, un cron de santé et des alertes sur failed_jobs peuvent suffire au départ.
Comment dimensionner le nombre de workers ?
Séparez les queues par type de charge et mesurez le job le plus lent. Multiplier les workers sur une queue bloquée par des appels externes ne crée que plus de connexions parallèles, pas plus de débit utile.
La prochaine fois qu'un deploy « réussit », vérifiez une chose : est-ce qu'un worker traite encore la queue — et est-ce que les échecs remontent quelque part ?