Diagnostic des performances
Cette section décrit les façons de détecter les problèmes de performances dans votre application EF, et une fois qu’une zone problématique a été identifiée, comment les analyser davantage pour identifier le problème racine. Il est important de diagnostiquer et d’examiner soigneusement les problèmes avant de passer à des conclusions, et d’éviter d’supposer où se trouve la racine du problème.
Identification des commandes de base de données lentes via la journalisation
À la fin de la journée, EF prépare et exécute des commandes à exécuter sur votre base de données ; avec une base de données relationnelle, ce qui signifie l’exécution d’instructions SQL via l’API de base de données ADO.NET. Si une requête donnée prend trop de temps (par exemple, parce qu’un index est manquant), cela peut être découvert en inspectant les journaux d’exécution des commandes et en observant le temps qu’ils prennent réellement.
EF facilite la capture des temps d’exécution des commandes, via la journalisation simple ou Microsoft.Extensions.Logging:
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder
.UseSqlServer(@"Server=(localdb)\mssqllocaldb;Database=Blogging;Trusted_Connection=True;ConnectRetryCount=0")
.LogTo(Console.WriteLine, LogLevel.Information);
}
Lorsque le niveau de journalisation est défini à LogLevel.Information
, EF émet un message de journal pour chaque exécution de commande avec le temps nécessaire :
info: 06/12/2020 09:12:36.117 RelationalEventId.CommandExecuted[20101] (Microsoft.EntityFrameworkCore.Database.Command)
Executed DbCommand (4ms) [Parameters=[], CommandType='Text', CommandTimeout='30']
SELECT [b].[Id], [b].[Name]
FROM [Blogs] AS [b]
WHERE [b].[Name] = N'foo'
La commande ci-dessus a pris 4 millisecondes. Si une certaine commande prend plus que prévu, vous avez trouvé un coupable possible pour un problème de performances et pouvez maintenant vous concentrer sur celui-ci pour comprendre pourquoi il s’exécute lentement. La journalisation des commandes peut également révéler des cas où des allers-retours de base de données inattendus sont effectués ; cela s’affiche sous la forme de plusieurs commandes où une seule commande est attendue.
Avertissement
Laisser la journalisation de l’exécution des commandes activée dans votre environnement de production est généralement une mauvaise idée. La journalisation elle-même ralentit votre application et peut rapidement créer d’énormes fichiers journaux qui peuvent remplir le disque de votre serveur. Il est recommandé de ne conserver la journalisation que pendant un court intervalle de temps pour collecter des données , tout en surveillant soigneusement votre application ou pour capturer les données de journalisation sur un système de préproduction.
Corrélation des commandes de base de données aux requêtes LINQ
Un problème avec la journalisation de l’exécution des commandes est qu’il est parfois difficile de mettre en corrélation les requêtes SQL et les requêtes LINQ : les commandes SQL exécutées par EF peuvent être très différentes des requêtes LINQ à partir de lesquelles elles ont été générées. Pour vous aider à résoudre cette difficulté, vous pouvez utiliser la fonctionnalité de balises de requête EF, qui vous permet d’injecter un petit commentaire identifiant le commentaire dans la requête SQL :
var myLocation = new Point(1, 2);
var nearestPeople = (from f in context.People.TagWith("This is my spatial query!")
orderby f.Location.Distance(myLocation) descending
select f).Take(5).ToList();
La balise s’affiche dans les journaux :
-- This is my spatial query!
SELECT TOP(@__p_1) [p].[Id], [p].[Location]
FROM [People] AS [p]
ORDER BY [p].[Location].STDistance(@__myLocation_0) DESC
Il est souvent utile de marquer les principales requêtes d’une application de cette façon pour rendre les journaux d’exécution de commandes plus lisibles immédiatement.
Autres interfaces pour capturer des données de performances
Il existe différentes alternatives à la fonctionnalité de journalisation d’EF pour capturer les temps d’exécution des commandes, ce qui peut être plus puissant. Les bases de données sont généralement fournies avec leurs propres outils de suivi et d’analyse des performances, qui fournissent généralement des informations plus riches et spécifiques à la base de données au-delà des temps d’exécution simples; la configuration, les fonctionnalités et l’utilisation réelles varient considérablement entre les bases de données.
Par exemple, SQL Server Management Studio est un client puissant qui peut se connecter à votre instance SQL Server et fournir des informations de gestion et de performances précieuses. Il est au-delà de l’étendue de cette section pour entrer dans les détails, mais deux fonctionnalités dignes d’intérêt sont le Moniteur d’activité, qui fournit un tableau de bord dynamique de l’activité du serveur (y compris les requêtes les plus coûteuses) et la fonctionnalité Événements étendus (XEvent), qui permet de définir des sessions de capture de données arbitraires qui peuvent être adaptées à vos besoins exacts. La documentation SQL Server sur la supervision fournit plus d’informations sur ces fonctionnalités, ainsi que d’autres.
Une autre approche pour capturer des données de performances consiste à collecter automatiquement des informations émises par EF ou le pilote de base de données via l’interface DiagnosticSource
, puis à analyser ces données ou à les afficher sur un tableau de bord. Si vous utilisez Azure, Azure Application Insights fournit une surveillance aussi puissante prête à l’emploi, en intégrant les temps d’exécution des requêtes et des performances de base de données dans l’analyse de la rapidité avec laquelle vos requêtes web sont traitées. Pour plus d’informations, consultez le didacticiel sur les performances d’Application Insights et dans lapage d’analytique Azure SQL.
Inspection des plans d’exécution des requêtes
Une fois que vous avez identifié une requête problématique nécessitant une optimisation, l’étape suivante analyse généralement le plan d’exécution de la requête. Lorsque les bases de données reçoivent une instruction SQL, elles produisent généralement un plan de l’exécution de ce plan ; cela nécessite parfois une prise de décision complexe en fonction des index qui ont été définis, de la quantité de données dans les tables, etc. (accessoirement, le plan lui-même doit généralement être mis en cache sur le serveur pour des performances optimales). Les bases de données relationnelles fournissent généralement un moyen pour les utilisateurs de voir le plan de requête, ainsi que le coût calculé pour différentes parties de la requête ; cela est inestimable pour améliorer vos requêtes.
Pour commencer sur SQL Server, consultez la documentation sur les plansd’exécution des requêtes. Le flux de travail d’analyse classique consiste à utiliser SQL Server Management Studio, en collant le code SQL d’une requête lente identifiée par l’un des moyens ci-dessus et en produisant un plan d’exécution graphique :
Bien que les plans d’exécution semblent compliqués au début, il vaut la peine de passer un peu de temps à se familiariser avec eux. Il est particulièrement important de noter les coûts associés à chaque nœud du plan et d’identifier la façon dont les index sont utilisés (ou non) dans les différents nœuds.
Bien que les informations ci-dessus sont spécifiques à SQL Server, d’autres bases de données fournissent généralement le même type d’outils avec une visualisation similaire.
Important
Les bases de données génèrent parfois différents plans de requête en fonction des données réelles de la base de données. Par exemple, si une table contient seulement quelques lignes, une base de données peut choisir de ne pas utiliser d’index sur cette table, mais d’effectuer une analyse complète de table à la place. Si vous analysez des plans de requête sur une base de données de test, vérifiez toujours qu’il contient des données similaires à votre système de production.
Métriques
Les sections ci-dessus se sont concentrées sur la façon d’obtenir des informations sur vos commandes et sur la façon dont ces commandes sont exécutées dans la base de données. En plus de cela, EF expose un ensemble de mesures qui fournissent d’autres informations de bas niveau sur ce qui se passe à l’intérieur d’EF lui-même et la façon dont votre application l’utilise. Ces mesures peuvent être très utiles pour diagnostiquer des problèmes et anomalies de performances spécifiques, tels que des problèmes de mise en cache des requêtes qui provoquent une recompilation constante, des fuites DbContext non libérées, etc.
Consultez la page dédiée sur les Mesures d’EF pour plus d’informations.
Benchmark avec EF Core
À la fin de la journée, vous devez parfois savoir si une méthode particulière d’écriture ou d’exécution d’une requête est plus rapide qu’une autre. Il est important de ne jamais supposer ou de spéculer sur la réponse, et il est extrêmement facile de mettre en place un benchmark rapide pour obtenir la réponse. Lors de l’écriture de benchmarks, il est fortement recommandé d’utiliser la bibliothèque BenchmarkDotNet connue , qui gère de nombreux pièges rencontrés par les utilisateurs lors de la tentative d’écriture de leurs propres benchmarks : avez-vous effectué des itérations de préchauffage ? Combien d’itérations votre benchmark s’exécute-t-il réellement et pourquoi ? Examinons ce qu’un benchmark avec EF Core ressemble.
Conseil
Le projet de référence complet pour la source ci-dessous est disponible ici. Vous êtes encouragé à le copier et à l’utiliser comme modèle pour vos propres benchmarks.
En tant que scénario de benchmark simple, nous allons comparer les différentes méthodes suivantes pour calculer le classement moyen de tous les blogs dans notre base de données :
- Chargez toutes les entités, additionnez leurs classements individuels et calculez la moyenne.
- Identique à ce qui précède, utilisez uniquement une requête sans suivi. Cela doit être plus rapide, étant donné que la résolution d’identité n’est pas effectuée et que les entités ne sont pas captures instantanées à des fins de suivi des modifications.
- Évitez de charger l’ensemble des instances d’entité blog du tout en projetant le classement uniquement. Il nous permet de transférer les autres colonnes inutiles du type d’entité Blog.
- Calculez la moyenne dans la base de données en la faisant partie de la requête. Cela doit être le moyen le plus rapide, car tout est calculé dans la base de données et seul le résultat est transféré au client.
Avec BenchmarkDotNet, vous écrivez le code à évaluer comme une méthode simple, tout comme un test unitaire, et BenchmarkDotNet exécute automatiquement chaque méthode pour un nombre suffisant d’itérations, en mesurant de manière fiable le temps nécessaire et la quantité de mémoire allouée. Voici les différentes méthodes (le code de référence complet est visible ici) :
- Charger des entités
- Charger des entités, sans suivi
- Classement du projet uniquement
- Calculer dans la base de données
[Benchmark]
public double LoadEntities()
{
var sum = 0;
var count = 0;
using var ctx = new BloggingContext();
foreach (var blog in ctx.Blogs)
{
sum += blog.Rating;
count++;
}
return (double)sum / count;
}
Les résultats sont ci-dessous, comme imprimé par BenchmarkDotNet :
Méthode | Moyenne | Erreur | StdDev | Median | Taux | RatioSD | Gen 0 | Gen1 | Gen2 | Affecté |
---|---|---|---|---|---|---|---|---|---|---|
LoadEntities | 2,860,4 us | 54,31 us | 93,68 us | 2,844,5 us | 4.55 | 0,33 | 210.9375 | 70.3125 | - | 1309.56 Ko |
LoadEntitiesNoTracking | 1,353,0 us | 21,26 us | 18,85 us | 1,355,6 us | 2,10 | 0.14 | 87.8906 USD | 3.9063 | - | 540.09 Ko |
ProjectOnlyRanking | 910,9 us | 20,91 us | 61,65 us | 892,9 us | 1,46 | 0.14 | 41.0156 | 0.9766 | - | 252.08 Ko |
CalculateInDatabase | 627,1 us | 14,58 us | 42,54 us | 626,4 us | 1,00 | 0.00 | 4.8828 | - | - | 33.27 Ko |
Remarque
Comme les méthodes instancient et suppriment le contexte dans la méthode, ces opérations sont comptabilisées pour le benchmark, bien que strictement parlant, elles ne font pas partie du processus d’interrogation. Cela ne devrait pas être important si l’objectif est de comparer deux alternatives les unes aux autres (étant donné que l’instanciation et l’élimination du contexte sont les mêmes), et donne une mesure plus holistique pour l’ensemble de l’opération.
L’une des limitations de BenchmarkDotNet est qu’elle mesure les performances simples et à thread unique des méthodes que vous fournissez et n’est donc pas bien adaptée aux scénarios simultanés.
Important
Veillez toujours à disposer de données dans votre base de données qui sont similaires aux données de production lors de l’évaluation, sinon les résultats du benchmark peuvent ne pas représenter les performances réelles en production.