Comment nous avons divisé par 10 le temps de chargement d’une application .NET sur Azure
Contexte et problématique initiale
Une application .NET Core et Angular ralentie par des requêtes SQL trop lourdes
Contexte du projet : Dans le cadre d’un projet de développement d'une application Web réalisée en .Net Core et Angular. Nous avons rencontré un problème de timeout sur la récupération d’une liste qui contient beaucoup de données agrégées de plusieurs tables différentes dans la base de données. Le temps de la requête dépassait les 60 secondes de timeout par moment pour afficher 20 éléments dans la liste. Les problèmes viennent en grande partie de la partie backend donc l’application .Net Core et la base de données PostgreSQL. Nous avons pu investiguer sur le sujet grâce à Azure App Insights, notre outil de monitoring.
La table principale contenait environ 10 000 enregistrements. Cette table était ensuite reliée par jointures à une dizaine d’autres tables : la plupart comptaient également autour de 10 000 lignes, quatre d’entre elles atteignaient environ 50 000 lignes, et la plus volumineuse dépassait 3 millions d’enregistrements. Cette volumétrie importante, combinée à un grand nombre de jointures, créait un contexte particulièrement exigeant pour la base PostgreSQL lors de l’exécution des requêtes.
Un projet repris après échec : priorité à la stabilisation avant l’optimisation
Le contexte du projet étant assez complexe suite à la reprise du projet après l’échec de son développement par un autre prestataire, nous avions initialement voulu éviter les optimisations précoces et de les traiter quand les performances ne nous paraissaient pas suffisantes. En effet, les délais pour le développement ont été plutôt contraignants au début du projet. Les problèmes de performances n’étaient pas non plus présents au début car la volumétrie était faible et a beaucoup augmenté depuis.
Le site est interne et accessible uniquement depuis le VPN du client donc les impacts sont principalement la satisfaction du client.
L’échec du précédent prestataire était avant tout technique. Le code livré présentait plusieurs problèmes : une qualité globale insuffisante, une structure difficile à refactorer et des performances faibles. Ces éléments rendaient toute évolution ou optimisation particulièrement délicate.
Des difficultés sont également apparues au niveau de la gestion de projet. Le pilotage technique n’était pas suffisamment structuré, ce qui a contribué à accumuler de la dette technique et à ralentir la stabilisation de l’application.
Depuis la reprise du projet, la volumétrie des données a été multipliée par cinq. Cette croissance progressive explique pourquoi les problèmes de performance n’étaient pas visibles au début du projet, mais sont devenus critiques avec l’augmentation du volume de données.
Objectifs de l'amélioration des performances
Nous avons donc eu comme volonté de rendre cette liste de nouveau accessible et rendre son affichage possible dans un temps acceptable, de l’ordre de 5 secondes et moins si possible.
La situation était devenue critique pour l’équipe :
- Temps de réponse : jusqu’à 60 secondes
- Liste affichant seulement 20 éléments
- Base PostgreSQL avec plusieurs millions de lignes
- Application interne devenant difficilement utilisable
L’objectif était clair :
>>>>> passer sous les 5 secondes de chargement !!!
tout en garantissant la stabilité de l’application.
Diagnostic des causes de lenteur
Pour identifier précisément l’origine des lenteurs, nous avons mis en place une analyse méthodique des performances.
Nous avons d’abord étudié l’état de l’infrastructure via Azure App Insights et les outils de monitoring Azure afin de vérifier si des limitations existaient au niveau du CPU ou de la mémoire des différents services.
Ensuite, nous avons analysé les logs applicatifs et les requêtes SQL exécutées par le backend afin d’identifier les opérations les plus coûteuses pour la base PostgreSQL.
Nous avons également exploré la base de données avec DBeaver pour détecter d’éventuelles anomalies de volumétrie ou de structure.
Enfin, afin d’isoler précisément les sources de lenteur, nous avons développé un script JavaScript spécifique qui appelait l’API et mesurait les temps de réponse pour chaque entité retournée. Cette approche nous a permis d’identifier précisément les éléments les plus coûteux en temps de calcul.
Les leviers d’optimisation activés
Nous avons donc plusieurs leviers d’action pour améliorer les performances de cette liste. Nous avons décidé de ne pas étudier les possibilités d’optimisation du frontend car celui-ci ne prend que quelques millisecondes pour traiter la réponse du backend et que le temps de la requête est la principale cause.
Analyse de l’infrastructure Azure
Avant d’optimiser le code ou les requêtes SQL, nous avons commencé par vérifier si l’infrastructure pouvait supporter la charge de l’application.
L’application est hébergée sur Microsoft Azure, avec :
- un App Service pour l’application .NET Core
- une base de données PostgreSQL
L’objectif était de déterminer si les problèmes de performance provenaient d’un manque de ressources côté infrastructure.
Identification des pics de charge sur la base PostgreSQL
Après analyse des métriques d’infrastructure, nous avons constaté que :
- la base de données subissait des pics de charge lors de l’appel de la liste
- le reste du temps, la charge restait relativement faible
Cela indiquait que le problème était lié à certaines requêtes spécifiques plutôt qu’à une surcharge globale du système.
À l’inverse, aucun problème particulier n’a été observé sur l’App Service, qui disposait des ressources nécessaires pour exécuter l’application.
Une base PostgreSQL obsolète sur Azure
L’analyse de l’infrastructure a également révélé deux points importants :
- La base PostgreSQL fonctionnait en version 11, alors que la version la plus récente disponible était la 15 au moment de l’investigation.
- La base était déployée sur Azure en mode Single Server, un modèle destiné à être progressivement déprécié par Microsoft.
Ces éléments pouvaient contribuer à limiter les performances et les capacités d’optimisation de la base de données.
Migration vers une architecture PostgreSQL plus performante
Pour améliorer la situation, nous avons décidé de :
- mettre à jour PostgreSQL vers une version plus récente
- migrer la base vers Azure Flexible Server
Cette migration a permis de bénéficier :
- d’améliorations de performance
- d’une meilleure gestion des ressources
- d’une architecture plus moderne et pérenne.
Premiers gains de performance après optimisation de l’infrastructure
Suite à ces actions, les performances globales de l’application se sont améliorées.
Nous avons constaté :
- beaucoup moins de timeouts
- un affichage de la liste nettement plus stable
- des temps de réponse compatibles avec une utilisation normale de l’application
Ces optimisations d’infrastructure ont permis de stabiliser la plateforme, avant d’aller plus loin dans l’optimisation du backend et des requêtes SQL.
Analyse des performances du backend
Même si l’amélioration de l’infrastructure et de la base de données a permis de stabiliser la situation, nous avons rapidement considéré que cela ne constituait qu’une solution à court terme.
Pour des raisons de stabilité applicative mais aussi de coût d’infrastructure, il était nécessaire d’optimiser les requêtes envoyées par le backend vers la base de données afin de réduire la consommation de ressources.
Identification des éléments les plus coûteux
Nous avons remarqué que les temps d’affichage de la liste variaient fortement selon les éléments affichés.
Afin d’identifier précisément l’origine du problème, nous avons réalisé une analyse comparative des différents éléments retournés par l’API.
Pour cela, nous avons développé un script spécifique permettant :
- de récupérer les éléments un par un
- de mesurer les temps d’affichage pour chacun d’entre eux
- de comparer les performances entre les différentes entités.
Cette approche nous a permis de détecter les éléments les plus coûteux en temps de calcul.
Détection d’une jointure inutile
L’analyse a révélé que certains éléments pouvaient prendre jusqu’à dix fois plus de temps à s’afficher que d’autres.
En examinant la requête SQL générée par le backend, nous avons identifié un problème important : une jointure était réalisée avec une table dont aucune donnée n’était utilisée dans la liste.
La suppression de cette jointure inutile a immédiatement permis d’améliorer la stabilité des performances et de réduire les temps de réponse.
Correction de données dupliquées provenant d’une API partenaire
L’analyse du backend a également permis d’identifier un second problème : certaines données avaient été massivement dupliquées à la suite d’un problème de communication avec l’API d’un partenaire.
Ces duplications augmentaient inutilement le volume de données à traiter lors des requêtes.
Nous avons donc procédé à un nettoyage des duplicatas, ce qui a permis de résoudre les derniers problèmes de performance observés.
Résultats : des performances multipliées par 10
Comparatif avant / après
Suite aux actions entreprises, nous avons constaté une amélioration significative des performances de l'application, notamment au niveau de l'affichage de la liste.
Vous pouvez voir ci-dessous qu’avant les améliorations le temps de réponse était en moyenne de 35 secondes pour certaines requêtes et celle-ci est descendue à 2.7. Ce test a été réalisé avec Locust et avec une requête que nous avions identifiée comme particulièrement longue dans l’ancienne version.
Ancienne version :

| # Requests | Median (ms) | 95%ile (ms) | 99%ile (ms) | Average (ms) | Min (ms) | Max (ms) |
|---|---|---|---|---|---|---|
| 10 | 36000 | 37000 | 37000 | 35875.97 | 34071 | 37214 |
Nouvelle version :

| # Requests | Median (ms) | 95%ile (ms) | 99%ile (ms) | Average (ms) | Min (ms) | Max (ms) |
|---|---|---|---|---|---|---|
| 10 | 2500 | 3800 | 3800 | 2734.45 | 2446 | 3812 |
Amélioration de la stabilité
La suppression de la jointure inutile et la correction des données dupliquées ont également amélioré la stabilité de l'application. Ces actions ont permis de réduire les variations de temps de chargement et d’éviter les erreurs liées à des données incohérentes.
La migration de la base de données PostgreSQL vers une version plus récente ainsi que le passage vers Azure Flexible Server ont également contribué à optimiser l’utilisation des ressources.
Nettoyage contrôlé des données dupliquées :
Pour corriger le problème de duplication, nous avons procédé à un nettoyage contrôlé des données directement en SQL. L’objectif était d’identifier les tables contenant un nombre anormalement élevé de doublons et de conserver uniquement la dernière valeur valide pour chaque enregistrement.
Cette opération a permis de réduire le volume de données traité lors des requêtes et d’améliorer la cohérence globale de la base.
Correction du problème à la source :
Concernant le risque d’"effet boule de neige" dans les imbrications de données, la solution ne pouvait pas être uniquement technique. Les duplications provenaient d’un processus externe lié à une API partenaire.
Nous avons donc travaillé avec ce prestataire afin qu’il corrige son mécanisme d’envoi des données.
De notre côté, les données étant critiques pour l’application, il n’était pas possible de les refuser automatiquement. La solution a donc consisté à corriger la source du problème tout en assainissant les données existantes.
Enseignements et bonnes pratiques
Importance de la surveillance continue
La surveillance régulière des performances de l'application et de l'infrastructure est essentielle pour détecter rapidement les problèmes et agir de manière proactive.
Approche méthodique
Une approche méthodique, basée sur l'analyse des données et des tests, est indispensable pour identifier les causes profondes des problèmes de performance et mettre en place des solutions efficaces.
Optimisation multi-facettes
L'optimisation des performances d'une application web nécessite souvent une approche multi-facettes, prenant en compte à la fois l'infrastructure, le code backend et la base de données.
Ces enseignements et bonnes pratiques pourront être appliqués à de futurs projets pour garantir des performances optimales et une expérience utilisateur satisfaisante.
Ce qu’Agaetis retient de cette expérience
- Ce projet nous a permis de renforcer notre approche d’analyse des performances sur des systèmes manipulant de grands volumes de données. Nous avons confirmé l’importance d’examiner successivement les trois dimensions clés d’une application : l’infrastructure, le code applicatif et la qualité des données. C’est souvent la combinaison de ces facteurs qui explique les problèmes de performance.
- Les développeurs possèdent une vision transversale du système : compréhension du métier, maîtrise du code applicatif et connaissance de l’infrastructure cloud. Cette approche globale nous permet d’analyser un problème technique sous plusieurs angles et d’identifier plus rapidement les véritables causes des ralentissements.
- Cette expérience a également renforcé certaines bonnes pratiques internes. Nous veillons notamment à maintenir nos outils et bases de données à jour, comme PostgreSQL, afin de bénéficier des optimisations de performance.
- Nous avons également renforcé notre discipline autour de logs explicites et exploitables, qui facilitent grandement les diagnostics.
- Enfin, nous privilégions désormais davantage les approches de pré‑traitement des données, afin de limiter les jointures complexes sur des tables volumineuses — en particulier lorsque certaines dépassent plusieurs millions de lignes.
Conclusion
La réduction drastique du temps de chargement des pages a eu un impact direct pour le client. Les utilisateurs peuvent désormais accéder aux données beaucoup plus rapidement, ce qui améliore significativement l’expérience d’utilisation de l’application.
Au‑delà du gain de temps opérationnel, cette optimisation a également renforcé la confiance du client dans la capacité de l’équipe à résoudre des problématiques techniques complexes.
Éviter les requêtes qui agrègent un très grand nombre de données via des jointures multiples. Lorsque cela est possible, il est préférable de clarifier le besoin métier en amont afin de ne récupérer que les informations réellement nécessaires.
- Maintenir à jour l’ensemble des composants techniques : librairies, frameworks et bases de données.
- Tester séparément les différentes sources potentielles de problèmes : infrastructure, code applicatif et qualité des données.
- Suivre une méthodologie structurée pour analyser les performances et prioriser les actions d’optimisation.
- Mettre en place des mécanismes de pré‑traitement ou d’agrégation des données afin de limiter les requêtes complexes sur des tables volumineuses.
Ressources Agaetis




