Share via


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 :

Obtention de métriques de requête

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


Le nombre de documents récupérés est à peu près égal au nombre de documents de sortie


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 :