Получение метрик выполнения SQL-запросов и анализ производительности запросов с помощью пакета SDK для .NET
ОБЛАСТЬ ПРИМЕНЕНИЯ: NoSQL
В этой статье показано, как профилировать производительность sql-запросов в Azure Cosmos DB с помощью ServerSideCumulativeMetrics , полученных из пакета SDK для .NET. ServerSideCumulativeMetrics
— это строго типизированный объект с информацией о выполнении внутреннего запроса. Он содержит накопительные метрики, агрегированные во всех физических секциях запроса, список метрик для каждой физической секции и общая плата за запрос. Эти метрики более подробно описаны в статье Настройка производительности запросов.
Получение метрик запроса
Метрики запросов доступны в виде строго типизированного объекта в пакете SDK для .NET, начиная с версии 3.36.0. До этой версии или при использовании другого языка ПАКЕТА SDK можно получить метрики запросов, проанализировав его Diagnostics
. В следующем примере кода показано, как получить ServerSideCumulativeMetrics
из Diagnostics
канала 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();
}
Метрики запросов также можно получить из FeedResponse
запроса LINQ с помощью ToFeedIterator()
метода:
FeedIterator<MyClass> feedIterator = container.GetItemLinqQueryable<MyClass>()
.Take(5)
.ToFeedIterator();
while (feedIterator.HasMoreResults)
{
FeedResponse<MyClass> feedResponse = await feedIterator.ReadNextAsync();
ServerSideCumulativeMetrics metrics = feedResponse.Diagnostics.GetQueryMetrics();
}
Накопительные метрики
ServerSideCumulativeMetrics
содержит CumulativeMetrics
свойство, представляющее метрики запросов, агрегированные по всем секциям для единого кругового пути.
// 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;
Эти метрики можно также агрегировать во всех круговых поездках для запроса. Ниже приведен пример того, как агрегировать время выполнения запроса во всех обходах для данного запроса с помощью 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);
Секционированные метрики
ServerSideCumulativeMetrics
содержит PartitionedMetrics
свойство, которое является списком метрик для секции для кругового пути. Если в одном круглом пути достигается несколько физических секций, в списке отображаются метрики для каждого из них. Секционированные метрики представлены как ServerSidePartitionedMetrics с уникальным идентификатором для каждой физической секции и плата за запрос для этой секции.
// 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;
При накоплении во всех круговых поездках метрики секций позволяют узнать, вызывает ли определенная секция проблемы с производительностью по сравнению с другими. Ниже приведен пример группирования метрик секции для каждой поездки с помощью 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();
}
}
Получение платы за запрос
Можно записать количество единиц запросов, потребляемых каждым из них, чтобы проверить ресурсоемкие запросы или запросы, требующие высокую пропускную способность. Вы можете получить общую плату за запрос с помощью TotalRequestCharge
свойства или ServerSideCumulativeMetrics
просмотреть плату за запрос из каждой RequestCharge
секции, используя свойство для каждого ServerSidePartitionedMetrics
возвращенного свойства.
Общая плата за запрос также доступна с помощью RequestCharge
свойства в FeedResponse
. Дополнительные сведения о том, как получить сведения об оплате запросов с помощью портала Azure и различных пакетов SDK, см. в статье Поиск расходов в единицах запросов для операций, выполняемых в Azure Cosmos DB SQL API.
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);
}
Получение времени выполнения запроса
Вы можете записать время выполнения запроса для каждой поездки из метрик запроса. При просмотре задержки запроса важно отличать время выполнения запроса от других источников задержки, например сетевого транзита. В следующем примере показано, как получить совокупное время выполнения запроса для каждого кругового пути:
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);
Получение использования индекса
Просмотр использования индекса позволяет выполнять отладку медленных запросов. Запросы, которые не могут использовать индекс, приводят к полному сканированию всех документов в контейнере перед возвратом результирующий набор.
Ниже приведен пример запроса сканирования:
SELECT VALUE c.description
FROM c
WHERE UPPER(c.description) = "BABYFOOD, DESSERT, FRUIT DESSERT, WITHOUT ASCORBIC ACID, JUNIOR"
В фильтре запроса используется системная функция UPPER, которая не обрабатывается из индекса. При выполнении этого запроса на соответствие большой коллекции были получены следующие метрики запроса для первого продолжения:
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
Обратите внимание на следующие значения из выходных данных метрик запроса:
Retrieved Document Count : 60,951
Retrieved Document Size : 399,998,938 bytes
Этот запрос загрузил 60 951 документ, которые в сумме занимают 399 998 938 байт. Загрузка такого большого числа байтов требует больших затрат или затрат единиц запросов. Выполнение запроса также занимает много времени, на что указывает значение свойства общего затраченного времени:
Total Query Execution Time : 4,500.34 milliseconds
Это означает, что выполнение запроса заняло 4,5 секунды (и это было только одно продолжение).
Чтобы оптимизировать этот пример запроса, не используйте UPPER в фильтре. Вместо этого при создании или обновлении документов необходимо вставлять значения c.description
прописными буквами. Затем запрос изменится на:
SELECT VALUE c.description
FROM c
WHERE c.description = "BABYFOOD, DESSERT, FRUIT DESSERT, WITHOUT ASCORBIC ACID, JUNIOR"
Теперь этот запрос может быть обслужен из индекса. Кроме того, вы можете использовать вычисляемые свойства для индексирования результатов системных функций или сложных вычислений, которые в противном случае приводят к полному сканированию.
Дополнительные сведения о настройке производительности запросов см. в статье Настройка производительности запросов.
Ссылки
- DocumentDB SQL Syntax (Синтаксис SQL в DocumentDB)
- ANSI SQL 2011
- JSON
- LINQ