Partager via


Obtenir des métriques d’exécution de requête SQL et analyser les performances de requête à l’aide du Kit de développement logiciel (SDK) .NET

S’APPLIQUE À : NoSQL

Cet article explique comment profiler les performances des requêtes SQL sur Azure Cosmos DB à l’aide de ServerSideCumulativeMetrics récupérées à partir du Kit de développement logiciel (SDK) .NET. ServerSideCumulativeMetrics est un objet fortement typé contenant des informations sur l’exécution de la requête principale. Il contient des métriques cumulées provenant de l’ensemble des partitions physiques pour la demande, une liste de métriques pour chaque partition physique et le coût total de la demande. Ces mesures sont décrites plus en détail dans l’article Réglage des performances des requêtes.

Obtenir les métriques de requête

Les métriques de requête sont disponibles en tant qu’objet fortement typé dans le Kit de développement logiciel (SDK) .NET à compter de la version 3.36.0. Avant cette version, ou si vous utilisez un autre langage SDK, vous pouvez récupérer des métriques de requête en analysant le Diagnostics. L’exemple de code suivant montre comment récupérer ServerSideCumulativeMetrics à partir de Diagnostics dans un FeedResponse :

CosmosClient client = new CosmosClient(myCosmosEndpoint, myCosmosKey);
Container container = client.GetDatabase(myDatabaseName).GetContainer(myContainerName);

QueryDefinition query = new QueryDefinition("SELECT TOP 5 * FROM c");
FeedIterator<MyClass> feedIterator = container.GetItemQueryIterator<MyClass>(query);

while (feedIterator.HasMoreResults)
{
    // Execute one continuation of the query
    FeedResponse<MyClass> feedResponse = await feedIterator.ReadNextAsync();

    // Retrieve the ServerSideCumulativeMetrics object from the FeedResponse
    ServerSideCumulativeMetrics metrics = feedResponse.Diagnostics.GetQueryMetrics();
}

Vous pouvez également obtenir des métriques de requête à partir du FeedResponse d’une requête LINQ à l’aide de la méthode ToFeedIterator() :

FeedIterator<MyClass> feedIterator = container.GetItemLinqQueryable<MyClass>()
    .Take(5)
    .ToFeedIterator();

while (feedIterator.HasMoreResults)
{
    FeedResponse<MyClass> feedResponse = await feedIterator.ReadNextAsync();
    ServerSideCumulativeMetrics metrics = feedResponse.Diagnostics.GetQueryMetrics();
}

Métriques cumulatives

ServerSideCumulativeMetrics contient une propriété CumulativeMetrics qui représente les métriques de requête agrégées sur toutes les partitions pour l’aller-retour unique.

// Retrieve the ServerSideCumulativeMetrics object from the FeedResponse
ServerSideCumulativeMetrics metrics = feedResponse.Diagnostics.GetQueryMetrics();

// CumulativeMetrics is the metrics for this continuation aggregated over all partitions
ServerSideMetrics cumulativeMetrics = metrics.CumulativeMetrics;

Vous pouvez également agréger ces métriques sur l’ensemble des trajets aller-retour pour la requête. L'exemple suivant montre comment agréger le temps d'exécution d'une requête sur l'ensemble des allers-retours pour une requête donnée à l'aide de LINQ :

QueryDefinition query = new QueryDefinition("SELECT TOP 5 * FROM c");
FeedIterator<MyClass> feedIterator = container.GetItemQueryIterator<MyClass>(query);

List<ServerSideCumulativeMetrics> metrics = new List<ServerSideCumulativeMetrics>();
TimeSpan cumulativeTime;
while (feedIterator.HasMoreResults)
{
    // Execute one continuation of the query
    FeedResponse<MyClass> feedResponse = await feedIterator.ReadNextAsync();

    // Store the ServerSideCumulativeMetrics object to aggregate values after all round trips
    metrics.Add(feedResponse.Diagnostics.GetQueryMetrics());
}

// Aggregate values across trips for metrics of interest
TimeSpan totalTripsExecutionTime = metrics.Aggregate(TimeSpan.Zero, (currentSum, next) => currentSum + next.CumulativeMetrics.TotalTime);
DoSomeLogging(totalTripsExecutionTime);

Métriques partitionnée

ServerSideCumulativeMetrics contient une propriété PartitionedMetrics qui est une liste de métriques par partition pour le trajet aller-retour. Si plusieurs partitions physiques sont atteintes en un seul aller-retour, les mesures de chacune d'entre elles apparaissent dans la liste. Les métriques partitionnées sont représentées avec ServerSidePartitionedMetrics, avec un identificateur unique pour chaque partition physique et le coût de la demande pour cette partition.

// Retrieve the ServerSideCumulativeMetrics object from the FeedResponse
ServerSideCumulativeMetrics metrics = feedResponse.Diagnostics.GetQueryMetrics();

// PartitionedMetrics is a list of per-partition metrics for this continuation
List<ServerSidePartitionedMetrics> partitionedMetrics = metrics.PartitionedMetrics;

Lorsqu’elles sont cumulées sur l’ensemble des allers-retours, les mesures par partition vous permettent de voir si une partition spécifique occasionne des problèmes de performances par rapport à d’autres. L'exemple suivant montre comment regrouper les mesures de partition pour chaque voyage à l'aide de LINQ :

QueryDefinition query = new QueryDefinition("SELECT TOP 5 * FROM c");
FeedIterator<MyClass> feedIterator = container.GetItemQueryIterator<MyClass>(query);

List<ServerSideCumulativeMetrics> metrics = new List<ServerSideCumulativeMetrics>();
while (feedIterator.HasMoreResults)
{
    // Execute one continuation of the query
    FeedResponse<MyClass> feedResponse = await feedIterator.ReadNextAsync();

    // Store the ServerSideCumulativeMetrics object to aggregate values after all round trips
    metrics.Add(feedResponse.Diagnostics.GetQueryMetrics());
}

// Group metrics by partition key range id
var groupedPartitionMetrics = metrics.SelectMany(m => m.PartitionedMetrics).GroupBy(p => p.PartitionKeyRangeId);
foreach(var partitionGroup in groupedPartitionMetrics)
{
    foreach(var tripMetrics in partitionGroup)
    {
        DoSomethingWithMetrics();
    }
}

Obtenir les frais de requêtes d’interrogation

Vous pouvez capturer les unités de requête consommées par chaque requête afin d’examiner les requêtes coûteuses, c’est-à-dire celles qui consomment un débit élevé. Vous pouvez obtenir le coût total de la demande en utilisant la propriété TotalRequestCharge dans ServerSideCumulativeMetrics, ou vous pouvez consulter le coût de la demande de chaque partition avec la propriété RequestCharge pour chaque ServerSidePartitionedMetrics renvoyées.

Le coût total de la demande est également disponible avec la propriété RequestCharge dans FeedResponse. Pour en savoir plus sur la façon d’obtenir le coût de requête à l’aide du portail Azure et de différents kits de développement logiciel (SDK), voir l’article Rechercher les frais d’unités de requête.

QueryDefinition query = new QueryDefinition("SELECT TOP 5 * FROM c");
FeedIterator<MyClass> feedIterator = container.GetItemQueryIterator<MyClass>(query);

while (feedIterator.HasMoreResults)
{
    // Execute one continuation of the query
    FeedResponse<MyClass> feedResponse = await feedIterator.ReadNextAsync();
    double requestCharge = feedResponse.RequestCharge;

    // Log the RequestCharge how ever you want.
    DoSomeLogging(requestCharge);
}

Obtenir le temps d’exécution de requête

Vous pouvez capturer le temps d'exécution de la requête pour chaque voyage à partir des métriques de la requête. Lorsque l'on examine la latence des requêtes, il est important de différencier le temps d'exécution des requêtes des autres sources de latence, telles que le temps de transit du réseau. L’exemple suivant montre comment obtenir le temps d’exécution cumulé de la requête pour chaque aller-retour :

QueryDefinition query = new QueryDefinition("SELECT TOP 5 * FROM c");
FeedIterator<MyClass> feedIterator = container.GetItemQueryIterator<MyClass>(query);

TimeSpan cumulativeTime;
while (feedIterator.HasMoreResults)
{
    // Execute one continuation of the query
    FeedResponse<MyClass> feedResponse = await feedIterator.ReadNextAsync();
    ServerSideCumulativeMetrics metrics = response.Diagnostics.GetQueryMetrics();
    cumulativeTime = metrics.CumulativeMetrics.TotalTime;
}

// Log the elapsed time
DoSomeLogging(cumulativeTime);

Obtenir l’utilisation de l’index

L’examen de l’utilisation de l’index peut vous aider à déboguer les requêtes lentes. Les requêtes qui ne peuvent pas utiliser l'index donnent lieu à une analyse complète de tous les documents d'un conteneur avant de renvoyer l'ensemble des résultats.

Voici un exemple de requête d’analyse :

SELECT VALUE c.description 
FROM   c 
WHERE UPPER(c.description) = "BABYFOOD, DESSERT, FRUIT DESSERT, WITHOUT ASCORBIC ACID, JUNIOR"

Le filtre de cette requête utilise la fonction système UPPER qui n’est pas servie à partir de l’index. L’exécution de cette requête sur une collection volumineuse a généré les métriques de requête suivantes pour la première continuation :

QueryMetrics

Retrieved Document Count                 :          60,951
Retrieved Document Size                  :     399,998,938 bytes
Output Document Count                    :               7
Output Document Size                     :             510 bytes
Index Utilization                        :            0.00 %
Total Query Execution Time               :        4,500.34 milliseconds
Query Preparation Time                   :             0.2 milliseconds
Index Lookup Time                        :            0.01 milliseconds
Document Load Time                       :        4,177.66 milliseconds
Runtime Execution Time                   :           407.9 milliseconds
Document Write Time                      :            0.01 milliseconds

Notez les valeurs suivantes dans la sortie des métriques de requête :

Retrieved Document Count                 :          60,951
Retrieved Document Size                  :     399,998,938 bytes

Cette requête a chargé 60 951 documents totalisant 399 998 938 d’octets. Le chargement d’un tel nombre d’octets a un coût élevé en terme d’unités de requête. L’exécution de la requête prend également beaucoup de temps, comme le montre clairement la propriété de durée totale :

Total Query Execution Time               :        4,500.34 milliseconds

Cela signifie que l’exécution de la requête a pris 4,5 secondes (et il ne s’agissait là que d’une seule continuation).

Pour optimiser cet exemple de requête, évitez d’utiliser la fonction UPPER dans le filtre. Au lieu de cela, lorsque de la création ou de la mise à jour de documents, les valeurs c.description doivent être insérées en caractères majuscules. La requête devient alors :

SELECT VALUE c.description 
FROM   c 
WHERE c.description = "BABYFOOD, DESSERT, FRUIT DESSERT, WITHOUT ASCORBIC ACID, JUNIOR"

Cette requête peut désormais être servie à partir de l’index. Vous pouvez également utiliser des propriétés calculées pour indexer les résultats des fonctions système ou des calculs complexes qui entraîneraient une analyse complète.

Pour en savoir plus sur le réglage des performances des requêtes, voir l’article Régler les performances des requêtes.

Références

Étapes suivantes