Skip to content

Commit c929d68

Browse files
Merge pull request #262074 from jcocchi/update-query-metrics
Cosmos DB: update query metrics docs
2 parents e4f7d6a + ce09f70 commit c929d68

2 files changed

Lines changed: 175 additions & 264 deletions

File tree

articles/cosmos-db/nosql/query-metrics-performance.md

Lines changed: 106 additions & 116 deletions
Original file line numberDiff line numberDiff line change
@@ -5,168 +5,168 @@ author: ginamr
55
ms.service: cosmos-db
66
ms.subservice: nosql
77
ms.topic: how-to
8-
ms.date: 05/17/2019
8+
ms.date: 1/5/2023
99
ms.author: girobins
1010
ms.custom: devx-track-csharp, ignite-2022, devx-track-dotnet
1111
---
1212
# Get SQL query execution metrics and analyze query performance using .NET SDK
1313
[!INCLUDE[NoSQL](../includes/appliesto-nosql.md)]
1414

15-
This article presents how to profile SQL query performance on Azure Cosmos DB. This profiling can be done using `QueryMetrics` retrieved from the .NET SDK and is detailed here. [QueryMetrics](/dotnet/api/microsoft.azure.documents.querymetrics) is a strongly typed object with information about the backend query execution. These metrics are documented in more detail in the [Tune Query Performance](./query-metrics.md) article.
15+
This article presents how to profile SQL query performance on Azure Cosmos DB using [ServerSideCumulativeMetrics](/dotnet/api/microsoft.azure.cosmos.serversidecumulativemetrics) retrieved from the .NET SDK. `ServerSideCumulativeMetrics` is a strongly typed object with information about the backend query execution. It contains cumulative metrics that are aggregated across all physical partitions for the request, and a list of metrics for each physical partition. These metrics are documented in more detail in the [Tune Query Performance](./query-metrics.md#query-execution-metrics) article.
1616

17-
## Set the FeedOptions parameter
17+
## Get query metrics
1818

19-
All the overloads for [DocumentClient.CreateDocumentQuery](/dotnet/api/microsoft.azure.documents.client.documentclient.createdocumentquery) take in an optional [FeedOptions](/dotnet/api/microsoft.azure.documents.client.feedoptions) parameter. This option is what allows query execution to be tuned and parameterized.
20-
21-
To collect the NoSQL query execution metrics, you must set the parameter [PopulateQueryMetrics](/dotnet/api/microsoft.azure.documents.client.feedoptions.populatequerymetrics#P:Microsoft.Azure.Documents.Client.FeedOptions.PopulateQueryMetrics) in the [FeedOptions](/dotnet/api/microsoft.azure.documents.client.feedoptions) to `true`. Setting `PopulateQueryMetrics` to true will make it so that the `FeedResponse` will contain the relevant `QueryMetrics`.
22-
23-
## Get query metrics with AsDocumentQuery()
24-
The following code sample shows how to do retrieve metrics when using [AsDocumentQuery()](/dotnet/api/microsoft.azure.documents.linq.documentqueryable.asdocumentquery) method:
19+
Query metrics are available as a strongly typed object in the .NET SDK beginning in [version 3.36.0](https://www.nuget.org/packages/Microsoft.Azure.Cosmos/3.36.0). Prior to this version, or if you're using a different SDK language, you can retrieve query metrics by parsing the `Diagnostics`. The following code sample shows how to retrieve `ServerSideCumulativeMetrics` from the `Diagnostics` in a [FeedResponse](/dotnet/api/microsoft.azure.cosmos.feedresponse-1):
2520

2621
```csharp
27-
// Initialize this DocumentClient and Collection
28-
DocumentClient documentClient = null;
29-
DocumentCollection collection = null;
22+
CosmosClient client = new CosmosClient(myCosmosEndpoint, myCosmosKey);
23+
Container container = client.GetDatabase(myDatabaseName).GetContainer(myContainerName);
3024

31-
// Setting PopulateQueryMetrics to true in the FeedOptions
32-
FeedOptions feedOptions = new FeedOptions
33-
{
34-
PopulateQueryMetrics = true
35-
};
25+
QueryDefinition query = new QueryDefinition("SELECT TOP 5 * FROM c");
26+
FeedIterator<MyClass> feedIterator = container.GetItemQueryIterator<MyClass>(query);
3627

37-
string query = "SELECT TOP 5 * FROM c";
38-
IDocumentQuery<dynamic> documentQuery = documentClient.CreateDocumentQuery(Collection.SelfLink, query, feedOptions).AsDocumentQuery();
39-
40-
while (documentQuery.HasMoreResults)
28+
while (feedIterator.HasMoreResults)
4129
{
4230
// Execute one continuation of the query
43-
FeedResponse<dynamic> feedResponse = await documentQuery.ExecuteNextAsync();
44-
45-
// This dictionary maps the partitionId to the QueryMetrics of that query
46-
IReadOnlyDictionary<string, QueryMetrics> partitionIdToQueryMetrics = feedResponse.QueryMetrics;
47-
48-
// At this point you have QueryMetrics which you can serialize using .ToString()
49-
foreach (KeyValuePair<string, QueryMetrics> kvp in partitionIdToQueryMetrics)
50-
{
51-
string partitionId = kvp.Key;
52-
QueryMetrics queryMetrics = kvp.Value;
53-
54-
// Do whatever logging you need
55-
DoSomeLoggingOfQueryMetrics(query, partitionId, queryMetrics);
56-
}
31+
FeedResponse<MyClass> feedResponse = await feedIterator.ReadNextAsync();
32+
33+
// Retrieve the ServerSideCumulativeMetrics object from the FeedResponse
34+
ServerSideCumulativeMetrics metrics = feedResponse.Diagnostics.GetQueryMetrics();
5735
}
5836
```
59-
## Aggregating QueryMetrics
6037

61-
In the previous section, notice that there were multiple calls to [ExecuteNextAsync](/dotnet/api/microsoft.azure.documents.linq.idocumentquery-1.executenextasync) method. Each call returned a `FeedResponse` object that has a dictionary of `QueryMetrics`; one for every continuation of the query. The following example shows how to aggregate these `QueryMetrics` using LINQ:
38+
You can also get query metrics from the `FeedResponse` of a LINQ query using the `ToFeedIterator()` method:
6239

6340
```csharp
64-
List<QueryMetrics> queryMetricsList = new List<QueryMetrics>();
41+
FeedIterator<MyClass> feedIterator = container.GetItemLinqQueryable<MyClass>()
42+
.Take(5)
43+
.ToFeedIterator();
6544

66-
while (documentQuery.HasMoreResults)
45+
while (feedIterator.HasMoreResults)
6746
{
68-
// Execute one continuation of the query
69-
FeedResponse<dynamic> feedResponse = await documentQuery.ExecuteNextAsync();
70-
71-
// This dictionary maps the partitionId to the QueryMetrics of that query
72-
IReadOnlyDictionary<string, QueryMetrics> partitionIdToQueryMetrics = feedResponse.QueryMetrics;
73-
queryMetricsList.AddRange(partitionIdToQueryMetrics.Values);
47+
FeedResponse<MyClass> feedResponse = await feedIterator.ReadNextAsync();
48+
ServerSideCumulativeMetrics metrics = feedResponse.Diagnostics.GetQueryMetrics();
7449
}
75-
76-
// Aggregate the QueryMetrics using the + operator overload of the QueryMetrics class.
77-
QueryMetrics aggregatedQueryMetrics = queryMetricsList.Aggregate((curr, acc) => curr + acc);
78-
Console.WriteLine(aggregatedQueryMetrics);
7950
```
8051

81-
## Grouping query metrics by Partition ID
52+
### Cumulative Metrics
8253

83-
You can group the `QueryMetrics` by the Partition ID. Grouping by Partition ID allows you to see if a specific Partition is causing performance issues when compared to others. The following example shows how to group `QueryMetrics` with LINQ:
54+
`ServerSideCumulativeMetrics` contains a `CumulativeMetrics` property that represents the query metrics aggregated over all partitions for the single round trip.
8455

8556
```csharp
86-
List<KeyValuePair<string, QueryMetrics>> partitionedQueryMetrics = new List<KeyValuePair<string, QueryMetrics>>();
87-
while (documentQuery.HasMoreResults)
88-
{
89-
// Execute one continuation of the query
90-
FeedResponse<dynamic> feedResponse = await documentQuery.ExecuteNextAsync();
91-
92-
// This dictionary is maps the partitionId to the QueryMetrics of that query
93-
IReadOnlyDictionary<string, QueryMetrics> partitionIdToQueryMetrics = feedResponse.QueryMetrics;
94-
partitionedQueryMetrics.AddRange(partitionIdToQueryMetrics.ToList());
95-
}
57+
// Retrieve the ServerSideCumulativeMetrics object from the FeedResponse
58+
ServerSideCumulativeMetrics metrics = feedResponse.Diagnostics.GetQueryMetrics();
59+
60+
// CumulativeMetrics is the metrics for this continuation aggregated over all partitions
61+
ServerSideMetrics cumulativeMetrics = metrics.CumulativeMetrics;
62+
```
9663

97-
// Now we are able to group the query metrics by partitionId
98-
IEnumerable<IGrouping<string, KeyValuePair<string, QueryMetrics>>> groupedByQueryMetrics = partitionedQueryMetrics
99-
.GroupBy(kvp => kvp.Key);
64+
You can also aggregate these metrics across all round trips for the query. The following is an example of how to aggregate query execution time across all round trips for a given query using LINQ:
10065

101-
// If we wanted to we could even aggregate the groupedby QueryMetrics
102-
foreach(IGrouping<string, KeyValuePair<string, QueryMetrics>> grouping in groupedByQueryMetrics)
66+
```csharp
67+
QueryDefinition query = new QueryDefinition("SELECT TOP 5 * FROM c");
68+
FeedIterator<MyClass> feedIterator = container.GetItemQueryIterator<MyClass>(query);
69+
70+
List<ServerSideCumulativeMetrics> metrics = new List<ServerSideCumulativeMetrics>();
71+
TimeSpan cumulativeTime;
72+
while (feedIterator.HasMoreResults)
10373
{
104-
string partitionId = grouping.Key;
105-
QueryMetrics aggregatedQueryMetricsForPartition = grouping
106-
.Select(kvp => kvp.Value)
107-
.Aggregate((curr, acc) => curr + acc);
108-
DoSomeLoggingOfQueryMetrics(query, partitionId, aggregatedQueryMetricsForPartition);
74+
// Execute one continuation of the query
75+
FeedResponse<MyClass> feedResponse = await feedIterator.ReadNextAsync();
76+
77+
// Store the ServerSideCumulativeMetrics object to aggregate values after all round trips
78+
metrics.Add(response.Diagnostics.GetQueryMetrics());
10979
}
80+
81+
// Aggregate values across trips for metrics of interest
82+
TimeSpan totalTripsExecutionTime = metrics.Aggregate(TimeSpan.Zero, (currentSum, next) => currentSum + next.CumulativeMetrics.TotalTime);
83+
DoSomeLogging(totalTripsExecutionTime);
11084
```
11185

112-
## LINQ on DocumentQuery
86+
### Partitioned Metrics
11387

114-
You can also get the `FeedResponse` from a LINQ Query using the `AsDocumentQuery()` method:
88+
`ServerSideCumulativeMetrics` contains a `PartitionedMetrics` property that is a list of per-partition metrics for the round trip. If multiple physical partitions are reached in a single round trip, then metrics for each of them appear in the list. Partitioned metrics are represented as [ServerSidePartitionedMetrics](/dotnet/api/microsoft.azure.cosmos.serversidepartitionedmetrics) with a unique identifier for each physical partition.
11589

11690
```csharp
117-
IDocumentQuery<Document> linqQuery = client.CreateDocumentQuery(collection.SelfLink, feedOptions)
118-
.Take(1)
119-
.Where(document => document.Id == "42")
120-
.OrderBy(document => document.Timestamp)
121-
.AsDocumentQuery();
122-
FeedResponse<Document> feedResponse = await linqQuery.ExecuteNextAsync<Document>();
123-
IReadOnlyDictionary<string, QueryMetrics> queryMetrics = feedResponse.QueryMetrics;
91+
// Retrieve the ServerSideCumulativeMetrics object from the FeedResponse
92+
ServerSideCumulativeMetrics metrics = feedResponse.Diagnostics.GetQueryMetrics();
93+
94+
// PartitionedMetrics is a list of per-partition metrics for this continuation
95+
List<ServerSidePartitionedMetrics> partitionedMetrics = metrics.PartitionedMetrics;
96+
```
97+
98+
When accumulated over all round trips, per partition metrics allow you to see if a specific partition is causing performance issues when compared to others. The following is an example of how to group partition metrics for each trip using LINQ:
99+
100+
```csharp
101+
QueryDefinition query = new QueryDefinition("SELECT TOP 5 * FROM c");
102+
FeedIterator<MyClass> feedIterator = container.GetItemQueryIterator<MyClass>(query);
103+
104+
List<ServerSideCumulativeMetrics> metrics = new List<ServerSideCumulativeMetrics>();
105+
while (feedIterator.HasMoreResults)
106+
{
107+
// Execute one continuation of the query
108+
FeedResponse<MyClass> feedResponse = await feedIterator.ReadNextAsync();
109+
110+
// Store the ServerSideCumulativeMetrics object to aggregate values after all round trips
111+
metrics.Add(response.Diagnostics.GetQueryMetrics());
112+
}
113+
114+
// Group metrics by partition key range id
115+
var groupedPartitionMetrics = metrics.SelectMany(m => m.PartitionedMetrics).GroupBy(p => p.PartitionKeyRangeId);
116+
foreach(var partitionGroup in groupedPartitionMetrics)
117+
{
118+
foreach(var tripMetrics in partitionGroup)
119+
{
120+
DoSomethingWithMetrics();
121+
}
122+
}
124123
```
125124

126-
## Expensive Queries
125+
## Get the query request charge
127126

128-
You can capture the request units consumed by each query to investigate expensive queries or queries that consume high throughput. You can get the request charge by using the [RequestCharge](/dotnet/api/microsoft.azure.documents.client.feedresponse-1.requestcharge) property in `FeedResponse`. To learn more about how to get the request charge using the Azure portal and different SDKs, see [find the request unit charge](find-request-unit-charge.md) article.
127+
You can capture the request units consumed by each query to investigate expensive queries or queries that consume high throughput. You can get the request charge by using the `RequestCharge` property in `FeedResponse`. To learn more about how to get the request charge using the Azure portal and different SDKs, see [find the request unit charge](find-request-unit-charge.md) article.
129128

130129
```csharp
131-
string query = "SELECT * FROM c";
132-
IDocumentQuery<dynamic> documentQuery = documentClient.CreateDocumentQuery(Collection.SelfLink, query, feedOptions).AsDocumentQuery();
130+
QueryDefinition query = new QueryDefinition("SELECT TOP 5 * FROM c");
131+
FeedIterator<MyClass> feedIterator = container.GetItemQueryIterator<MyClass>(query);
133132

134-
while (documentQuery.HasMoreResults)
133+
while (feedIterator.HasMoreResults)
135134
{
136135
// Execute one continuation of the query
137-
FeedResponse<dynamic> feedResponse = await documentQuery.ExecuteNextAsync();
138-
double requestCharge = feedResponse.RequestCharge
139-
136+
FeedResponse<MyClass> feedResponse = await feedIterator.ReadNextAsync();
137+
double requestCharge = feedResponse.RequestCharge;
138+
140139
// Log the RequestCharge how ever you want.
141140
DoSomeLogging(requestCharge);
142141
}
143142
```
144143

145144
## Get the query execution time
146145

147-
When calculating the time required to execute a client-side query, make sure that you only include the time to call the `ExecuteNextAsync` method and not other parts of your code base. Just these calls help you in calculating how long the query execution took as shown in the following example:
146+
You can capture query execution time for each trip from the query metrics. When looking at request latency, it's important to differentiate query execution time from other sources of latency, such as network transit time. The following example shows how to get cumulative query execution time for each round trip:
148147

149148
```csharp
150-
string query = "SELECT * FROM c";
151-
IDocumentQuery<dynamic> documentQuery = documentClient.CreateDocumentQuery(Collection.SelfLink, query, feedOptions).AsDocumentQuery();
152-
Stopwatch queryExecutionTimeEndToEndTotal = new Stopwatch();
153-
while (documentQuery.HasMoreResults)
149+
QueryDefinition query = new QueryDefinition("SELECT TOP 5 * FROM c");
150+
FeedIterator<MyClass> feedIterator = container.GetItemQueryIterator<MyClass>(query);
151+
152+
TimeSpan cumulativeTime;
153+
while (feedIterator.HasMoreResults)
154154
{
155155
// Execute one continuation of the query
156-
queryExecutionTimeEndToEndTotal.Start();
157-
FeedResponse<dynamic> feedResponse = await documentQuery.ExecuteNextAsync();
158-
queryExecutionTimeEndToEndTotal.Stop();
156+
FeedResponse<MyClass> feedResponse = await feedIterator.ReadNextAsync();
157+
ServerSideCumulativeMetrics metrics = response.Diagnostics.GetQueryMetrics();
158+
cumulativeTime = metrics.CumulativeMetrics.TotalTime;
159159
}
160160

161161
// Log the elapsed time
162-
DoSomeLogging(queryExecutionTimeEndToEndTotal.Elapsed);
162+
DoSomeLogging(cumulativeTime);
163163
```
164164

165-
## Scan queries (commonly slow and expensive)
165+
## Get the index utilization
166166

167-
A scan query refers to a query that wasn't served by the index, due to which, many documents are loaded before returning the result set.
167+
Looking at the index utilization can help you debug slow queries. Queries that can't use the index result in a full scan of all documents in a container before returning the result set.
168168

169-
Below is an example of a scan query:
169+
Here's an example of a scan query:
170170

171171
```sql
172172
SELECT VALUE c.description
@@ -185,21 +185,11 @@ Output Document Count : 7
185185
Output Document Size : 510 bytes
186186
Index Utilization : 0.00 %
187187
Total Query Execution Time : 4,500.34 milliseconds
188-
Query Preparation Times
189-
Query Compilation Time : 0.09 milliseconds
190-
Logical Plan Build Time : 0.05 milliseconds
191-
Physical Plan Build Time : 0.04 milliseconds
192-
Query Optimization Time : 0.01 milliseconds
193-
Index Lookup Time : 0.01 milliseconds
194-
Document Load Time : 4,177.66 milliseconds
195-
Runtime Execution Times
196-
Query Engine Times : 322.16 milliseconds
197-
System Function Execution Time : 85.74 milliseconds
198-
User-defined Function Execution Time : 0.00 milliseconds
199-
Document Write Time : 0.01 milliseconds
200-
Client Side Metrics
201-
Retry Count : 0
202-
Request Charge : 4,059.95 RUs
188+
Query Preparation Time : 0.2 milliseconds
189+
Index Lookup Time : 0.01 milliseconds
190+
Document Load Time : 4,177.66 milliseconds
191+
Runtime Execution Time : 407.9 milliseconds
192+
Document Write Time : 0.01 milliseconds
203193
```
204194

205195
Note the following values from the query metrics output:
@@ -225,7 +215,7 @@ FROM c
225215
WHERE c.description = "BABYFOOD, DESSERT, FRUIT DESSERT, WITHOUT ASCORBIC ACID, JUNIOR"
226216
```
227217

228-
This query is now able to be served from the index.
218+
This query is now able to be served from the index. Alternatively, you can use [computed properties](query/computed-properties.md) to index the results of system functions or complex calculations that would otherwise result in a full scan.
229219

230220
To learn more about tuning query performance, see the [Tune Query Performance](./query-metrics.md) article.
231221

0 commit comments

Comments
 (0)