Une application qui rame : où chercher, dans quel ordre
Vincent
Co-fondateur, direction technique

En bref
Comment diagnostiquer une application web lente ?
Dans neuf cas sur dix, la cause est l'accès aux données : requêtes multipliées dans une boucle, index manquant, ou chargement de bien plus de données que nécessaire. Il faut mesurer avant de toucher quoi que ce soit — identifier la page lente, puis le temps passé côté base, côté application et côté navigateur. Optimiser sans mesure conduit à améliorer ce qui n'était pas le problème.
« L’application est lente. » C’est une phrase, pas un diagnostic. Et la réaction instinctive — ajouter du cache, augmenter le serveur, changer de technologie — traite presque toujours le mauvais problème.
Voici l’ordre dans lequel je cherche, et pourquoi cet ordre-là.
Mesurer d’abord, toujours
Avant toute chose, trois questions.
Quelle page, quel geste ? « Tout est lent » n’existe pas. En creusant, on trouve généralement deux ou trois écrans, souvent la liste principale et un export.
Depuis quand ? Une dégradation progressive sur six mois évoque un volume de données qui grossit. Une dégradation brutale évoque une livraison ou un changement d’infrastructure — et l’on sait alors où regarder.
Combien de temps, réellement ? Pas « c’est long » : un chiffre. La perception est très variable selon les personnes, et l’on ne peut pas améliorer ce qu’on ne mesure pas.
Ensuite, un découpage simple : sur les temps totaux, combien est passé côté base de données, côté application, et côté navigateur ? Cette répartition oriente immédiatement, et elle s’obtient sans outil sophistiqué.
Cause 1 — Les requêtes multipliées
De très loin la première cause, et la plus facile à corriger.
Le schéma est toujours le même : on récupère une liste de cent éléments, puis pour chacun on va chercher une information liée. Cent une requêtes au lieu de deux. Chacune est rapide, leur somme ne l’est pas — et le problème grandit avec le volume, donc il est invisible en développement où la base contient vingt lignes.
Le repérage est simple : compter le nombre de requêtes émises pour afficher une page. Si ce nombre dépend du nombre d’éléments affichés, le problème est là.
La correction consiste à charger les données liées en une seule requête. Le gain est généralement spectaculaire — d’un facteur dix à cinquante.
Cause 2 — Les index manquants
La deuxième cause, et celle qui explique le mieux les dégradations progressives.
Une requête qui filtre ou trie sur une colonne non indexée oblige la base à parcourir toute la table. À mille lignes, c’est instantané. À un million, c’est plusieurs secondes.
Le diagnostic passe par le plan d’exécution de la requête : la base indique elle-même si elle a parcouru la table entière. C’est une commande, et la réponse est sans ambiguïté.
Attention à l’excès inverse : chaque index ralentit les écritures et occupe de l’espace. On indexe ce qu’on interroge réellement, pas tout ce qui pourrait l’être.
Cause 3 — Charger trop de données
Une page qui affiche vingt lignes mais en récupère dix mille pour les filtrer ensuite côté application. Une requête qui remonte toutes les colonnes alors que trois sont affichées, dont un champ texte volumineux.
Cette cause est fréquente et discrète : rien n’a l’air anormal dans le code, chaque étape est raisonnable, et le volume transféré est absurde.
La correction est conceptuelle : filtrer, trier et paginer dans la base, pas dans l’application. La base est faite pour ça, elle le fait mieux, et cela évite de transporter des données qu’on jette immédiatement.
Photo : Vidsplay — licence CC0.
Cause 4 — Le navigateur
Quand le serveur répond en 150 ms et que la page met quatre secondes à s’afficher, le problème est côté client.
Les suspects habituels : trop de scripts chargés avant l’affichage, des images non redimensionnées servies en pleine résolution, des polices qui bloquent le rendu, ou un tableau de plusieurs milliers de lignes construit d’un bloc dans le navigateur.
Cette catégorie est souvent ignorée parce que les métriques serveur sont bonnes. Or c’est l’expérience réelle de l’utilisateur — et c’est celle sur laquelle il juge.
Photo : FOCA Stock — licence CC0.
Ce que je fais rarement
Ajouter du cache. C’est la réponse la plus tentante et souvent la pire : elle masque le problème et en ajoute un nouveau — l’invalidation. Un cache posé sur une requête mal écrite fait gagner du temps jusqu’au premier affichage de données périmées, et le débogage devient nettement plus difficile.
Le cache est un bon outil sur une donnée coûteuse à calculer et rarement modifiée. Ce n’est pas un correctif pour une requête inefficace.
Augmenter la machine. Cela achète du temps et coûte tous les mois. Sur une requête qui parcourt un million de lignes, doubler le processeur divise le temps par deux ; ajouter un index le divise par mille.
Cause 5 — Les appels externes en série
Celle-ci n’apparaît pas dans les profils de base de données, et c’est ce qui la rend difficile à trouver.
Une page qui interroge trois services externes — un annuaire, une facturation, un service de courriel — attend chacun l’un après l’autre. Trois fois trois cents millisecondes font une seconde, et aucun de ces appels n’est anormal pris isolément.
Deux corrections, dans cet ordre.
Paralléliser ce qui est indépendant. Si les trois appels ne dépendent pas les uns des autres, ils doivent partir ensemble. On passe de la somme au maximum.
Sortir de la requête ce qui n’est pas nécessaire à la réponse. Envoyer un courriel de notification n’a rien à faire dans le temps de réponse de l’utilisateur. Ce travail se dépose dans une file et s’exécute juste après.
Un troisième point, moins évident : un appel externe doit toujours avoir un délai d’attente explicite. Sans lui, la lenteur d’un service tiers devient la lenteur de votre application, et un incident chez un fournisseur se transforme en indisponibilité chez vous.
Le cas de la lenteur intermittente
C’est le plus difficile, parce que la reproduction échoue et que le problème disparaît dès qu’on le regarde.
Quatre causes couvrent presque tous les cas que j’ai rencontrés.
Un traitement planifié qui tourne au même moment. Un export nocturne devenu quotidien, une tâche de nettoyage qui verrouille une table. La corrélation se voit immédiatement en superposant les temps de réponse et le calendrier des tâches.
Un cache qui expire en bloc. Toutes les entrées créées au même instant expirent au même instant, et la charge repart d’un coup vers la base. Il suffit d’introduire une variation aléatoire sur les durées de vie.
Une file d’attente sur un pool de connexions saturé. Le temps n’est pas passé à travailler, il est passé à attendre une connexion libre. Ce symptôme est trompeur parce que la base semble inactive.
Un voisin bruyant sur une infrastructure partagée. C’est le seul cas où le problème n’est pas chez vous — et il se démontre en comparant les mesures système à la charge réelle de l’application.
L’ordre, en résumé
- Mesurer : quelle page, quel geste, combien de temps, depuis quand.
- Répartir : base, application, navigateur.
- Si c’est la base : compter les requêtes, puis vérifier les index.
- Si c’est l’application : regarder le volume de données manipulé.
- Si c’est le navigateur : regarder ce qui est chargé avant l’affichage.
Cet ordre n’est pas arbitraire : il va du plus fréquent au plus rare, et du moins coûteux à corriger au plus coûteux. Les trois premières étapes règlent l’immense majorité des cas, en quelques heures, sans rien changer à l’architecture.