Ottenere le metriche di esecuzione delle query SQL e analizzare le prestazioni delle query con .NET SDK
SI APPLICA A: NoSQL
Questo articolo illustra come profilare le prestazioni delle query SQL in Azure Cosmos DB usando l'oggetto ServerSideCumulativeMetrics recuperato da .NET SDK. ServerSideCumulativeMetrics
è un oggetto fortemente tipizzato con informazioni sull'esecuzione delle query back-end. Contiene metriche cumulative aggregate in tutte le partizioni fisiche per la richiesta, un elenco di metriche per ogni partizione fisica e l'addebito totale delle richieste. Queste metriche sono documentate in modo più dettagliato nell'articolo Ottimizzare le prestazioni delle query.
Recuperare le metriche della query
Le metriche delle query sono disponibili come oggetto fortemente tipizzato in .NET SDK a partire dalla versione 3.36.0. Prima di questa versione o se si usa un linguaggio SDK diverso, è possibile recuperare le metriche di query analizzando Diagnostics
. L'esempio di codice seguente illustra come recuperare ServerSideCumulativeMetrics
da Diagnostics
in un oggetto 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();
}
È anche possibile ottenere le metriche delle query dall'oggetto FeedResponse
di una query LINQ usando il metodo ToFeedIterator()
:
FeedIterator<MyClass> feedIterator = container.GetItemLinqQueryable<MyClass>()
.Take(5)
.ToFeedIterator();
while (feedIterator.HasMoreResults)
{
FeedResponse<MyClass> feedResponse = await feedIterator.ReadNextAsync();
ServerSideCumulativeMetrics metrics = feedResponse.Diagnostics.GetQueryMetrics();
}
Metriche cumulative
ServerSideCumulativeMetrics
contiene una proprietà CumulativeMetrics
che rappresenta le metriche di query aggregate per tutte le partizioni per il singolo round trip.
// 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;
È anche possibile aggregare queste metriche per tutti i round trip della query. Di seguito è riportato un esempio di come aggregare il tempo di esecuzione delle query per tutti i round trip di una determinata query usando 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);
Metriche per partizione
ServerSideCumulativeMetrics
contiene una proprietà PartitionedMetrics
che rappresenta un elenco di metriche per partizione per il round trip. Se in un singolo round trip vengono raggiunte più partizioni fisiche, nell'elenco vengono visualizzate le metriche per ognuna delle partizioni. Le metriche partizionate sono rappresentate come ServerSidePartitionedMetrics con un identificatore univoco per ogni partizione fisica e addebito della richiesta per tale partizione.
// 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;
Quando vengono accumulate per tutti i round trip, le metriche per partizione consentono di verificare se una partizione specifica causa problemi di prestazioni rispetto alle altre. Di seguito è riportato un esempio di come raggruppare le metriche per partizione per ogni round trip usando 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();
}
}
Ottenere l'addebito delle richieste per le query
È possibile acquisire le unità richiesta utilizzate da ogni query per individuare le query costose o che utilizzano una velocità effettiva elevata. È possibile ottenere l'addebito totale della richiesta usando la TotalRequestCharge
proprietà in ServerSideCumulativeMetrics
oppure esaminare l'addebito della richiesta da ogni partizione usando la RequestCharge
proprietà per ogni ServerSidePartitionedMetrics
partizione restituita.
L'addebito totale per le richieste è disponibile anche usando la RequestCharge
proprietà in FeedResponse
. Per altre informazioni su come ottenere l'addebito delle richieste tramite il portale di Azure e diversi SDK, vedere l'articolo su come trovare l'addebito delle unità richiesta.
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);
}
Ottenere il tempo di esecuzione delle query
È possibile acquisire il tempo di esecuzione delle query per ogni round trip dalle metriche di query. Quando si esamina la latenza delle richieste, è importante distinguere il tempo di esecuzione delle query dalle altre cause di latenza, ad esempio il tempo impiegato per il trasferimento di rete. L'esempio seguente illustra come ottenere il tempo di esecuzione cumulativo delle query per ogni round trip:
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);
Ottenere l'utilizzo dell'indice
L'analisi dell'utilizzo dell'indice consente di eseguire il debug delle query lente. Le query che non possono usare il risultato dell'indice danno luogo a un'analisi completa di tutti i documenti in un contenitore prima di restituire il set di risultati.
Ecco un esempio di query di analisi:
SELECT VALUE c.description
FROM c
WHERE UPPER(c.description) = "BABYFOOD, DESSERT, FRUIT DESSERT, WITHOUT ASCORBIC ACID, JUNIOR"
Il filtro di questa query usa la funzione di sistema UPPER, che non viene servita dall'indice. L'esecuzione di questa query su una raccolta di grandi dimensioni ha prodotto le metriche delle query seguenti per la prima continuazione:
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
Si notino i valori seguenti dell'output delle metriche delle query:
Retrieved Document Count : 60,951
Retrieved Document Size : 399,998,938 bytes
Questa query ha caricato 60.951 documenti, per un totale di 399.998.938 byte. Il caricamento di questo numero di byte comporta costi elevati o addebiti per unità richiesta. Inoltre, l'esecuzione della query richiede molto tempo, come risulta chiaramente dalla proprietà relativa al tempo totale impiegato:
Total Query Execution Time : 4,500.34 milliseconds
Ciò significa che l'esecuzione della query ha richiesto 4,5 secondi (e questa era una sola continuazione).
Per ottimizzare questa query di esempio, evitare l'uso di UPPER nel filtro. Quando invece vengono creati o aggiornati documenti, i valori c.description
devono essere inseriti in caratteri maiuscoli. La query diventa quindi:
SELECT VALUE c.description
FROM c
WHERE c.description = "BABYFOOD, DESSERT, FRUIT DESSERT, WITHOUT ASCORBIC ACID, JUNIOR"
Questa query ora può essere servita dall'indice. In alternativa, è possibile usare proprietà calcolate per indicizzare i risultati delle funzioni di sistema o calcoli complessi che altrimenti genererebbero un'analisi completa.
Per altre informazioni sull'ottimizzazione delle prestazioni delle query, vedere l'articolo Ottimizzare le prestazioni delle query.