Conseils sur les performances pour Azure Cosmos DB et .NET

S’APPLIQUE À : NoSQL

Azure Cosmos DB est une base de données distribuée rapide et flexible, qui peut être mise à l’échelle en toute transparence avec des niveaux de latence et de débit garantis. Vous n’avez pas à apporter de modifications d’architecture majeures ou écrire de code complexe pour mettre à l’échelle votre base de données avec Azure Cosmos DB. La réduction et l’augmentation de l’échelle est aussi simple que le passage d’un appel d’API. Pour en savoir plus, consultez Approvisionner le débit sur un conteneur ou Approvisionner le débit sur une base de données.

Comme Azure Cosmos DB est accessible via des appels réseau, vous pouvez apporter des optimisations côté client pour atteindre des performances de pointe quand vous utilisez le kit SDK SQL .NET.

Si vous essayez d’améliorer les performances de votre base de données, envisagez les options présentées dans les sections suivantes.

Recommandations relatives à l’hébergement

Activer le garbage collection (GC) côté serveur

Réduire la fréquence de garbage collection peut aider dans certains cas. Dans .NET, définissez gcServer sur true.

Effectuer une montée en charge de votre charge de travail cliente

Si vous effectuez des tests à des niveaux de débit élevés ou à des débits supérieurs à 50 000 unités de requête par seconde (RU/s), l’application cliente peut devenir un goulet d’étranglement pour la charge de travail. Cela est dû au fait que l’ordinateur peut atteindre un seuil d’utilisation du processeur ou du réseau. Si vous atteignez ce point, vous pouvez continuer à augmenter le compte Azure Cosmos DB en augmentant la taille des instances de vos applications clientes sur plusieurs serveurs.

Notes

Une utilisation élevée de l'UC peut entraîner une latence accrue et des exceptions en termes de délai d’expiration des requêtes.

Opérations sur les métadonnées

Ne pas vérifier qu’une base de données et/ou un conteneur existe en appelant Create...IfNotExistsAsync et/ou Read...Async dans le chemin chaud et/ou avant de faire une opération d’éléments. La validation ne doit être effectuée qu’au démarrage de l’application lorsque cela est nécessaire, si vous pensez qu’elle doit être supprimée (dans le cas contraire, elle n’est pas nécessaire). Ces opérations de métadonnées génèrent une latence de bout en bout supplémentaire, n’ont pas de SLA et ont leurs propres limitations distinctes qui ne se mettent pas à l’échelle comme les opérations de données.

Journalisation et suivi

Pour certains environnements .NET DefaultTraceListener est activé. DefaultTraceListener pose des problèmes de performance dans les environnements de production, provoquant des goulots d’étranglement importants au niveau du processeur et des E/S. Vérifiez que DefaultTraceListener est désactivé pour votre application en le supprimant des TraceListeners dans les environnements de production.

Les dernières versions du SDK (supérieures à 3.23.0) le suppriment automatiquement dès qu’elles le détectent. Avec les versions antérieures, vous pouvez le supprimer avec :

if (!Debugger.IsAttached)
{
    Type defaultTrace = Type.GetType("Microsoft.Azure.Cosmos.Core.Trace.DefaultTrace,Microsoft.Azure.Cosmos.Direct");
    TraceSource traceSource = (TraceSource)defaultTrace.GetProperty("TraceSource").GetValue(null);
    traceSource.Listeners.Remove("Default");
    // Add your own trace listeners
}

Mise en réseau

Stratégie de connexion : Utiliser le mode de connexion directe

Le mode de connexion par défaut du Kit de développement logiciel (SDK) .NET v3 est direct avec le protocole TCP. Vous configurez le mode de connexion lorsque vous créez l’instance CosmosClient dans CosmosClientOptions. Pour en savoir plus sur les différentes options de connectivité, consultez l’article sur les modes de connectivité.

string connectionString = "<your-account-connection-string>";
CosmosClient client = new CosmosClient(connectionString,
new CosmosClientOptions
{
    ConnectionMode = ConnectionMode.Gateway // ConnectionMode.Direct is the default
});

Épuisement des ports éphémères

Si vous êtes confronté à un volume de connexion élevé ou à une utilisation élevée des ports sur vos instances, vérifiez d’abord que vos instances clientes sont des singletons. En d’autres termes, les instances clientes doivent être uniques pour la durée de vie de l’application.

Lorsqu’il s’exécute sur le protocole TCP, le client optimise la latence en utilisant les connexions à long terme. Cela diffère du protocole HTTPS, qui met fin aux connexions après deux minutes d’inactivité.

Dans les scénarios où vous disposez de peu d’accès, si vous remarquez un nombre de connexions supérieur par rapport au mode passerelle, vous pouvez :

  • Configurer la propriété CosmosClientOptions.PortReuseMode sur PrivatePortPool (en vigueur avec la version 4.6.1 ou ultérieure du framework et .NET Core version 2.0 ou ultérieure). Cette propriété permet au kit SDK d’utiliser un petit pool de ports éphémères pour différents points de terminaison de destination Azure Cosmos DB.
  • Configurer la propriété CosmosClientOptions.IdleTcpConnectionTimeout afin qu’elle soit supérieure ou égale à 10 minutes. Les valeurs recommandées sont comprises entre 20 minutes et 24 heures.

Pour des performances optimales, colocalisez les clients dans la même région Azure

Dans la mesure du possible, placez toutes les applications qui appellent Azure Cosmos DB dans la même région que la base de données Azure Cosmos DB. Voici une comparaison approximative : des appels à Azure Cosmos DB dans une même région s’effectuent en 1 à 2 millisecondes (ms), mais la latence entre la côte ouest et la côte est des États-Unis est supérieure à 50 ms. Cette latence peut varier d’une requête à l’autre, en fonction de l’itinéraire utilisé par la requête lorsqu’elle passe du client à la limite du centre de données Azure.

Vous pouvez obtenir la latence la plus faible possible en veillant à ce que l’application appelante soit située dans la même région Azure que le point de terminaison Azure Cosmos DB configuré. Pour obtenir la liste des régions disponibles, voir Régions Azure.

Colocalisez les clients dans la même région Azure

Augmenter le nombre de threads/tâches

Étant donné que les appels à Azure Cosmos DB sont effectués sur le réseau, vous devrez peut-être modifier le degré de concurrence de vos requêtes, afin que l’application cliente attende un temps minimal entre les requêtes. Par exemple, si vous utilisez la bibliothèque parallèle de tâches .NET, créez des centaines de tâches de lecture ou d’écriture dans Azure Cosmos DB.

Activer les performances réseau accélérées pour réduire la latence et la gigue du processeur

Nous vous recommandons de suivre les instructions relatives à l’activation des Performances réseau accélérées dans votre machine virtuelle Azure Windows (instructions ici) ou Linux (instructions ici) pour optimiser les performances.

Sans accélération réseau, les E/S qui transitent entre votre machine virtuelle Azure et d’autres ressources Azure peuvent être inutilement routées par le biais d’un hôte et d’un commutateur virtuel situés entre la machine virtuelle et sa carte réseau. Le fait d’avoir l’hôte et le commutateur virtuel inline dans le chemin de données entraîne non seulement une augmentation de la latence et de l’instabilité dans le canal de communication, mais aussi le vol des cycles processeur de la machine virtuelle. L’accélération réseau offre plusieurs avantages : la machine virtuelle interagit directement avec la carte réseau sans intermédiaires, les détails de la stratégie réseau précédemment gérés par l’hôte et le commutateur virtuel sont désormais gérés dans le matériel au niveau de la carte réseau, et l’hôte et le commutateur virtuel sont contournés. L’activation de l’accélération réseau se traduit généralement par une latence plus faible et plus cohérente, un débit plus élevé et une utilisation réduite du processeur.

Limitations : l’accélération réseau doit être prise en charge sur le système d’exploitation de la machine virtuelle et ne peut être activée que si la machine virtuelle est arrêtée et libérée. La machine virtuelle ne peut pas être déployée avec Azure Resource Manager. App Service n’a pas de réseau accéléré activé.

Pour plus d’informations, consultez les instructions propres à Windows et à Linux.

Utilisation du Kit de développement logiciel (SDK)

Installation du kit de développement logiciel (SDK) le plus récent

Les SDK Azure Cosmos DB sont constamment améliorés pour fournir des performances optimales. Pour déterminer le kit SDK le plus récent et passer en revue les améliorations, consultez SDK Azure Cosmos DB.

Utiliser les API de flux

Le Kit de développement logiciel (SDK) .NET V3 contient des API de flux qui peuvent recevoir et retourner des données sans sérialisation.

Les applications intermédiaires qui ne consomment pas directement les réponses du Kit de développement logiciel (SDK), mais qui les relayent vers d’autres couches Application, peuvent tirer parti des API de flux. Pour obtenir des exemples sur la gestion des flux, consultez les exemples de gestion d’élément.

Utiliser un client Azure Cosmos DB singleton pour la durée de vie de votre application

Chaque instance de CosmosClient est thread-safe et effectue une gestion des connexions efficace et une mise en cache d’adresses efficace en mode direct. Pour permettre une gestion efficace des connexions et améliorer les performances du client SDK, nous vous recommandons d’utiliser une seule instance par AppDomain pour la durée de vie de l’application, pour chaque compte avec lequel l’application interagit.

Pour les applications multilocataires qui gèrent plusieurs comptes, consultez les meilleures pratiques associées.

Lorsque vous travaillez sur Azure Functions, les instances doivent également suivre les instructions existantes et gérer une seule instance.

Éviter les appels bloquants

Le kit SDK Azure Cosmos DB doit être conçu pour traiter plusieurs requêtes simultanément. Les API asynchrones permettent à un petit pool de threads de gérer des milliers de requêtes simultanées sans attendre lors d’appels bloquants. Au lieu d’attendre la fin d’une tâche synchrone de longue durée, le thread peut travailler sur une autre requête.

Un problème de performances courant dans les applications utilisant le kit SDK Azure Cosmos DB bloque les appels qui pourraient être asynchrones. De nombreux appels bloquants synchrones conduisent à une défaillance du pool de threads et à des temps de réponse qui se dégradent.

À ne pas faire :

  • Bloquer l’exécution asynchrone en appelant Task.Wait ou Task.Result.
  • Utiliser Task.Run pour rendre une API synchrone asynchrone.
  • Acquérir des verrous dans les chemins de code courants. Le kit SDK .NET Azure Cosmos DB est le plus performant lorsqu’il est conçu pour exécuter du code en parallèle.
  • Appeler Task.Run et l’attendre tout de suite. ASP.NET Core exécute déjà le code d’application sur des threads de pool de threads normaux, donc l’appel de Task.Run entraîne uniquement une planification de pool de threads superflue. Même si le code planifié bloquait un thread, Task.Run ne l’empêcherait pas.
  • Utiliser ToList() sur Container.GetItemLinqQueryable<T>(), lequel utilise des appels bloquants pour vider la requête de façon synchrone. Utilisez ToFeedIterator() pour vider la requête de façon asynchrone.

À faire :

  • Appeler les API .NET Azure Cosmos DB de façon asynchrone.
  • L’ensemble de la pile des appels est asynchrone afin de tirer parti des modèles async/await.

Un profileur, tel que PerfView, peut être utilisé pour rechercher les threads ajoutés fréquemment au pool de threads. L’événement Microsoft-Windows-DotNETRuntime/ThreadPoolWorkerThread/Start indique qu’un thread a été ajouté au pool de threads.

Désactiver la réponse de contenu sur les opérations d’écriture

Pour les charges de travail qui ont des charges utiles de création intensives, attribuez à l’option de requête EnableContentResponseOnWrite la valeur false. Le service ne renvoie plus la ressource créée ou mise à jour au kit de développement logiciel (SDK). Normalement, comme l’application détient l’objet en cours de création, le service n’a pas besoin de le retourner. Les valeurs d’en-tête sont toujours accessibles, comme des frais de requête. La désactivation de la réponse de contenu peut améliorer les performances, car le kit SDK n’a plus besoin d’allouer de la mémoire ni de sérialiser le corps de la réponse. Cela réduit également l’utilisation de la bande passante réseau pour améliorer encore les performances.

ItemRequestOptions requestOptions = new ItemRequestOptions() { EnableContentResponseOnWrite = false };
ItemResponse<Book> itemResponse = await this.container.CreateItemAsync<Book>(book, new PartitionKey(book.pk), requestOptions);
// Resource will be null
itemResponse.Resource

Activer l’exécution en bloc pour optimiser le débit plutôt que la latence

Activez l’exécution en bloc pour les scénarios où la charge de travail requiert un débit très élevé et où la latence n’est pas aussi importante. Pour plus d’informations sur l’activation de l’exécution en bloc et sur les scénarios où elle devrait être utilisée, consultez Introduction à la prise en charge de l’exécution en bloc.

Augmenter System.Net MaxConnections par hôte lors de l’utilisation du mode passerelle

Les requêtes d’Azure Cosmos DB sont effectuées via le protocole HTTPS/REST lorsque vous utilisez le mode passerelle. Elles sont soumises à la limite de connexion par défaut par nom d’hôte ou adresse IP. Vous devrez peut-être définir MaxConnections sur une valeur plus élevée (de 100 à 1 000) afin que la bibliothèque cliente puisse utiliser plusieurs connexions simultanées à Azure Cosmos DB. Dans le Kit de développement logiciel (SDK) .NET 1.8.0 et versions ultérieures, la valeur par défaut de ServicePointManager. DefaultConnectionLimit est 50. Pour modifier cette valeur, vous pouvez définir Documents.Client.ConnectionPolicy.MaxConnectionLimit sur une valeur supérieure.

Augmenter le nombre de threads/tâches

Consultez Augmenter le nombre de threads/tâches dans la section Mise en réseau de cet article.

Opérations de requête

Pour les opérations de requête, consultez les conseils en matière de performances pour les requêtes.

Stratégie d’indexation

Exclusion des chemins d’accès inutilisés de l’indexation pour des écritures plus rapides

La stratégie d’indexation d’Azure Cosmos DB vous permet également de spécifier les chemins de document à inclure ou exclure lors de l’indexation en utilisant les chemins d’accès d’indexation (IndexingPolicy.IncludedPaths et IndexingPolicy.ExcludedPaths).

Indexer uniquement les chemins dont vous avez besoin vous permet d’améliorer les performances d’écriture, de réduire les frais liés aux unités de requête dans le cadre des opérations d’écriture et de réduire le stockage des index pour les scénarios dans lesquels les modèles de requête sont connus au préalable. Cela est dû au fait que les coûts d’indexation correspondent directement au nombre de chemins d’accès uniques indexés. Par exemple, le code suivant montre comment exclure une section entière des documents (appelée sous-arborescence) de l’indexation à l’aide du caractère générique « * » :

var containerProperties = new ContainerProperties(id: "excludedPathCollection", partitionKeyPath: "/pk" );
containerProperties.IndexingPolicy.IncludedPaths.Add(new IncludedPath { Path = "/*" });
containerProperties.IndexingPolicy.ExcludedPaths.Add(new ExcludedPath { Path = "/nonIndexedContent/*");
Container container = await this.cosmosDatabase.CreateContainerAsync(containerProperties);

Pour plus d’informations, consultez Stratégies d’indexation d’Azure Cosmos DB.

Débit

RU/s

Azure Cosmos DB offre un ensemble complet d’opérations de base de données. Ces opérations incluent les requêtes hiérarchiques et relationnelles avec les fichiers UDF (Universal Disk Format), les procédures stockées et les déclencheurs, qui fonctionnent tous au niveau des documents d’une collection de bases de données.

Les coûts associés à chacune de ces opérations varient en fonction du processeur, des E/S et de la mémoire nécessaires à l’exécution de l’opération. Plutôt que de vous soucier de la gestion des ressources matérielles, vous pouvez considérer une unité de requête (RU) comme une mesure unique des ressources nécessaires à l’exécution des diverses opérations de base de données et au traitement d’une requête d’application.

Le débit est approvisionné en fonction du nombre d’unités de requête défini pour chaque conteneur. La consommation d’unités de requête est évaluée sous la forme d’un débit d’unités par seconde. Les applications qui dépassent le taux d’unités de requête configuré pour le conteneur associé sont limitées jusqu’à ce que le taux soit inférieur au niveau configuré pour le conteneur. Si votre application requiert un niveau de débit plus élevé, vous pouvez augmenter le débit en approvisionnant des unités de requête supplémentaires.

La complexité d’une requête a une incidence sur le nombre d’unités de requête consommées pour une opération. Le nombre de prédicats, la nature des prédicats, le nombre de fichiers UDF et la taille du jeu de données source ont tous une influence sur le coût des opérations de requête.

Pour mesurer les frais de l’opération (création, mise à jour ou suppression), inspectez l’en-tête x-ms-request-charge (ou la propriété RequestCharge équivalente dans ResourceResponse<T> ou FeedResponse<T> dans le SDK .NET) afin de déterminer le nombre d’unités de requête consommées par les opérations :

// Measure the performance (Request Units) of writes
ItemResponse<Book> response = await container.CreateItemAsync<Book>(myBook, new PartitionKey(myBook.PkValue));
Console.WriteLine("Insert of item consumed {0} request units", response.RequestCharge);
// Measure the performance (Request Units) of queries
FeedIterator<Book> queryable = container.GetItemQueryIterator<ToDoActivity>(queryString);
while (queryable.HasMoreResults)
    {
        FeedResponse<Book> queryResponse = await queryable.ExecuteNextAsync<Book>();
        Console.WriteLine("Query batch consumed {0} request units", queryResponse.RequestCharge);
    }

Les frais de requête retournés dans cet en-tête correspondent à une fraction de votre débit provisionné (à savoir 2 000 RU/s). Par exemple, si la requête ci-dessus renvoie 1 000 documents de 1 Ko, le coût de l’opération est de 1 000. Ainsi, en une seconde, le serveur honore uniquement deux requêtes de ce type avant de limiter le taux des requêtes ultérieures. Pour plus d’informations, consultez Unités de requête et la calculatrice d’unités de requête.

Gestion de la limite de taux/du taux de requête trop importants

Lorsqu’un client tente de dépasser le débit réservé pour un compte, les performances au niveau du serveur ne sont pas affectées et la capacité de débit n’est pas utilisée au-delà du niveau réservé. Le serveur met fin à la requête de manière préventive avec RequestRateTooLarge (code d’état HTTP 429). Il renvoie un en-tête x-ms-retry-after-ms qui indique la durée, en millisecondes, pendant laquelle l’utilisateur doit attendre avant de retenter la requête.

    HTTP Status 429,
    Status Line: RequestRateTooLarge
    x-ms-retry-after-ms :100

Les kits de développement logiciel (SDK) interceptent tous implicitement cette réponse, respectent l’en-tête retry-after spécifiée par le serveur, puis relancent la requête. La tentative suivante réussira toujours, sauf si plusieurs clients accèdent simultanément à votre compte.

Si plusieurs de vos clients opèrent simultanément et systématiquement au-delà du taux de requête, le nombre de nouvelles tentatives par défaut actuellement défini sur 9 en interne par le client ne suffira peut-être pas. Dans ce cas, le client envoie à l’application une exception CosmosException avec le code d’état 429.

Vous pouvez modifier le nombre de nouvelles tentatives par défaut en définissant les RetryOptions sur l’instance CosmosClientOptions. Par défaut, l’exception CosmosException avec le code d’état 429 est retournée après un temps d’attente cumulé de 30 secondes si la requête continue à fonctionner au-dessus du taux de requête. Cette erreur est renvoyée même lorsque le nombre actuel de nouvelles tentatives est inférieur au nombre maximal de nouvelles tentatives, que la valeur actuelle soit la valeur par défaut 9 ou une valeur définie par l’utilisateur.

Le comportement de nouvelle tentative automatisée contribue à améliorer la résilience et la convivialité pour la plupart des applications. Mais cela peut ne pas être le meilleur comportement lorsque vous effectuez des benchmarks des performances, en particulier lorsque vous mesurez la latence. La latence client observée atteindra un pic si l’expérience atteint la limite de serveur et oblige le kit de développement logiciel (SDK) client à effectuer une nouvelle tentative en silence. Pour éviter des pics de latence lors d’expériences de performances, mesurez les frais retournés par chaque opération et assurez-vous que les requêtes restent sous le taux de requêtes réservé.

Pour plus d’informations, consultez Unités de requête.

Pour un débit plus élevé, concevez en prévision des documents plus petits

Les frais de requête (à savoir, le coût de traitement des requêtes) d’une opération donnée sont directement liés à la taille du document. Les opérations sur les documents volumineux coûtent plus cher que les opérations sur les petits documents.

Étapes suivantes

Pour obtenir un exemple d’application permettant d’évaluer Azure Cosmos DB lors de scénarios hautes performances sur quelques ordinateurs clients, consultez Test des performances et de la mise à l’échelle avec Azure Cosmos DB.

Pour en savoir plus sur la conception de votre application pour une mise à l’échelle et de hautes performances, consultez Partitionnement, clés de partition et mise à l’échelle dans Cosmos DB.