Une API qui passe l'étape du staging sans encombre peut s'effondrer dès que le trafic réel arrive. L'échec est rarement spectaculaire. C'est une mort par mille petites coupures. Un thread bloqué ici. Une requête redondante là. Sous une charge légère, ces petites inefficacités passent inaperçues. Sous la pression de la production, elles se cumulent pour provoquer l'épuisement du pool de threads, l'étouffement de la base de données et des temps de réponse qui détruisent la confiance des utilisateurs. L'optimisation des performances ne consiste pas à trouver une solution miracle. Il s'agit de construire un système où chaque couche respecte la rareté des threads, de la mémoire et des connexions à la base de données. Lorsque vous traitez ces ressources comme étant finies, vous cessez de réagir aux pannes pour commencer à les prévenir.

Éliminez le sync-over-async avant qu'il ne vous tue

Le modèle le plus destructeur dans les applications ASP.NET Core à fort trafic est le sync-over-async. Vous le voyez lorsqu'un développeur appelle .Result ou .Wait() sur une méthode asynchrone parce qu'il a besoin de la valeur immédiatement et ne veut pas refactoriser la pile d'appels. Cette décision bloque le thread appelant. Le thread reste inactif, attendant un travail qui est déjà en cours ailleurs, mais le runtime ne peut pas le réutiliser pour une autre requête.

Lorsque suffisamment de requêtes procèdent ainsi, le pool de threads s'épuise. Votre graphique CPU semble sain car les processeurs ne sont pas occupés, et pourtant votre latence explose. Les requêtes s'accumulent, attendant des threads qui ne se libéreront jamais. La solution est mécanique mais exige de la discipline : utilisez await tout au long de la pile d'appels. Si une méthode appelle une API asynchrone, elle doit elle-même être asynchrone. Il n'y a pas de raccourci ici. Chaque blocage synchrone que vous supprimez libère de la marge pour le trafic réel.

Arrêtez de gaspiller des ressources pour des clients déconnectés

Les clients se déconnectent. Les navigateurs ferment des onglets. Les applications mobiles perdent le signal. Si votre serveur ne sait pas que le client est parti, il continue d'exécuter des requêtes de base de données, d'analyser le JSON et de consommer des threads pour une réponse que personne ne recevra. Passez un CancellationToken à chaque opération asynchrone qui le supporte. Cela inclut les requêtes Entity Framework, les appels HTTP avec HttpClient et tout travail de fond de longue durée.

Lorsque la connexion est interrompue, le jeton déclenche l'annulation et le travail s'arrête immédiatement. Cela préserve les cycles CPU de la base de données et restitue les threads au pool plus rapidement. C'est un petit changement dans les signatures de méthodes qui porte ses fruits sous la charge.

Cachez ce qui ne change pas

Votre catalogue de produits ne change probablement pas entre chaque requête. Vos drapeaux de configuration non plus, certainement pas. Pourtant, de nombreuses API interrogent la base de données de manière répétée pour les mêmes données statiques. L'output caching dans ASP.NET Core vous permet de stocker les réponses rendues et de les servir directement depuis la mémoire sans solliciter à nouveau vos contrôleurs ou votre base de données.

Utilisez l'invalidation basée sur les tags avec prudence. Lorsque vous mettez à jour un produit, n'invalidez que le tag associé à cette catégorie ou à cet article. Vous n'avez pas besoin de vider l'intégralité du cache. Cela permet de maintenir un taux de réussite du cache (cache hit ratio) élevé et un nombre de requêtes à la base de données faible.

Optimisez d'abord votre accès aux données

Les appels à la base de données consomment la majeure partie du temps de requête dans la plupart des API. Avant d'optimiser quoi que ce soit d'autre, regardez ici.

Si vous interrogez des données uniquement pour les afficher et que vous ne prévoyez jamais de les mettre à jour, ajoutez AsNoTracking() à vos requêtes Entity Framework. EF Core ignorera le suivi des modifications et la création de snapshots.