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
+67-36Lines changed: 67 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 Count with 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,25 @@ 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 desired paths specified in the index policy should match the properties in the JSON documents.
133
+
134
+
> [!NOTE]
135
+
> Properties in Azure Cosmos DB indexing policy are case-sensitive
131
136
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:
137
+
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
138
134
139
#### Original
135
140
136
141
Query:
137
142
138
143
```sql
139
-
SELECT*FROM c WHEREc.description="Malabar spinach, cooked"
144
+
SELECT*
145
+
FROM c
146
+
WHEREc.description="Malabar spinach, cooked"
140
147
```
141
148
142
149
Indexing policy:
@@ -213,42 +220,55 @@ For example, given these two sample queries, the query with both an equality and
213
220
Query with only `CONTAINS` filter - higher RU charge:
214
221
215
222
```sql
216
-
SELECTCOUNT(1) FROM c WHERE CONTAINS(c.description, "spinach")
223
+
SELECTCOUNT(1)
224
+
FROM c
225
+
WHERE CONTAINS(c.description, "spinach")
217
226
```
218
227
219
228
Query with both equality filter and `CONTAINS` filter - lower RU charge:
220
229
221
230
```sql
222
-
SELECTAVG(c._ts) FROM c WHEREc.foodGroup="Sausages and Luncheon Meats"AND CONTAINS(c.description, "spinach")
231
+
SELECTAVG(c._ts)
232
+
FROM c
233
+
WHEREc.foodGroup="Sausages and Luncheon Meats"AND CONTAINS(c.description, "spinach")
223
234
```
224
235
225
-
Here are additional examples of aggregates queries that will not fully use the index:
236
+
Here are additional examples of aggregate queries that will not fully use the index:
226
237
227
238
#### Queries with system functions that don't use the index
228
239
229
240
You should refer to the relevant [system function's page](sql-query-system-functions.md) to see if it uses the index.
230
241
231
242
```sql
232
-
SELECTMAX(c._ts) FROM c WHERE CONTAINS(c.description, "spinach")
243
+
SELECTMAX(c._ts)
244
+
FROM c
245
+
WHERE CONTAINS(c.description, "spinach")
233
246
```
234
247
235
248
#### Aggregate queries with user-defined functions(UDF's)
236
249
237
250
```sql
238
-
SELECTAVG(c._ts) FROM c WHEREudf.MyUDF("Sausages and Luncheon Meats")
251
+
SELECTAVG(c._ts)
252
+
FROM c
253
+
WHEREudf.MyUDF("Sausages and Luncheon Meats")
239
254
```
240
255
241
256
#### Queries with GROUP BY
242
257
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.
258
+
The RU charge of queries with `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.
259
+
260
+
The RU charge of an aggregate function with a `GROUP BY` clause will be higher than the RU charge of an aggregate function alone. 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
261
245
262
```sql
246
-
SELECTCOUNT(1) FROM c WHEREc.foodGroup="Sausages and Luncheon Meats"GROUP BYc.description
263
+
SELECTCOUNT(1)
264
+
FROM c
265
+
WHEREc.foodGroup="Sausages and Luncheon Meats"
266
+
GROUP BYc.description
247
267
```
248
268
249
269
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
270
251
-
### Modify queries that have both a filter and an ORDER BY clause
271
+
### Optimize queries that have both a filter and an ORDER BY clause
252
272
253
273
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
274
@@ -257,7 +277,10 @@ Although queries that have a filter and an `ORDER BY` clause will normally use a
257
277
Query:
258
278
259
279
```sql
260
-
SELECT*FROM c WHEREc.foodGroup="Soups, Sauces, and Gravies"ORDER BYc._tsASC
280
+
SELECT*
281
+
FROM c
282
+
WHEREc.foodGroup="Soups, Sauces, and Gravies"
283
+
ORDER BYc._tsASC
261
284
```
262
285
263
286
Indexing policy:
@@ -283,7 +306,8 @@ Indexing policy:
283
306
Updated query (includes both properties in the `ORDER BY` clause):
284
307
285
308
```sql
286
-
SELECT*FROM c
309
+
SELECT*
310
+
FROM c
287
311
WHEREc.foodGroup="Soups, Sauces, and Gravies"
288
312
ORDER BYc.foodGroup, c._tsASC
289
313
```
@@ -319,6 +343,7 @@ Updated indexing policy:
319
343
**RU charge:** 8.86 RUs
320
344
321
345
### Optimize JOIN expressions by using a subquery
346
+
322
347
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
348
324
349
Consider this query:
@@ -335,7 +360,7 @@ AND n.nutritionValue < 10) AND s.amount > 1
335
360
336
361
**RU charge:** 167.62 RUs
337
362
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.
363
+
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
364
340
365
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
366
@@ -355,9 +380,9 @@ Assume that only one item in the tags array matches the filter and that there ar
355
380
356
381
## Queries where Retrieved Document Count is equal to Output Document Count
357
382
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.
383
+
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
384
360
-
### Avoid cross partition queries
385
+
### Minimize cross partition queries
361
386
362
387
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
388
@@ -366,26 +391,30 @@ If you have a large number of provisioned RUs (more than 30,000) or a large amou
366
391
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
392
368
393
```sql
369
-
SELECT*FROM c
394
+
SELECT*
395
+
FROM c
370
396
WHEREc.foodGroup="Soups, Sauces, and Gravies"andc.description="Mushroom, oyster, raw"
371
397
```
372
398
373
-
These queries would also be optimized by the addition of the partition key in the query:
399
+
Queries that have an `IN` filter with the partition key will only check the relevant physical partition(s) and will not "fan-out":
374
400
375
401
```sql
376
-
SELECT*FROM c
402
+
SELECT*
403
+
FROM c
377
404
WHEREc.foodGroupIN("Soups, Sauces, and Gravies", "Vegetables and Vegetable Products") andc.description="Mushroom, oyster, raw"
378
405
```
379
406
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:
407
+
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
408
382
409
```sql
383
-
SELECT*FROM c
410
+
SELECT*
411
+
FROM c
384
412
WHEREc.description="Mushroom, oyster, raw"
385
413
```
386
414
387
415
```sql
388
-
SELECT*FROM c
416
+
SELECT*
417
+
FROM c
389
418
WHEREc.foodGroup>"Soups, Sauces, and Gravies"andc.description="Mushroom, oyster, raw"
390
419
```
391
420
@@ -396,12 +425,14 @@ Although queries that have filters on multiple properties will normally use a ra
396
425
Here are some examples of queries that could be optimized with a composite index:
397
426
398
427
```sql
399
-
SELECT*FROM c
428
+
SELECT*
429
+
FROM c
400
430
WHEREc.foodGroup="Vegetables and Vegetable Products"ANDc._ts=1575503264
401
431
```
402
432
403
433
```sql
404
-
SELECT*FROM c
434
+
SELECT*
435
+
FROM c
405
436
WHEREc.foodGroup="Vegetables and Vegetable Products"ANDc._ts>1575503264
0 commit comments