Réglage des performances de requête avec Azure Cosmos DB

S’APPLIQUE À : NoSQL

Azure Cosmos DB fournit une API pour NoSQL pour interroger des données, sans nécessiter de schéma ou d’index secondaires. Cet article fournit les informations suivantes à l’attention des développeurs :

  • Détails techniques sur le fonctionnement de l’exécution de requêtes SQL dans Azure Cosmos DB
  • Conseils et meilleures pratiques pour les performances de requêtes
  • Exemples montrant comment utiliser les mesures d’exécution de la requête SQL pour déboguer les performances de requêtes

À propos de l’exécution de requêtes SQL

Dans Azure Cosmos DB, les données sont stockées dans des conteneurs pouvant se remplir à n’importe quel débit de requête ou taille de stockage. Azure Cosmos DB adapte de façon continue les données entre les partitions physiques situées en arrière-plan, pour gérer la croissance des données ou augmente le débit configuré. Vous pouvez émettre des requêtes SQL sur n’importe quel conteneur à l’aide de l’API REST ou de l’un des kits SDK SQL pris en charge.

Pour résumer brièvement le partitionnement : vous définissez une clé de partition, par exemple "city", qui détermine la façon dont les données sont fractionnées entre plusieurs partitions physiques. Les données appartenant à une clé de partition unique (par exemple, "city" == "Seattle") sont stockées dans une partition physique, et une partition physique unique peut stocker des données à partir de plusieurs clés de partition. Lorsqu’une partition atteint sa limite de stockage, le service divise la partition en deux nouvelles partitions. Les données sont réparties uniformément entre les nouvelles partitions, en conservant toutes les données d’une clé de partition unique. Étant donné que les partitions sont temporaires, les API utilisent une abstraction d’une plage de clé de partition représentant les plages des hachages de clé de partition.

Lorsque vous exécutez une requête sur Azure Cosmos DB, le kit SDK exécute les étapes logiques suivantes :

  • Analyse de la requête SQL pour déterminer le plan d’exécution de la requête.
  • Si la requête contient un filtre sur la clé de partition, tel que SELECT * FROM c WHERE c.city = "Seattle", elle est routée vers une partition unique. Si la requête n’a pas de filtre sur la clé de partition, elle est exécutée dans toutes les partitions et les résultats de chaque partition sont fusionnés côté client.
  • La requête est exécutée dans chaque partition, en série ou en parallèle, selon la configuration du client. Dans chaque partition, la requête risque de faire un ou plusieurs allers-retours selon la complexité de la requête, la taille de page configurée et le débit approvisionné de la collection. Chaque exécution renvoie le nombre d’unités de requête consommées par exécution de la requête et des statistiques d’exécution de la requête.
  • Le kit SDK réalise une synthèse des résultats de la requête sur les partitions. Par exemple, si la requête implique une clause ORDER BY sur l’ensemble des partitions, les résultats de chacune des partitions sont fusionnés et triés pour retourner un résultat global trié. Si la requête est une agrégation telle que COUNT, les nombres provenant de chaque partition sont additionnés pour obtenir le nombre total.

Les kits SDK offrent diverses options pour l’exécution de requêtes. Par exemple, dans .NET ces options sont disponibles dans la classe QueryRequestOptions. La table suivante décrit ces options ainsi que leur incidence sur la durée d’exécution de la requête.

Option Description
EnableScanInQuery Uniquement applicable si l’indexation pour le chemin du filtre demandé est désactivé. Doit être défini sur true si vous avez refusé l’indexation et que vous souhaitez exécuter des requêtes à l’aide d’une analyse complète.
MaxItemCount Le nombre maximal d’éléments à retourner par aller-retour au serveur. Vous pouvez définir la valeur sur -1 pour permettre au serveur de gérer le nombre d’éléments à renvoyer.
MaxBufferedItemCount Le nombre maximal d’éléments pouvant être mis en mémoire tampon côté client lors de l’exécution de requête en parallèle. Une valeur de propriété positive limite le nombre d’éléments mis en mémoire tampon à la valeur définie. Vous pouvez définir la valeur sur un chiffre inférieur à 0 pour permettre au système de décider automatiquement du nombre d’éléments à mettre en mémoire tampon.
MaxConcurrency Obtient ou définit le nombre d’opérations simultanées qui sont exécutées côté client, lors de l’exécution de la requête en parallèle. Une valeur de propriété positive limite le nombre d’opérations simultanées à la valeur définie. Vous pouvez définir la valeur sur un chiffre inférieur à 0 pour permettre au système de décider automatiquement du nombre d’opérations simultanées à exécuter.
PopulateIndexMetrics Permet la collecte des mesures d’index pour comprendre comment le moteur de requête a utilisé des index existants et comment il peut utiliser de nouveaux index potentiels. Cette option entraîne une surcharge. Elle ne doit être activée que lors du débogage de requêtes lentes.
ResponseContinuationTokenLimitInKb Vous pouvez limiter la taille maximale du jeton de continuation retourné par le serveur. Vous devrez peut-être la définir si votre hôte d’application a des limites sur la taille de l’en-tête de réponse, mais cela peut augmenter la durée globale et les unités de requête (RU) consommées pour la requête.

Par exemple, voici une requête sur un conteneur partitionné par /city à l’aide du Kit de développement logiciel (SDK) .NET :

QueryDefinition query = new QueryDefinition("SELECT * FROM c WHERE c.city = 'Seattle'");
QueryRequestOptions options = new QueryRequestOptions()
{
    MaxItemCount = -1,
    MaxBufferedItemCount = -1,
    MaxConcurrency = -1,
    PopulateIndexMetrics = true
};
FeedIterator<dynamic> feedIterator = container.GetItemQueryIterator<dynamic>(query);

FeedResponse<dynamic> feedResponse = await feedIterator.ReadNextAsync();

Chaque exécution de requête correspond à une API REST POST avec des en-têtes définis pour les options de demande de requête et la requête SQL dans le corps. Pour plus d’informations sur les options et les en-têtes de demande d’API REST, consultez Interrogation des ressources à l’aide de l’API REST.

Meilleures pratiques pour les performances de requêtes

Les facteurs suivants ont généralement l’effet le plus important sur les performances des requêtes Azure Cosmos DB. Nous approfondissons chacun de ces facteurs dans cet article.

Facteur Conseil
Débit approvisionné Mesurez la RU (unité de requête) par requête et assurez-vous d’avoir le débit approvisionné nécessaire pour vos requêtes.
Partitionnement et clés de partition Préférez les requêtes avec la valeur de clé de partition dans la clause de filtre pour une latence faible.
Options de requête et de kit SDK Suivez les meilleures pratiques du kit SDK, comme la connectivité directe, et paramétrez les options d’exécution de requêtes du côté client.
Latence du réseau Exécutez votre application dans la même région que celle de votre compte Azure Cosmos DB quand vous le pouvez pour réduire la latence.
Stratégie d’indexation Assurez-vous que vous disposez de la stratégie ou des chemins d’indexation nécessaires pour la requête.
Mesures d’exécution des requêtes Analysez les mesures de l’exécution des requêtes pour identifier les réécritures potentielles de formes de données et de requêtes.

Débit approvisionné

Azure Cosmos DB vous permet de créer des conteneurs de données, chacun avec un débit réservé qui est exprimé en RU (unité de requête) par seconde. La lecture d’un document de 1 Ko correspond à une unité de requête, et chaque opération (y compris les requêtes) est normalisée en un nombre fixe d’unités de requête en fonction de sa complexité. Par exemple, si vous avez 1000 RU/s configurées pour votre conteneur, et que vous avez une requête, telle que SELECT * FROM c WHERE c.city = 'Seattle' qui consomme 5 RU, vous pouvez alors effectuer (1000 RU/s) / (5 RU/requête) = 200 de ces requêtes par seconde.

Si vous envoyez plus de 200 requêtes/s (ou d’autres opérations qui saturent toutes les unités de requête configurées), le service démarre la limitation du débit des demandes entrantes. Les kits SDK gèrent automatiquement la limitation du débit en effectuant une interruption ou une nouvelle tentative, par conséquent vous pouvez remarquer une latence plus élevée pour ces requêtes. L’augmentation du débit approvisionné jusqu’à la valeur nécessaire améliore le débit et la latence des requêtes.

Pour en savoir plus sur les unités de requête, consultez Unités de requête.

Partitionnement et clés de partition

Avec Azure Cosmos DB, les scénarios suivants pour la lecture des données sont généralement classés du plus rapide/plus efficace au plus lent/moins efficace.

  • GET sur une clé de partition unique et un ID d’élément, également appelé lecture de point
  • Requête avec une clause de filtre sur une clé de partition unique
  • Requête avec une clause de filtre d’égalité ou de plage sur une propriété
  • Requête sans filtre

Les requêtes qui doivent être exécutées sur toutes les partitions ont une latence plus élevée et peuvent consommer des unités de requête plus élevées. Étant donné que chaque partition dispose de l’indexation automatique sur toutes les propriétés, la requête peut être servie efficacement à partir de l’index dans ce cas. Vous pouvez effectuer des requêtes qui englobent les partitions plus rapidement en utilisant les options de parallélisme.

Pour en savoir plus sur le partitionnement et les clés de partition, consultez Partitionnement dans Azure Cosmos DB.

Options de requête et de kit SDK

Consultez conseils sur les performances de requête et tests de performances pour savoir comment obtenir les meilleures performances côté client à partir d’Azure Cosmos DB à l’aide de nos kits SDK.

Latence du réseau

Consultez Distribution mondiale d’Azure Cosmos DB pour savoir comment configurer la distribution mondiale et vous connecter à la région la plus proche. La latence du réseau a une incidence significative sur les performances des requêtes lorsque vous devez effectuer plusieurs allers-retours ou récupérer un jeu de résultats volumineux de la requête.

Vous pouvez utiliser les mesures d’exécution de requête pour récupérer le délai d’exécution du serveur des requêtes, ce qui vous permet de différencier le temps passé dans l’exécution des requêtes du temps passé dans le trafic réseau.

Stratégie d’indexation

Consultez configuration de la stratégie d’indexation pour l’indexation de chemins d’accès, de types et de modes, ainsi que leur impact sur l’exécution des requêtes. Par défaut, Azure Cosmos DB applique l’indexation automatique à toutes les données et utilise des index de plage pour les chaînes et les nombres, ce qui est efficace pour les requêtes d’égalité. Pour bénéficier de scénarios d’insertion hautes performances, envisagez d’exclure les chemins d’accès, pour réduire le coût des unités de requête pour chaque opération d’insertion.

Vous pouvez utiliser les mesures d’index pour identifier les index utilisés pour chaque requête et s’il existe des index manquants qui pourraient améliorer les performances des requêtes.

Mesures d’exécution des requêtes

Les mesures détaillées sont renvoyées pour chaque exécution de requête dans Diagnostics pour la requête. Ces mesures décrivent l’endroit où le temps est passé pendant l’exécution de la requête et permettent de résoudre les problèmes avancés.

En savoir plus sur l’obtention des mesures de requête.

Métrique Unité Description
TotalTime millisecondes Durée totale d’exécution de la requête
DocumentLoadTime millisecondes Temps de chargement des documents
DocumentWriteTime millisecondes Temps passé à écrire et sérialiser les documents générés
IndexLookupTime millisecondes Temps passé dans la couche de l’index physique
QueryPreparationTime millisecondes Temps passé dans la préparation de la requête
RuntimeExecutionTime millisecondes Durée totale d’exécution du runtime de la requête
VMExecutionTime millisecondes Temps passé dans le runtime de requête exécutant la requête
OutputDocumentCount count Nombre de documents générés dans le jeu de résultats
OutputDocumentSize count Taille totale des documents générés, en octets
RetrievedDocumentCount count Nombre total de documents récupérés
RetrievedDocumentSize octets Taille totale des documents récupérés, en octets
IndexHitRatio ratio [0,1] Ratio du nombre de documents mis en correspondance avec le filtre par rapport au nombre de documents chargés

Les kits SDK client peuvent effectuer plusieurs demandes de requête en interne pour servir la requête dans chaque partition. Le client effectue plusieurs appels par partition si l’ensemble des résultats dépassent l’option de demande de nombre maximal d’éléments, si la requête dépasse le débit configuré pour la partition, si la charge utile de la requête atteint la taille maximale par page, ou si la requête atteint la limite de délai d’attente allouée du système. Chaque exécution de requête partielle renvoie des mesures de requête pour cette page.

Voici quelques exemples de requête, et la façon d’interpréter certains mesures retournées à partir de l’exécution des requêtes :

Requête Exemples de mesures Description
SELECT TOP 100 * FROM c "RetrievedDocumentCount": 101 Le nombre de documents récupérés est 100 + 1 pour correspondre à la clause TOP. Le temps de la requête est essentiellement consacré à WriteOutputTime et DocumentLoadTime puisqu’il s’agit d’une analyse.
SELECT TOP 500 * FROM c "RetrievedDocumentCount": 501 RetrievedDocumentCount est désormais plus élevé (500 + 1 pour correspondre à la clause TOP).
SELECT * FROM c WHERE c.N = 55 "IndexLookupTime": "00:00:00.0009500" Environ 0,9 ms est passé dans IndexLookupTime pour une recherche de clé, car c’est une recherche d’index sur /N/?.
SELECT * FROM c WHERE c.N > 55 "IndexLookupTime": "00:00:00.0017700" Légèrement plus de temps (1,7 ms) est passé dans IndexLookupTime sur une analyse de plage, car c’est une recherche d’index sur /N/?.
SELECT TOP 500 c.N FROM c "IndexLookupTime": "00:00:00.0017700" Même temps passé sur DocumentLoadTime que sur les requêtes précédentes, mais DocumentWriteTime est inférieur, car nous ne projetons qu’une seule propriété.
SELECT TOP 500 udf.toPercent(c.N) FROM c "RuntimeExecutionTime": "00:00:00.2136500" Environ un temps de 213 ms est consacré à RuntimeExecutionTime exécutant l’UDF sur chaque valeur de c.N.
SELECT TOP 500 c.Name FROM c WHERE STARTSWITH(c.Name, 'Den') "IndexLookupTime": "00:00:00.0006400", "RuntimeExecutionTime": "00:00:00.0074100" Environ 0,6 ms est consacré à IndexLookupTime sur /Name/?. La majeure partie de la durée d’exécution de la requête (~ 7 ms) dans RuntimeExecutionTime.
SELECT TOP 500 c.Name FROM c WHERE STARTSWITH(LOWER(c.Name), 'den') "IndexLookupTime": "00:00:00", "RetrievedDocumentCount": 2491, "OutputDocumentCount": 500 La requête est exécutée en tant qu’analyse, car elle utilise LOWER, et 500 documents sur les 2491 récupérés sont retournés.

Étapes suivantes

  • Pour en savoir plus sur les opérateurs et les mots clés de requête SQL pris en charge, consultez Requête SQL.
  • Pour obtenir plus d’informations sur les unités de requête, consultez Unités de requête.
  • Pour en savoir plus sur la stratégie d’indexation, consultez Stratégie d’indexation