Uzyskiwanie metryk wykonywania zapytań SQL i analizowanie wydajności zapytań przy użyciu zestawu SDK platformy .NET
DOTYCZY: NoSQL
W tym artykule przedstawiono sposób profilowania wydajności zapytań SQL w usłudze Azure Cosmos DB przy użyciu parametrów ServerSideCumulativeMetrics pobranych z zestawu .NET SDK. ServerSideCumulativeMetrics
jest silnie typizowanym obiektem zawierającym informacje o wykonaniu zapytania zaplecza. Zawiera ona skumulowane metryki, które są agregowane we wszystkich partycjach fizycznych dla żądania, listę metryk dla każdej partycji fizycznej i łączną opłatę za żądanie. Te metryki zostały szczegółowo opisane w artykule Dostosowywanie wydajności zapytań.
Pobieranie metryk zapytania
Metryki zapytań są dostępne jako silnie typizowane obiekty w zestawie .NET SDK, począwszy od wersji 3.36.0. Przed tą wersją lub jeśli używasz innego języka zestawu SDK, możesz pobrać metryki zapytań, analizujejąc element Diagnostics
. Poniższy przykładowy kod pokazuje, jak pobrać ServerSideCumulativeMetrics
dane z Diagnostics
elementu w obiekcie 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();
}
Metryki zapytań można również pobrać z FeedResponse
zapytania LINQ przy użyciu ToFeedIterator()
metody :
FeedIterator<MyClass> feedIterator = container.GetItemLinqQueryable<MyClass>()
.Take(5)
.ToFeedIterator();
while (feedIterator.HasMoreResults)
{
FeedResponse<MyClass> feedResponse = await feedIterator.ReadNextAsync();
ServerSideCumulativeMetrics metrics = feedResponse.Diagnostics.GetQueryMetrics();
}
Metryki skumulowane
ServerSideCumulativeMetrics
zawiera właściwość reprezentującą CumulativeMetrics
metryki zapytania zagregowane na wszystkich partycjach dla pojedynczej rundy.
// 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;
Te metryki można również agregować we wszystkich rundach zapytania. Poniżej przedstawiono przykład sposobu agregowania czasu wykonywania zapytania we wszystkich rundach dla danego zapytania przy użyciu 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);
Metryki partycjonowane
ServerSideCumulativeMetrics
zawiera właściwość, która jest listą PartitionedMetrics
metryk dla poszczególnych partycji dla rundy. Jeśli w jednej rundzie zostanie osiągniętych wiele partycji fizycznych, na liście zostaną wyświetlone metryki dla każdego z nich. Partycjonowane metryki są reprezentowane jako ServerSidePartitionedMetrics z unikatowym identyfikatorem dla każdej partycji fizycznej i opłatą za żądanie dla tej partycji.
// 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;
Podczas gromadzenia się na wszystkich rundach metryki partycji umożliwiają sprawdzenie, czy określona partycja powoduje problemy z wydajnością w porównaniu z innymi. Poniżej przedstawiono przykład grupowania metryk partycji dla każdej podróży przy użyciu 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();
}
}
Pobieranie opłaty za żądanie zapytania
Jednostki żądań używane przez każde zapytanie można przechwycić w celu zbadania kosztownych zapytań lub zapytań korzystających z wysokiej przepływności. Możesz uzyskać łączną opłatę za żądanie przy użyciu TotalRequestCharge
właściwości w ServerSideCumulativeMetrics
programie lub sprawdzić opłatę za żądanie z każdej partycji przy użyciu RequestCharge
właściwości dla każdego ServerSidePartitionedMetrics
zwróconego obiektu.
Łączna opłata za żądanie jest również dostępna przy użyciu RequestCharge
właściwości w pliku FeedResponse
. Aby dowiedzieć się więcej na temat sposobu pobierania opłaty za żądanie przy użyciu witryny Azure Portal i różnych zestawów SDK, zobacz artykuł dotyczący opłat za jednostkę żądania.
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);
}
Pobieranie czasu wykonywania zapytania
Możesz przechwycić czas wykonywania zapytań dla każdej podróży z metryk zapytania. Podczas przeglądania opóźnienia żądań ważne jest, aby odróżnić czas wykonywania zapytania od innych źródeł opóźnienia, takich jak czas tranzytowy sieci. W poniższym przykładzie pokazano, jak uzyskać skumulowany czas wykonywania zapytania dla każdej rundy:
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);
Pobieranie wykorzystania indeksu
Przyjrzenie się wykorzystaniu indeksu może pomóc w debugowaniu wolnych zapytań. Zapytania, które nie mogą używać indeksu, powodują pełne skanowanie wszystkich dokumentów w kontenerze przed zwróceniem zestawu wyników.
Oto przykład zapytania skanowania:
SELECT VALUE c.description
FROM c
WHERE UPPER(c.description) = "BABYFOOD, DESSERT, FRUIT DESSERT, WITHOUT ASCORBIC ACID, JUNIOR"
Filtr tego zapytania używa funkcji systemowej UPPER, która nie jest obsługiwana z indeksu. Wykonanie tego zapytania względem dużej kolekcji wywołało następujące metryki zapytania dla pierwszej kontynuacji:
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
Zwróć uwagę na następujące wartości z danych wyjściowych metryk zapytania:
Retrieved Document Count : 60,951
Retrieved Document Size : 399,998,938 bytes
To zapytanie załadowało 60 951 dokumentów, w sumie 399 998 938 bajtów. Ładowanie tych wielu bajtów powoduje wysokie koszty lub opłaty jednostkowe żądania. Wykonanie zapytania trwa również długo, co jest jasne z łącznym czasem spędzonym na właściwości:
Total Query Execution Time : 4,500.34 milliseconds
Oznacza to, że wykonanie zapytania trwało 4,5 sekundy (i było to tylko jedna kontynuacja).
Aby zoptymalizować to przykładowe zapytanie, należy unikać używania funkcji UPPER w filtrze. Zamiast tego po utworzeniu lub zaktualizowaniu c.description
dokumentów należy wstawić wartości we wszystkich wielkich znakach. Następnie zapytanie staje się następujące:
SELECT VALUE c.description
FROM c
WHERE c.description = "BABYFOOD, DESSERT, FRUIT DESSERT, WITHOUT ASCORBIC ACID, JUNIOR"
To zapytanie jest teraz w stanie obsłużyć z indeksu. Alternatywnie można użyć właściwości obliczeniowych do indeksowania wyników funkcji systemowych lub złożonych obliczeń, które w przeciwnym razie spowodują pełne skanowanie.
Aby dowiedzieć się więcej na temat dostrajania wydajności zapytań, zobacz artykuł Dostosowywanie wydajności zapytań.
Informacje
- Azure Cosmos DB SQL specification (Specyfikacja języka SQL w usłudze Azure Cosmos DB)
- ANSI SQL 2011
- JSON
- LINQ