Udostępnij za pośrednictwem


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

Następne kroki