Résoudre des problèmes de requête lors de l’utilisation d’Azure Cosmos DB
S’APPLIQUE À : NoSQL
Cet article décrit une approche générale recommandée pour le dépannage des requêtes dans Azure Cosmos DB. Bien que vous ne devriez pas considérer les étapes décrites dans cet article comme une protection complète contre les problèmes de requête potentiels, nous avons inclus ici les conseils sur les performances les plus courants. Vous devez utiliser cet article comme point de départ pour le dépannage des requêtes lentes ou coûteuses dans Azure Cosmos DB for NoSQL. Vous pouvez également utiliser des journaux de diagnostic pour identifier les requêtes qui sont lentes ou qui consomment un débit significatif. Si vous utilisez l’API d’Azure Cosmos DB pour MongoDB, vous devez utiliser le guide de résolution des problèmes de requête relatif à l’API d’Azure Cosmos DB pour MongoDB.
Les optimisations de requête dans Azure Cosmos DB sont classées de manière générale comme suit :
- celles qui réduisent les frais en unités de requête (RU) de la requête
- Optimisations qui réduisent simplement la latence
Si vous réduisez les frais en RU d’une requête, vous diminuez généralement aussi la latence.
Cet article fournit des exemples que vous pouvez recréer à l’aide du jeu de données nutrition.
Problèmes courants du Kit de développement logiciel (SDK)
Avant de lire ce guide, il est utile de prendre en compte les problèmes courants liés au Kit de développement logiciel (SDK) et qui ne sont pas liés au moteur de requête.
- Suivez les conseils relatifs aux performances du kit SDK pour la requête.
- Parfois, les requêtes peuvent avoir des pages vides, même si des résultats se trouvent sur une page ultérieure. Les raisons peuvent être les suivantes :
- Le Kit de développement logiciel (SDK) peut effectuer plusieurs appels réseau.
- La requête peut prendre beaucoup de temps pour récupérer les documents.
- Toutes les requêtes ont un jeton de continuation qui permet à la requête de continuer. Veillez à vider complètement la requête. En savoir plus sur la gestion de plusieurs pages de résultats
Obtenir les métriques de requête
Lors de l’optimisation d’une requête dans Azure Cosmos DB, la première étape consiste toujours à obtenir les métriques de requête pour votre requête. Ces métriques sont également disponibles par le biais du portail Azure. Une fois que vous avez exécuté votre requête dans l’Explorateur de données, les métriques de requête apparaissent en regard de l’onglet Résultats :
Après avoir obtenu les métriques de requête, comparez le nombre de documents récupérés au nombre de documents de sortie pour votre requête. Utilisez cette comparaison pour identifier les sections pertinentes à vérifier dans cet article.
Le nombre de documents récupérés correspond au nombre de documents que le moteur de requête a dû charger. Le nombre de documents de sortie correspond au nombre de documents qui ont été nécessaires pour les résultats de la requête. Si le nombre de documents récupérés est beaucoup plus élevé que le nombre de documents de sortie, il y a au moins une partie de votre requête qui n’a pas pu utiliser d’index et qui a dû effectuer une analyse.
Reportez-vous aux sections suivantes pour comprendre les optimisations de requête pertinentes pour votre scénario.
Les frais en RU de la requête sont trop élevés
Le nombre de documents récupérés est beaucoup plus élevé que le nombre de documents de sortie
Incluez les chemins nécessaires dans la stratégie d’indexation.
Optimisez les requêtes qui ont à la fois un filtre et une clause ORDER BY.
Le nombre de documents récupérés est à peu près égal au nombre de documents de sortie
Optimisez les requêtes qui ont des filtres sur plusieurs propriétés.
Optimisez les requêtes qui ont à la fois un filtre et une clause ORDER BY.
Les frais en RU de la requête sont acceptables, mais la latence est encore trop élevée
Requêtes où le nombre de documents récupérés dépasse le nombre de documents de sortie
Le nombre de documents récupérés correspond au nombre de documents que le moteur de requête a dû charger. Le nombre de documents de sortie correspond au nombre de documents retournés par la requête. Si le nombre de documents récupérés est beaucoup plus élevé que le nombre de documents de sortie, il y a au moins une partie de votre requête qui n’a pas pu utiliser d’index et qui a dû effectuer une analyse.
Voici un exemple de requête d’analyse qui n’a pas été entièrement servie par l’index :
Requête :
SELECT VALUE c.description
FROM c
WHERE UPPER(c.description) = "BABYFOOD, DESSERT, FRUIT DESSERT, WITHOUT ASCORBIC ACID, JUNIOR"
Métriques de requête :
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 Times
Query Compilation Time : 0.09 milliseconds
Logical Plan Build Time : 0.05 milliseconds
Physical Plan Build Time : 0.04 milliseconds
Query Optimization Time : 0.01 milliseconds
Index Lookup Time : 0.01 milliseconds
Document Load Time : 4,177.66 milliseconds
Runtime Execution Times
Query Engine Times : 322.16 milliseconds
System Function Execution Time : 85.74 milliseconds
User-defined Function Execution Time : 0.00 milliseconds
Document Write Time : 0.01 milliseconds
Client Side Metrics
Retry Count : 0
Request Charge : 4,059.95 RUs
Le nombre de documents récupérés (60 951) est beaucoup plus élevé que le nombre de documents de sortie (7), indiquant que cette requête a entraîné une analyse de document. Dans ce cas, la fonction système UPPER () n’utilise pas d’index.
Inclure les chemins nécessaires dans la stratégie d’indexation
Votre stratégie d’indexation doit couvrir toutes les propriétés incluses dans les clauses WHERE
, les clauses ORDER BY
, JOIN
et la plupart des fonctions système. Les chemins souhaités spécifiés dans la stratégie d’index doivent correspondre aux propriétés dans les documents JSON.
Notes
Les propriétés de la stratégie d’indexation Azure Cosmos DB sont sensibles à la casse
Si vous exécutez la requête simple suivante sur le jeu de données nutrition, vous constaterez que les frais en RU sont plus faibles quand la propriété de la clause WHERE
est indexée :
Original
Requête :
SELECT *
FROM c
WHERE c.description = "Malabar spinach, cooked"
Stratégie d’indexation :
{
"indexingMode": "consistent",
"automatic": true,
"includedPaths": [
{
"path": "/*"
}
],
"excludedPaths": [
{
"path": "/description/*"
}
]
}
Frais en RU : 409.51 RU
Optimisée
Stratégie d’indexation mise à jour :
{
"indexingMode": "consistent",
"automatic": true,
"includedPaths": [
{
"path": "/*"
}
],
"excludedPaths": []
}
Frais en RU : 2.98 RU
Vous pouvez ajouter des propriétés à la stratégie d’indexation à tout moment, sans impact sur la disponibilité en lecture ou en écriture. Vous pouvez suivre la progression de la transformation d’index.
Comprendre quelles fonctions système utilisent l’index
La plupart des fonctions système utilisent des index. Voici une liste de certaines fonctions de chaîne courantes qui utilisent des index :
- StartsWith
- Contient
- RegexMatch
- Gauche
- Substring, mais uniquement si la première num_expr est 0
Voici quelques fonctions système courantes qui n’utilisent pas l’index et doivent charger chaque document lorsqu’elles sont utilisées dans une clause WHERE
:
Fonction système | Idées pour l’optimisation |
---|---|
Upper/Lower | Au lieu d’utiliser la fonction système pour normaliser les données pour les comparaisons, normalisez la casse lors de l’insertion. Une requête telle que SELECT * FROM c WHERE UPPER(c.name) = 'BOB' devient SELECT * FROM c WHERE c.name = 'BOB' . |
GetCurrentDateTime/GetCurrentTimestamp/GetCurrentTicks | Calculez l’heure actuelle avant l’exécution de la requête et utilisez cette valeur de chaîne dans la clause WHERE . |
Fonctions mathématiques (non-agrégations) | Si vous devez calculer fréquemment une valeur dans votre requête, stockez cette valeur en tant que propriété dans votre document JSON. |
Ces fonctions système peuvent utiliser des index, sauf en cas d’utilisation dans les requêtes avec des agrégats :
Fonction système | Idées pour l’optimisation |
---|---|
Fonctions système spatiales | Stocker le résultat de la requête dans une vue matérialisée en temps réel |
Lorsqu’elles sont utilisées dans la clause SELECT
, les fonctions système inefficaces n’ont pas d’impact sur la manière dont les requêtes peuvent utiliser des index.
Améliorer l’exécution des fonctions système de chaîne
Pour certaines fonctions système qui utilisent des index, vous pouvez améliorer l’exécution de la requête en ajoutant à cette dernière une clause ORDER BY
.
Plus spécifiquement, toute fonction système dont les frais de RU augmente à mesure que la cardinalité de la propriété augmente peut tirer parti de la clause ORDER BY
dans la requête. Comme ces requêtes effectuent une analyse d’index, le tri des résultats de la requête peut rendre celle-ci plus efficace.
Cette optimisation peut améliorer l’exécution pour les fonctions système suivantes :
- StartsWith (où non-respect de la casse = true)
- StringEquals (où non-respect de la casse = true)
- Contient
- RegexMatch
- EndsWith
Prenons l’exemple de la requête ci-dessous avec CONTAINS
. CONTAINS
utilisera les index, mais, parfois, même après l’ajout de l’index approprié, vous pouvez encore observer des frais de RU très élevés lors de l’exécution de la requête ci-dessous.
Requête d’origine :
SELECT *
FROM c
WHERE CONTAINS(c.town, "Sea")
Vous pouvez améliorer l’exécution de la requête en ajoutant ORDER BY
:
SELECT *
FROM c
WHERE CONTAINS(c.town, "Sea")
ORDER BY c.town
La même optimisation peut être utile dans les requêtes avec des filtres supplémentaires. Dans ce cas, il est préférable d’ajouter également des propriétés avec des filtres d’égalité à la clause ORDER BY
.
Requête d’origine :
SELECT *
FROM c
WHERE c.name = "Samer" AND CONTAINS(c.town, "Sea")
Vous pouvez améliorer l’exécution de la requête en ajoutant ORDER BY
et un index composite pour (c.name, c.town) :
SELECT *
FROM c
WHERE c.name = "Samer" AND CONTAINS(c.town, "Sea")
ORDER BY c.name, c.town
Identifier les requêtes d’agrégation qui utilisent l’index
Dans la plupart des cas, les fonctions système d’agrégation dans Azure Cosmos DB utilisent l’index. Toutefois, en fonction des filtres ou des clauses supplémentaires dans une requête d’agrégation, le moteur d’interrogation peut être amené à charger un grand nombre de documents. En règle générale, le moteur d’interrogation applique d’abord les filtres d’égalité et de plage. Après l’application de ces filtres, le moteur d’interrogation peut évaluer des filtres supplémentaires et recourir au chargement des documents restants pour calculer l’agrégat, le cas échéant.
Par exemple, pour ces deux exemples de requêtes, la requête avec les filtres de fonction système d’égalité et CONTAINS
est généralement plus efficace qu’une requête avec simplement un filtre de fonction système CONTAINS
. Cela est dû au fait que le filtre d’égalité est appliqué en premier et utilise l’index avant le chargement des documents pour le filtre CONTAINS
, plus onéreux.
Requête avec le filtre CONTAINS
seulement (frais RU plus élevés) :
SELECT COUNT(1)
FROM c
WHERE CONTAINS(c.description, "spinach")
Requête avec le filtre d’égalité et le filtre CONTAINS
(frais RU réduits) :
SELECT AVG(c._ts)
FROM c
WHERE c.foodGroup = "Sausages and Luncheon Meats" AND CONTAINS(c.description, "spinach")
Voici des exemples supplémentaires de requêtes d’agrégation qui n’utiliseront pas pleinement l’index :
Requêtes avec des fonctions système qui n’utilisent pas l’index
Vous devez consulter la page de la fonction système correspondante pour voir si elle utilise l’index.
SELECT MAX(c._ts)
FROM c
WHERE CONTAINS(c.description, "spinach")
Requêtes d’agrégation avec des fonctions définies par l’utilisateur (UDF)
SELECT AVG(c._ts)
FROM c
WHERE udf.MyUDF("Sausages and Luncheon Meats")
Requêtes avec GROUP BY
Les frais RU de requêtes avec GROUP BY
augmenteront à mesure que la cardinalité des propriétés de la clause GROUP BY
augmente. Dans la requête ci-dessous, par exemple, les frais RU de la requête augmentent à mesure que le nombre de descriptions uniques augmente.
Les frais RU d’une fonction d’agrégation avec une clause GROUP BY
seront supérieurs à ceux d’une fonction d’agrégation seule. Dans cet exemple, le moteur d’interrogation doit charger chaque document qui correspond au filtre c.foodGroup = "Sausages and Luncheon Meats"
, de sorte que les frais RU devraient être élevés.
SELECT COUNT(1)
FROM c
WHERE c.foodGroup = "Sausages and Luncheon Meats"
GROUP BY c.description
Si vous prévoyez d’exécuter fréquemment les mêmes requêtes d’agrégation, il peut être plus efficace de créer un affichage matérialisé en temps réel avec le flux de modification Azure Cosmos DB que d’exécuter des requêtes individuelles.
Optimiser les requêtes qui ont à la fois un filtre et une clause ORDER BY
Les requêtes avec un filtre et une clause ORDER BY
utilisent normalement un index de plage, mais elles sont plus efficaces si elles peuvent être servies à partir d’un index composite. En plus de modifier la stratégie d’indexation, vous devez ajouter toutes les propriétés de l’index composite à la clause ORDER BY
. Cette modification de la requête permet de s’assurer qu’elle utilise l’index composite. Vous pouvez observer l’impact en exécutant une requête sur le jeu de données nutrition :
Original
Requête :
SELECT *
FROM c
WHERE c.foodGroup = "Soups, Sauces, and Gravies"
ORDER BY c._ts ASC
Stratégie d’indexation :
{
"automatic":true,
"indexingMode":"Consistent",
"includedPaths":[
{
"path":"/*"
}
],
"excludedPaths":[]
}
Frais en RU : 44.28 RU
Optimisée
Requête mise à jour (comprend les deux propriétés dans la clause ORDER BY
) :
SELECT *
FROM c
WHERE c.foodGroup = "Soups, Sauces, and Gravies"
ORDER BY c.foodGroup, c._ts ASC
Stratégie d’indexation mise à jour :
{
"automatic":true,
"indexingMode":"Consistent",
"includedPaths":[
{
"path":"/*"
}
],
"excludedPaths":[],
"compositeIndexes":[
[
{
"path":"/foodGroup",
"order":"ascending"
},
{
"path":"/_ts",
"order":"ascending"
}
]
]
}
Frais en RU : 8.86 RU
Optimiser les expressions JOIN à l’aide d’une sous-requête
Les sous-requêtes multivaleurs peuvent optimiser des expressions JOIN
en envoyant les prédicats après chaque expression de sélection multiple, plutôt qu’après toutes les jointures croisées dans la clause WHERE
.
Prenons par exemple la requête suivante :
SELECT Count(1) AS Count
FROM c
JOIN t IN c.tags
JOIN n IN c.nutrients
JOIN s IN c.servings
WHERE t.name = 'infant formula' AND (n.nutritionValue > 0
AND n.nutritionValue < 10) AND s.amount > 1
Frais en RU : 167.62 RU
Pour cette requête, l’index correspond à n’importe quel document qui a une étiquette portant le nom infant formula
, avec nutritionValue
supérieure à 0, et amount
supérieure à 1. Dans ce cas, l’expression JOIN
effectue le produit croisé de tous les éléments des tableaux tags, nutrients et servings pour chaque document correspondant avant l’application de tout filtre. Ensuite, la clause WHERE
applique le prédicat de filtre sur chaque tuple <c, t, n, s>
.
Par exemple, si un document correspondant comporte 10 éléments dans chacun des trois tableaux, l’opération aboutit à 1 x 10 x 10 x 10 (autrement dit, 1 000) tuples. L’utilisation de sous-requêtes ici peut aider à filtrer des éléments de tableaux joints avant d’effectuer une jointure avec l’expression suivante.
Cette requête est équivalente à la précédente, mais utilise des sous-requêtes :
SELECT Count(1) AS Count
FROM c
JOIN (SELECT VALUE t FROM t IN c.tags WHERE t.name = 'infant formula')
JOIN (SELECT VALUE n FROM n IN c.nutrients WHERE n.nutritionValue > 0 AND n.nutritionValue < 10)
JOIN (SELECT VALUE s FROM s IN c.servings WHERE s.amount > 1)
Frais en RU : 22.17 RU
Supposons qu’un seul élément du tableau tags correspond au filtre, et qu’il existe cinq éléments pour les tableaux nutrients et servings. Les expressions JOIN
aboutissent à 1 x 1 x 5 x 5 = 25 éléments, au lieu de 1000 éléments dans la première requête.
Requêtes où le nombre de documents récupérés est égal au nombre de documents de sortie
Si le nombre de documents récupérés est à peu près égal au nombre de documents de sortie, le moteur de requête n’a pas eu à analyser de nombreux documents inutiles. Pour de nombreuses requêtes, telles que celles qui utilisent le mot clé TOP
, le nombre de documents récupérés peut dépasser le nombre de documents de sortie de 1. Vous n’avez pas besoin de vous en préoccuper.
Réduire les requêtes entre les partitions
Azure Cosmos DB utilise le partitionnement pour mettre à l’échelle des conteneurs en fonction de l’augmentation des besoins en unités de requête et en stockage des données. Chaque partition physique a un index distinct et indépendant. Si votre requête a un filtre d’égalité qui correspond à la clé de partition de votre conteneur, vous devrez uniquement vérifier l’index de la partition pertinente. Cette optimisation réduit le nombre total de RU requis par la requête.
Si vous disposez d’un grand nombre d’unités de requête provisionnées (plus de 30 000) ou d’une grande quantité de données stockées (plus de 100 Go environ), vous disposez probablement d’un conteneur suffisamment grand pour constater une réduction significative des frais en RU liés aux requêtes.
Par exemple, si vous créez un conteneur avec la clé de partition foodGroup, les requêtes suivantes ne devront vérifier qu’une seule partition physique :
SELECT *
FROM c
WHERE c.foodGroup = "Soups, Sauces, and Gravies" and c.description = "Mushroom, oyster, raw"
Les requêtes qui ont un filtre IN
avec la clé de partition ne vérifient que les partitions physiques pertinentes et n’effectuent pas de distribution ramifiée (fan-out) :
SELECT *
FROM c
WHERE c.foodGroup IN("Soups, Sauces, and Gravies", "Vegetables and Vegetable Products") and c.description = "Mushroom, oyster, raw"
Les requêtes qui ont des filtres de plage sur la clé de partition ou qui n’ont aucun filtre sur la clé de partition devront vérifier l’index de chaque partition physique pour obtenir les résultats :
SELECT *
FROM c
WHERE c.description = "Mushroom, oyster, raw"
SELECT *
FROM c
WHERE c.foodGroup > "Soups, Sauces, and Gravies" and c.description = "Mushroom, oyster, raw"
Optimiser les requêtes qui ont des filtres sur plusieurs propriétés
Les requêtes avec des filtres sur plusieurs propriétés utilisent normalement un index de plage, mais elles sont plus efficaces si elles peuvent être servies à partir d’un index composite. Pour les petites quantités de données, cette optimisation n’aura pas d’impact significatif. Toutefois, elle peut s’avérer utile pour les grandes quantités de données. Vous pouvez uniquement optimiser, au plus, un filtre de non-égalité par index composite. Si votre requête comporte plusieurs filtres de non-égalité, choisissez l’un d’entre eux qui utilisera l’index composite. Le reste va continuer à utiliser des index de plage. Le filtre de non-égalité doit être défini en dernier dans l’index composite. En savoir plus sur les index composites.
Voici quelques exemples de requêtes qui peuvent être optimisées avec un index composite :
SELECT *
FROM c
WHERE c.foodGroup = "Vegetables and Vegetable Products" AND c._ts = 1575503264
SELECT *
FROM c
WHERE c.foodGroup = "Vegetables and Vegetable Products" AND c._ts > 1575503264
Voici l’index composite approprié :
{
"automatic":true,
"indexingMode":"Consistent",
"includedPaths":[
{
"path":"/*"
}
],
"excludedPaths":[],
"compositeIndexes":[
[
{
"path":"/foodGroup",
"order":"ascending"
},
{
"path":"/_ts",
"order":"ascending"
}
]
]
}
Optimisations qui réduisent la latence des requêtes
Dans de nombreux cas, les frais en RU peuvent être acceptables, même si la latence des requêtes est encore trop élevée. Les sections suivantes présentent différents conseils pour réduire la latence des requêtes. Si vous exécutez la même requête plusieurs fois sur le même jeu de données, elle aura généralement les mêmes frais de RU à chaque fois. Mais la latence des requêtes peut varier d’une exécution de requête à une autre.
Améliorer la proximité
Les requêtes exécutées à partir d’une région différente de celle du compte Azure Cosmos DB auront une latence plus élevée que si elles étaient exécutées dans la même région. Par exemple, si vous exécutez du code sur votre ordinateur de bureau, vous devez vous attendre à une latence supérieure de quelques dizaines ou centaines de millisecondes (ou plus) à la même requête provenant d’une machine virtuelle située dans la même région Azure qu’Azure Cosmos DB. Il est facile de distribuer les données globalement dans Azure Cosmos DB pour vous assurer de pouvoir rapprocher vos données de votre application.
Augmenter le débit provisionné
Dans Azure Cosmos DB, votre débit provisionné est mesuré en unités de requête (RU). Supposez que vous avez une requête qui consomme 5 RU de débit. Par exemple, si vous provisionnez 1 000 RU, vous pouvez exécuter cette requête 200 fois par seconde. Si vous avez essayé d’exécuter la requête alors que le débit disponible est insuffisant, Azure Cosmos DB retourne une erreur HTTP 429. Les kits SDK actuels de l’API pour NoSQL réessaient automatiquement d’exécuter cette requête après un bref délai. Les requêtes limitées prennent plus de temps ; par conséquent, l’augmentation du débit provisionné peut améliorer la latence des requêtes. Vous pouvez observer le nombre total de requêtes limitées dans le panneau Métriques du portail Azure.
Augmenter MaxConcurrency
Les requêtes parallèles interrogent plusieurs partitions en parallèle. Les données d’une collection partitionnée individuelle sont toutefois extraites en série dans le cadre de la requête. Par conséquent, si vous réglez de MaxConcurrency sur le nombre de partitions, cela vous donnera les meilleures chances de résultats pour la requête, sous réserve que toutes les autres conditions système restent inchangées. Si vous ne connaissez pas le nombre de partitions, vous pouvez définir MaxConcurrency (ou MaxDegreesOfParallelism dans les versions antérieures du SDK) sur un nombre élevé. Le système choisit la valeur minimale (nombre de partitions, entrée fournie par l’utilisateur) comme degré maximal de parallélisme.
Augmenter MaxBufferedItemCount
Les requêtes sont conçues pour pré-extraire les résultats pendant que le lot de résultats actuel est en cours de traitement par le client. La pré-récupération permet d’améliorer la latence globale d’une requête. La définition de setMaxBufferedItemCount limite le nombre de résultats pré-extraits. Pour que la requête tire le maximum de bénéfices de la pré-extraction, affectez à cette valeur le nombre de résultats attendus (ou un nombre plus élevé). Si vous définissez cette valeur sur-1, le système détermine automatiquement le nombre d’éléments à mettre en mémoire tampon.
Étapes suivantes
Reportez-vous aux articles suivants pour des informations sur la mesure des unités de requête par requête et pour obtenir des statistiques d’exécution afin d’optimiser vos requêtes, entre autres :
- Obtenir des métriques sur l’exécution des requêtes SQL à l’aide du SDK .NET
- Réglage des performances de requête avec Azure Cosmos DB
- Conseils en matière de performances pour le Kit de développement logiciel (SDK) .NET
- Conseils en matière de performances pour le Kit de développement logiciel (SDK) Java v4