Optimiser les performances d'une application Laravel : le guide pratique
La performance Laravel est une séquence, pas un tour de magie : mesurer, corriger les requêtes, puis recourir au cache, aux queues et à Octane. Voici l'ordre complet avec du code à chaque étape.
Le travail de performance Laravel est une séquence, pas un sac d'astuces. On mesure pour trouver le vrai goulot, on corrige l'accès à la base qui cause l'essentiel de la lenteur, puis on superpose cache, queues et enfin Octane. Faire les choses dans cet ordre fait que chaque étape amplifie la précédente au lieu de masquer un problème jamais corrigé.
Mesurer avant d'optimiser
Ne devinez jamais un goulot. Utilisez Telescope en staging pour inspecter en détail requêtes, requests et jobs (gardez-le derrière une authentification, jamais ouvert en production). Utilisez Debugbar ou Clockwork en local pour voir le nombre de requêtes et le timing par requête. En production, le slow query log de la base et une sonde rapide DB::listen vous disent quelles requêtes font vraiment mal.
Requêtes N+1 et eager loading
La cause la plus fréquente d'un endpoint Laravel lent est une boucle qui charge une relation en lazy, transformant une page en des dizaines de requêtes quasi identiques. Corrigez avec with, withCount et loadMissing, et hors production appelez preventLazyLoading pour que tout lazy load lève une exception et que le problème apparaisse dans les tests plutôt qu'en production.
// N+1 : une requête pour les commandes, puis une par commande pour le client
$orders = Order::latest()->limit(50)->get();
foreach ($orders as $order) {
echo $order->customer->name;
}
// Corrigé : deux requêtes au total
$orders = Order::with('customer')->latest()->limit(50)->get();
// app/Providers/AppServiceProvider.php
Model::preventLazyLoading(! app()->isProduction());Index de base de données
Lancez EXPLAIN sur les requêtes lentes. Indexez les colonnes utilisées dans WHERE, ORDER BY et JOIN. Pour un index composite, ordonnez les colonnes de la plus sélective à la moins sélective, en suivant l'ordre de filtrage de la requête. Une requête qui filtre et trie sur les colonnes couvertes par un index ne touche jamais les lignes de la table.
Schema::table('orders', function (Blueprint $table) {
$table->index(['status', 'created_at']); // filtre par statut, tri par date
});Hygiène des requêtes sur les gros volumes
Ne sélectionnez que les colonnes que vous utilisez, pour qu'une table large ne transporte pas des mégaoctets que vous jetez. Ne chargez jamais un gros résultat entièrement en mémoire : utilisez lazy ou cursor pour streamer les lignes, chunkById pour les jobs batch, et paginez chaque endpoint de liste.
Order::where('status', 'pending')
->select(['id', 'customer_id', 'total'])
->lazy()
->each(fn (Order $order) => $order->reconcile());Cache ciblé
Mettez en cache les lectures coûteuses répétées bien plus souvent qu'elles ne changent, pas toutes les requêtes. Les tags de cache permettent d'invalider une tranche sans tout vider, ce qui garde les écritures peu coûteuses. Mettez en cache le résultat calculé, pas la réponse HTTP entière, sauf si la page est entièrement cacheable.
$topProducts = Cache::tags(['products', 'dashboard'])
->remember('top-products', now()->addMinutes(30), function () {
return Product::withCount('orders')
->orderByDesc('orders_count')
->limit(10)
->get();
});
// Invalider seulement la tranche products
Cache::tags(['products'])->flush();Déplacer le travail lourd vers des queues
Les emails, exports, génération de PDF et appels tiers n'ont pas leur place dans le cycle de requête. Dispatchez-les vers une queue et retournez immédiatement. Horizon donne des métriques en temps réel sur le débit, le temps d'attente et les échecs, pour que les jobs cessent de disparaître silencieusement dans failed_jobs. Utilisez le batching de jobs quand une tâche se ramifie en de nombreuses unités de travail.
Cache au niveau HTTP
Ajoutez des headers ETag et Cache-Control aux endpoints GET dont les données changent lentement, pour que clients et proxies servent un 304 au lieu d'une réponse complète. Servez les assets depuis un CDN. Pour les pages identiques pour tous les visiteurs, un cache de réponse complet devant Laravel retire complètement le framework du chemin critique.
Caches au moment du déploiement
À chaque déploiement, lancez config:cache, route:cache, view:cache et event:cache pour que Laravel ne les reconstruise pas à chaque requête. Activez OPcache sur le serveur, et installez avec composer install --optimize-autoloader --no-dev. Ce sont des gains gratuits faciles à oublier.
Octane, en dernier
Le PHP-FPM traditionnel démarre tout le framework à chaque requête. Octane, sur FrankenPHP ou Swoole, garde l'application en mémoire entre les requêtes et retire une part réelle du temps de réponse sous charge. C'est un multiplicateur sur une app déjà rapide, pas un correctif pour une app lente, et il introduit des pièges de fuite d'état : pas d'état statique mutable, gestion prudente des singletons du container.
// Dangereux avec Octane : l'état statique persiste entre les requêtes
class ReportCache
{
private static array $cache = []; // fuite entre requêtes, à éviter
}| Étape | Effort | Gain typique |
|---|---|---|
| Profiler les requêtes lentes | Faible | Indique où va le reste de l'effort |
| Corriger les N+1 et ajouter des index | Faible à moyen | Souvent la plus grosse amélioration seule |
| Hygiène des requêtes (select, lazy, paginer) | Faible | Supprime les pics mémoire et les timeouts |
| Cache ciblé avec tags | Moyen | Fort sur les endpoints à forte lecture |
| Déporter le travail vers des queues | Moyen | Réduit le temps de réponse des endpoints d'écriture |
| Caches de déploiement et OPcache | Faible | Petit mais gratuit, à chaque requête |
| Laravel Octane | Moyen à élevé | Multiplie une app déjà rapide sous charge |
Mesurez avant d'optimiser. Octane ne corrigera pas des requêtes N+1 et le cache ne corrigera pas un index manquant. Profilez d'abord, corrigez la cause racine, puis recourez aux outils qui multiplient une app déjà rapide.
FAQ
- Par quoi commencer pour accélérer une app Laravel ?
- Mesurez d'abord avec Telescope ou Debugbar pour trouver les requêtes les plus lentes, puis corrigez les requêtes N+1 et les index manquants. Ces deux points expliquent l'essentiel de la lenteur réelle. Le cache et Octane viennent après, et ils multiplient une app déjà rapide plutôt que de sauver une app lente.
- Octane vaut-il le coup ?
- Oui, une fois le travail de base de données optimisé. Octane garde le framework démarré entre les requêtes et retire une part réelle du temps de réponse sous charge. Sur une app qui fait encore des N+1, il fait juste tourner le code lent plus vite, et les pièges de fuite d'état sont réels, donc c'est la dernière étape, pas la première.
- Comment détecter les requêtes N+1 ?
- Debugbar ou Telescope montrent le nombre de requêtes par requête HTTP ; un endpoint de liste qui déclenche des dizaines de requêtes quasi identiques en est la signature. Hors production, appelez Model::preventLazyLoading() pour qu'un lazy load lève une exception et que le N+1 apparaisse dans les tests et les runs locaux.
- Le cache Laravel ralentit-il les écritures ?
- Les lectures en cache sont rapides ; le coût est l'invalidation. Si chaque écriture vide un cache large, vous échangez de la vitesse de lecture contre du churn d'écriture. Les tags de cache permettent d'invalider juste la tranche concernée, ce qui garde les écritures peu coûteuses tout en gardant les lectures chaudes en cache.
- FrankenPHP ou Swoole pour Octane ?
- Les deux gardent le framework en mémoire. FrankenPHP est plus simple à déployer, un binaire unique avec HTTPS et workers intégrés, et c'est la recommandation par défaut actuelle. Swoole expose plus de primitives comme les coroutines et les tables si vous en avez besoin. Commencez par FrankenPHP.
Suivez l'ordre : mesurer, corriger requêtes et index, assainir l'accès aux données, puis cache, queues et cache HTTP, et seulement ensuite Octane. La plupart des apps Laravel récupèrent l'essentiel de leur vitesse dans les deux premières étapes, et le reste de la liste la garde rapide à mesure que le trafic grandit.
Besoin d'aide sur ce sujet ? Audit Technique
Découvrir ce service →