Notre

blog

Optimisation des performances : Retour d’expérience sur API Platform

Optimisation des performances : Retour d’expérience sur API Platform

Neil, Ingénieur Études et Développement chez Elosi vous partage son expérience d’optimisation des performances sur un projet utilisant API Platform. 

 

Constat initial : un projet gourmand en ressources 

Ayant déjà travaillé avec cet outil, j’avais quelques repères, mais le défi était de taille.

Après avoir récupéré et installé le projet, tout semblait bien se dérouler… jusqu’à ce que je constate l’impact d’un code mal optimisé :

  • Une page d’accueil mettant 8 à 10 secondes à charger
  • Une récupération d’entité prenant 2,5 secondes
  • Un tableau affiché en 7 secondes

 

Il était évident qu’il y avait de nombreuses pistes d’amélioration.  

 

Première étape : Optimisation des requêtes 

Mon premier défi concernait la récupération d’une entité. Ne disposant pas d’un accès complet au projet, j’ai travaillé en peer programming avec un collègue maîtrisant déjà le code. Un vrai gain de temps !

 

Analyse et premières optimisations 

La première chose à faire était de comprendre l’existant (commentez votre code, c’est la vie !). Une fois cette phase passée, nous avons tenté quelques optimisations: 

  • Sélectionner uniquement les champs nécessaires via le QueryBuilder (Symfony)
  • Ajouter des DISTINCT pour éviter les entrées dupliquées

 

Partie non optimisée 

<?php
$this->createQueryBuilder('u')
// On sélectionne plus de champs que nécessaire
// Dans le cadre ou nous avons des doublons, nous auront encore + d'entrées
		->select('rj.libelle, rj.name, rj.firstname, rj.age, rj.birthDate')
		->join(u.randomJoin, 'rj')
		->getQuery()
		→getResult()

 

Partie optimisée 

<?php
$this->createQueryBuilder('u')
// On ne sélectionne que les champs qui nous sont nécessaire en front
// On utilise le mot clé distinct pour ne pas récupérer de doublons et devoir faire un array_unique par exemple par la suite. Le SQL étant + rapide que le php
            ->select('distinct rj.name')
            ->join ('u.randomJoin', 'rj')
            ->getQuery()
            ->getResult()

 

Résultat : un gain limité, de 2,5 secondes à 2 secondes. C’était un début, mais insuffisant. 

 

Changement de logique : une approche plus efficace 

Le code en place vérifiait d’abord les droits, ajoutait des conditions à la requête et récupérait un ensemble d’objets… pour ensuite simplement vérifier si l’objet concerné faisait partie du lot.

 

J’ai alors posé une question simple à l’équipe :« Pourquoi ne pas partir directement de l’objet et vérifier s’il remplit une des conditions ? Si oui, on le renvoie. »

 

Ce changement de logique a été radical : nous sommes passés de 2,5 secondes à 0,3 seconde, avec un temps de traitement back-end réduit de 2 secondes à 0,02 seconde !

 

La seule amélioration restante concernait le temps de requête entre le front et le back.

 

Deuxième étape : Réduction du nombre de requêtes

La suite du travail a été plus complexe, car j’avais moins de contrôle sur les appels et requêtes. Il fallait donc optimiser le front plutôt que le back.

 

Analyse des requêtes

En examinant le trafic réseau, j’ai découvert que 40 requêtes étaient exécutées sur une seule page ! Après un premier tri, j’ai réussi à les réduire à 34 en fusionnant certaines.

 

La majorité des requêtes étaient par exemple : je veux récupérer l’entité A qui contient le champs X, puis je veux récupérer l’entité B qui contient le champs Y. Un simple groupement pour récupérer un tableau de valeurs passé quand nécessaire permet d’éviter un souci. L’ensemble asynchrone permet aussi de ne pas perdre de temps avec le traitement du tableau reçu.

 

Optimisation de l’ordre des requêtes

Un navigateur ne peut gérer qu’un certain nombre de requêtes simultanément (Chrome et Firefox en limitent à 6 en parallèle). Cela signifie que trop de requêtes ralentissent l’affichage.

 

J’ai donc adopté une approche d’optimisation placebo : modifier l’ordre d’exécution des requêtes. Deux stratégies étaient possibles :

  1. Lancer d’abord les requêtes rapides, puis les plus lentes
  2. Prioriser les requêtes visibles à l’écran, pour donner l’impression de rapidité

 

J’ai choisi la seconde approche : il fallait que l’utilisateur voie rapidement du mouvement sur sa page. Même si le temps total restait le même, l’expérience utilisateur s’améliorait.

 

Et ça a fonctionné : l’équipe avait la sensation que les pages chargeaient plus vite ! Deuxième optimisation réussie.

 

Troisième étape : Optimisation API Platform

Une question m’a traversé l’esprit : « Étrange qu’API Platform soit aussi lent pour ces requêtes… »

 

Curieux, j’ai creusé le sujet et découvert qu’un problème similaire avait déjà été rencontré lors d’une montée de version. Après avoir parcouru plusieurs discussions et documentations, une piste intéressante s’est dégagée : la configuration du cache d’API Platform.

 

J’ai donc mis en place les recommandations trouvées, en ajustant la configuration du cache HTTP et en optimisant la gestion des réponses côté serveur. Une fois ces modifications appliquées, les résultats ont été immédiats : les requêtes s’exécutaient beaucoup plus rapidement. Le cache du front et du back travaillaient enfin en parfaite harmonie, réduisant la latence et offrant une expérience utilisateur nettement plus fluide.

 

Pour aller plus loin : 
https://api-platform.com/docs/core/performance/ (la doc c’est la vie)

Conclusion : L’optimisation, un travail continu

L’optimisation ne se limite pas à du code. Parfois, de petits hacks suffisent à donner l’impression d’une amélioration.

 

Quelques conseils à retenir :

  • Faire appel à un regard extérieur sur le projet. Une nouvelle perspective peut révéler des solutions inattendues.
  • L’optimisation ne doit pas être une phase finale ! Dès l’ajout d’une fonctionnalité, posez-vous ces questions :
    • Vais-je réutiliser ce code ailleurs ?
    • Ai-je uniquement les champs nécessaires ou trop d’informations ?
    • Puis-je exploiter un élément existant au lieu de tout recréer ?
    • Vaut-il mieux paginer et faire plusieurs appels courts ou un seul plus long ?

 

L’optimisation est un état d’esprit plus qu’une tâche. Chaque milliseconde gagnée peut améliorer l’expérience globale.