You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: articles/cosmos-db/troubleshoot-query-performance.md
+66-36Lines changed: 66 additions & 36 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -4,7 +4,7 @@ description: Learn how to identify, diagnose, and troubleshoot Azure Cosmos DB S
4
4
author: timsander1
5
5
ms.service: cosmos-db
6
6
ms.topic: troubleshooting
7
-
ms.date: 04/20/2020
7
+
ms.date: 04/22/2020
8
8
ms.author: tisande
9
9
ms.subservice: cosmosdb-sql
10
10
ms.reviewer: sngun
@@ -24,26 +24,28 @@ This article provides examples that you can re-create by using the [nutrition](h
24
24
25
25
## Common SDK issues
26
26
27
-
- For best performance, follow the [Performance tips](performance-tips.md).
27
+
Before reading this guide, it is helpful to consider common SDK issues that aren't related to the query engine.
28
+
29
+
- For best performance, follow these [Performance tips](performance-tips.md).
28
30
> [!NOTE]
29
31
> For improved performance, we recommend Windows 64-bit host processing. The SQL SDK includes a native ServiceInterop.dll to parse and optimize queries locally. ServiceInterop.dll is supported only on the Windows x64 platform. For Linux and other unsupported platforms where ServiceInterop.dll isn't available, an additional network call will be made to the gateway to get the optimized query.
30
-
-You can set a `MaxItemCount` for your queries but you can't specify a minimum item count.
32
+
-The SDK allows setting a `MaxItemCount` for your queries but you can't specify a minimum item count.
31
33
- Code should handle any page size, from zero to the `MaxItemCount`.
32
-
- The number of items in a page will always be less than the specified `MaxItemCount`. However, `MaxItemCount` is strictly a maximum and there could be fewer results than this amount.
34
+
- The number of items in a page will always be less or equal to the specified `MaxItemCount`. However, `MaxItemCount` is strictly a maximum and there could be fewer results than this amount.
33
35
- Sometimes queries may have empty pages even when there are results on a future page. Reasons for this could be:
34
36
- The SDK could be doing multiple network calls.
35
37
- The query might be taking a long time to retrieve the documents.
36
38
- All queries have a continuation token that will allow the query to continue. Be sure to drain the query completely. Look at the SDK samples, and use a `while` loop on `FeedIterator.HasMoreResults` to drain the entire query.
37
39
38
40
## Get query metrics
39
41
40
-
When you optimize a query in Azure Cosmos DB, the first step is always to [get the query metrics](profile-sql-api-query.md) for your query. These metrics are also available through the Azure portal:
42
+
When you optimize a query in Azure Cosmos DB, the first step is always to [get the query metrics](profile-sql-api-query.md) for your query. These metrics are also available through the Azure portal. Once you run your query in the Data Explorer, the query metrics are visible next to the **Results** tab:
After you get the query metrics, compare the Retrieved Document Countwith the Output Document Count for your query. Use this comparison to identify the relevant sections to review in this article.
46
+
After you get the query metrics, compare the **Retrieved Document Count**with the **Output Document Count** for your query. Use this comparison to identify the relevant sections to review in this article.
45
47
46
-
The Retrieved Document Count is the number of documents that the query needed to load. The Output Document Count is the number of documents that were needed for the results of the query. If the Retrieved Document Count is significantly higher than the Output Document Count, there was at least one part of your query that was unable to use the index and needed to do a scan.
48
+
The **Retrieved Document Count** is the number of documents that the query engine needed to load. The **Output Document Count** is the number of documents that were needed for the results of the query. If the **Retrieved Document Count** is significantly higher than the **Output Document Count**, there was at least one part of your query that was unable to use an index and needed to do a scan.
47
49
48
50
Refer to the following sections to understand the relevant query optimizations for your scenario.
49
51
@@ -57,19 +59,19 @@ Refer to the following sections to understand the relevant query optimizations f
57
59
58
60
-[Understand which aggregate queries use the index.](#understand-which-aggregate-queries-use-the-index)
59
61
60
-
-[Modify queries that have both a filter and an ORDER BY clause.](#modify-queries-that-have-both-a-filter-and-an-order-by-clause)
62
+
-[Optimize queries that have both a filter and an ORDER BY clause.](#optimize-queries-that-have-both-a-filter-and-an-order-by-clause)
61
63
62
64
-[Optimize JOIN expressions by using a subquery.](#optimize-join-expressions-by-using-a-subquery)
63
65
64
66
<br>
65
67
66
68
#### Retrieved Document Count is approximately equal to Output Document Count
-[Optimize queries that have filters on multiple properties.](#optimize-queries-that-have-filters-on-multiple-properties)
71
73
72
-
-[Modify queries that have both a filter and an ORDER BY clause.](#modify-queries-that-have-both-a-filter-and-an-order-by-clause)
74
+
-[Optimize queries that have both a filter and an ORDER BY clause.](#optimize-queries-that-have-both-a-filter-and-an-order-by-clause)
73
75
74
76
<br>
75
77
@@ -85,7 +87,7 @@ Refer to the following sections to understand the relevant query optimizations f
85
87
86
88
## Queries where Retrieved Document Count exceeds Output Document Count
87
89
88
-
The Retrieved Document Count is the number of documents that the query needed to load. The Output Document Count is the number of documents that were needed for the results of the query. If the Retrieved Document Count is significantly higher than the Output Document Count, there was at least one part of your query that was unable to use the index and needed to do a scan.
90
+
The **Retrieved Document Count** is the number of documents that the query engine needed to load. The **Output Document Count** is the number of documents returned by the query. If the **Retrieved Document Count** is significantly higher than the **Output Document Count**, there was at least one part of your query that was unable to use an index and needed to do a scan.
89
91
90
92
Here's an example of scan query that wasn't entirely served by the index:
91
93
@@ -123,20 +125,24 @@ Client Side Metrics
123
125
Request Charge : 4,059.95 RUs
124
126
```
125
127
126
-
The Retrieved Document Count (60,951) is significantly higher than the Output Document Count (7), so this query needed to do a scan. In this case, the system function [UPPER()](sql-query-upper.md) doesn't use the index.
128
+
The **Retrieved Document Count** (60,951) is significantly higher than the **Output Document Count** (7), implying that this query resulted in a document scan. In this case, the system function [UPPER()](sql-query-upper.md) doesn't use an index.
127
129
128
130
### Include necessary paths in the indexing policy
129
131
130
-
Your indexing policy should cover any properties included in `WHERE` clauses, `ORDER BY` clauses, `JOIN`, and most system functions. The path specified in the index policy should match (case-sensitive) the property in the JSON documents.
132
+
Your indexing policy should cover any properties included in `WHERE` clauses, `ORDER BY` clauses, `JOIN`, and most system functions. The path specified in the index policy the property in the JSON documents.
133
+
134
+
> [!NOTE] Properties in Azure Cosmos DB indexing policy are case-sensitive
131
135
132
-
If you run a simple query on the [nutrition](https://github.com/CosmosDB/labs/blob/master/dotnet/setup/NutritionData.json) dataset, you observe a much lower RU charge when the property in the `WHERE` clause is indexed:
136
+
If you run the following simple query on the [nutrition](https://github.com/CosmosDB/labs/blob/master/dotnet/setup/NutritionData.json) dataset, you will observe a much lower RU charge when the property in the `WHERE` clause is indexed:
133
137
134
138
#### Original
135
139
136
140
Query:
137
141
138
142
```sql
139
-
SELECT*FROM c WHEREc.description="Malabar spinach, cooked"
143
+
SELECT*
144
+
FROM c
145
+
WHEREc.description="Malabar spinach, cooked"
140
146
```
141
147
142
148
Indexing policy:
@@ -213,42 +219,55 @@ For example, given these two sample queries, the query with both an equality and
213
219
Query with only `CONTAINS` filter - higher RU charge:
214
220
215
221
```sql
216
-
SELECTCOUNT(1) FROM c WHERE CONTAINS(c.description, "spinach")
222
+
SELECTCOUNT(1)
223
+
FROM c
224
+
WHERE CONTAINS(c.description, "spinach")
217
225
```
218
226
219
227
Query with both equality filter and `CONTAINS` filter - lower RU charge:
220
228
221
229
```sql
222
-
SELECTAVG(c._ts) FROM c WHEREc.foodGroup="Sausages and Luncheon Meats"AND CONTAINS(c.description, "spinach")
230
+
SELECTAVG(c._ts)
231
+
FROM c
232
+
WHEREc.foodGroup="Sausages and Luncheon Meats"AND CONTAINS(c.description, "spinach")
223
233
```
224
234
225
-
Here are additional examples of aggregates queries that will not fully use the index:
235
+
Here are additional examples of aggregate queries that will not fully use the index:
226
236
227
237
#### Queries with system functions that don't use the index
228
238
229
239
You should refer to the relevant [system function's page](sql-query-system-functions.md) to see if it uses the index.
230
240
231
241
```sql
232
-
SELECTMAX(c._ts) FROM c WHERE CONTAINS(c.description, "spinach")
242
+
SELECTMAX(c._ts)
243
+
FROM c
244
+
WHERE CONTAINS(c.description, "spinach")
233
245
```
234
246
235
247
#### Aggregate queries with user-defined functions(UDF's)
236
248
237
249
```sql
238
-
SELECTAVG(c._ts) FROM c WHEREudf.MyUDF("Sausages and Luncheon Meats")
250
+
SELECTAVG(c._ts)
251
+
FROM c
252
+
WHEREudf.MyUDF("Sausages and Luncheon Meats")
239
253
```
240
254
241
255
#### Queries with GROUP BY
242
256
243
-
The RU charge of `GROUP BY` will increase as the cardinality of the properties in the `GROUP BY` clause increases. In this example, the query engine must load every document that matches the `c.foodGroup = "Sausages and Luncheon Meats"` filter so the RU charge is expected to be high.
257
+
The RU charge of `GROUP BY` will increase as the cardinality of the properties in the `GROUP BY` clause increases. In the below query, for example, the RU charge of the query will increase as the number unique descriptions increases.
258
+
259
+
The RU charge of an aggregate function with a `GROUP BY` clause will be higher than the RU charge of an aggregate function on its own.In this example, the query engine must load every document that matches the `c.foodGroup = "Sausages and Luncheon Meats"` filter so the RU charge is expected to be high.
244
260
245
261
```sql
246
-
SELECTCOUNT(1) FROM c WHEREc.foodGroup="Sausages and Luncheon Meats"GROUP BYc.description
262
+
SELECTCOUNT(1)
263
+
FROM c
264
+
WHEREc.foodGroup="Sausages and Luncheon Meats"
265
+
GROUP BYc.description
247
266
```
248
267
249
268
If you plan to frequently run the same aggregate queries, it may be more efficient to build a real-time materialized view with the [Azure Cosmos DB change feed](change-feed.md) than running individual queries.
250
269
251
-
### Modify queries that have both a filter and an ORDER BY clause
270
+
### Optimize queries that have both a filter and an ORDER BY clause
252
271
253
272
Although queries that have a filter and an `ORDER BY` clause will normally use a range index, they'll be more efficient if they can be served from a composite index. In addition to modifying the indexing policy, you should add all properties in the composite index to the `ORDER BY` clause. This change to the query will ensure that it uses the composite index. You can observe the impact by running a query on the [nutrition](https://github.com/CosmosDB/labs/blob/master/dotnet/setup/NutritionData.json) dataset:
254
273
@@ -257,7 +276,10 @@ Although queries that have a filter and an `ORDER BY` clause will normally use a
257
276
Query:
258
277
259
278
```sql
260
-
SELECT*FROM c WHEREc.foodGroup="Soups, Sauces, and Gravies"ORDER BYc._tsASC
279
+
SELECT*
280
+
FROM c
281
+
WHEREc.foodGroup="Soups, Sauces, and Gravies"
282
+
ORDER BYc._tsASC
261
283
```
262
284
263
285
Indexing policy:
@@ -283,7 +305,8 @@ Indexing policy:
283
305
Updated query (includes both properties in the `ORDER BY` clause):
284
306
285
307
```sql
286
-
SELECT*FROM c
308
+
SELECT*
309
+
FROM c
287
310
WHEREc.foodGroup="Soups, Sauces, and Gravies"
288
311
ORDER BYc.foodGroup, c._tsASC
289
312
```
@@ -319,6 +342,7 @@ Updated indexing policy:
319
342
**RU charge:** 8.86 RUs
320
343
321
344
### Optimize JOIN expressions by using a subquery
345
+
322
346
Multi-value subqueries can optimize `JOIN` expressions by pushing predicates after each select-many expression rather than after all cross joins in the `WHERE` clause.
323
347
324
348
Consider this query:
@@ -335,7 +359,7 @@ AND n.nutritionValue < 10) AND s.amount > 1
335
359
336
360
**RU charge:** 167.62 RUs
337
361
338
-
For this query, the index will match any document that has a tag with the name "infant formula", nutritionValue greater than 0, and serving amount greater than 1. The `JOIN` expression here will perform the cross-product of all items of tags, nutrients, and servings arrays for each matching document before any filter is applied. The `WHERE` clause will then apply the filter predicate on each `<c, t, n, s>` tuple.
362
+
For this query, the index will match any document that has a tag with the name `infant formula`, `nutritionValue` greater than 0, and `amount` greater than 1. The `JOIN` expression here will perform the cross-product of all items of tags, nutrients, and servings arrays for each matching document before any filter is applied. The `WHERE` clause will then apply the filter predicate on each `<c, t, n, s>` tuple.
339
363
340
364
For example, if a matching document has 10 items in each of the three arrays, it will expand to 1 x 10 x 10 x 10 (that is, 1,000) tuples. The use of subqueries here can help to filter out joined array items before joining with the next expression.
341
365
@@ -355,9 +379,9 @@ Assume that only one item in the tags array matches the filter and that there ar
355
379
356
380
## Queries where Retrieved Document Count is equal to Output Document Count
357
381
358
-
If the Retrieved Document Count is approximately equal to the Output Document Count, the query didn't have to scan many unnecessary documents. For many queries, like those that use the TOP keyword, Retrieved Document Count might exceed Output Document Count by 1. You don't need to be concerned about this.
382
+
If the **Retrieved Document Count** is approximately equal to the **Output Document Count**, the query engine didn't have to scan many unnecessary documents. For many queries, like those that use the `TOP` keyword, **Retrieved Document Count** might exceed **Output Document Count** by 1. You don't need to be concerned about this.
359
383
360
-
### Avoid cross partition queries
384
+
### Minimize cross partition queries
361
385
362
386
Azure Cosmos DB uses [partitioning](partitioning-overview.md) to scale individual containers as Request Unit and data storage needs increase. Each physical partition has a separate and independent index. If your query has an equality filter that matches your container's partition key, you'll need to check only the relevant partition's index. This optimization reduces the total number of RUs that the query requires.
363
387
@@ -366,26 +390,30 @@ If you have a large number of provisioned RUs (more than 30,000) or a large amou
366
390
For example, if you create a container with the partition key foodGroup, the following queries will need to check only a single physical partition:
367
391
368
392
```sql
369
-
SELECT*FROM c
393
+
SELECT*
394
+
FROM c
370
395
WHEREc.foodGroup="Soups, Sauces, and Gravies"andc.description="Mushroom, oyster, raw"
371
396
```
372
397
373
-
These queries would also be optimized by the addition of the partition key in the query:
398
+
Queries that have an `IN` filter with the partition key will only check the relevant physical partition(s) and will not "fan-out":
374
399
375
400
```sql
376
-
SELECT*FROM c
401
+
SELECT*
402
+
FROM c
377
403
WHEREc.foodGroupIN("Soups, Sauces, and Gravies", "Vegetables and Vegetable Products") andc.description="Mushroom, oyster, raw"
378
404
```
379
405
380
-
Queries that have range filters on the partition key, or that don't have any filters on the partition key, will need to check every physical partition's index for results:
406
+
Queries that have range filters on the partition key, or that don't have any filters on the partition key, will need to "fan-out" and check every physical partition's index for results:
381
407
382
408
```sql
383
-
SELECT*FROM c
409
+
SELECT*
410
+
FROM c
384
411
WHEREc.description="Mushroom, oyster, raw"
385
412
```
386
413
387
414
```sql
388
-
SELECT*FROM c
415
+
SELECT*
416
+
FROM c
389
417
WHEREc.foodGroup>"Soups, Sauces, and Gravies"andc.description="Mushroom, oyster, raw"
390
418
```
391
419
@@ -396,12 +424,14 @@ Although queries that have filters on multiple properties will normally use a ra
396
424
Here are some examples of queries that could be optimized with a composite index:
397
425
398
426
```sql
399
-
SELECT*FROM c
427
+
SELECT*
428
+
FROM c
400
429
WHEREc.foodGroup="Vegetables and Vegetable Products"ANDc._ts=1575503264
401
430
```
402
431
403
432
```sql
404
-
SELECT*FROM c
433
+
SELECT*
434
+
FROM c
405
435
WHEREc.foodGroup="Vegetables and Vegetable Products"ANDc._ts>1575503264
0 commit comments