Modifier

Optimisation des performances - Services back-end multiples

Azure Kubernetes Service (AKS)
Azure Cosmos DB

Cet article décrit comment une équipe de développement a utilisé des métriques pour détecter des goulets d’étranglement et améliorer les performances d’un système distribué. L'article est basé sur des tests de charge réels que nous avons effectués pour un exemple d'application. L'application provient de la Base de référence Azure Kubernetes Service (AKS) pour les microservices, ainsi que d'un projet de test de charge Visual Studio utilisé pour générer les résultats.

Cet article fait partie d’une série. Lisez la première partie ici.

Scénario : Appeler plusieurs services back-end pour récupérer des informations, puis agréger les résultats

Ce scénario implique une application de livraison par drone. Les clients peuvent interroger une API REST pour obtenir leurs dernières informations de facturation. La facture comprend un récapitulatif des livraisons, des colis et de l'utilisation totale des drones par le client. Cette application utilise une architecture de microservices exécutée sur AKS, et les informations nécessaires à la facturation sont réparties sur plusieurs microservices.

Plutôt que de laisser le client appeler directement chaque service, l'application implémente le modèle Agrégation de passerelle. Grâce à ce modèle, le client n'adresse qu'une seule requête à un service de passerelle. En parallèle, la passerelle appelle les services back-end, puis agrège les résultats en une seule charge utile de réponse.

Diagramme illustrant le modèle Agrégation de passerelle

Test 1 : Performances de base

Pour établir une base de référence, l'équipe de développement a d'abord effectué un test de charge par étapes, passant d'un utilisateur simulé à 40 utilisateurs, sur une durée totale de 8 minutes. Le graphique suivant, extrait de Visual Studio, présente les résultats. La ligne violette indique la charge utilisateur et la ligne orange le débit (nombre moyen de requêtes par seconde).

Graphique des résultats des tests de charge de Visual Studio

La ligne rouge du bas du graphique montre qu'aucune erreur n'a été renvoyée au client, ce qui est encourageant. Toutefois, le débit moyen atteint un pic à la moitié du test, puis diminue, même si la charge continue d'augmenter. Cela indique que le back-end n'est pas en mesure de suivre. Le modèle présenté ici est courant lorsqu’un système commence à atteindre les limites en termes de ressources ; après avoir atteint un maximum, le débit diminue de manière significative. Les conflits de ressources, les erreurs temporaires ou l'augmentation de la fréquence des exceptions peuvent tous contribuer à ce modèle.

Examinons les données de surveillance pour savoir ce qui se passe à l'intérieur du système. Le graphique suivant est extrait d'Application Insights. Il indique la durée moyenne des appels HTTP entre la passerelle et les services back-end.

Graphique présentant la durée des appels HTTP

Ce graphique montre qu’une opération en particulier, GetDroneUtilization, prend en moyenne beaucoup plus de temps que les autres, par ordre de grandeur. La passerelle passe ces appels en parallèle, de sorte que l'opération la plus lente détermine le temps nécessaire à l'ensemble de la requête.

L'étape suivante consiste à examiner l'opération GetDroneUtilization et à rechercher les éventuels goulots d'étranglement. L'insuffisance de ressources est une possibilité. Ce service back-end particulier est peut-être à court d'UC ou de mémoire. Pour un cluster AKS, ces informations sont disponibles sur le portail Azure via la fonctionnalité Azure Monitor Container Insights. Les graphiques suivants présentent l'utilisation des ressources au niveau du cluster :

Graphique présentant l'utilisation des nœuds AKS

Dans cette capture d'écran, les valeurs moyennes et maximum sont affichées. Il est important de ne pas se limiter à la moyenne, car celle-ci peut masquer des pics de données. Ici, l'utilisation moyenne de l'UC reste inférieure à 50 %, mais il y a quelques pics à 80 %. Nous sommes proches de la capacité maximum, mais toujours dans les limites de tolérance. Un autre problème est à l'origine du goulot d'étranglement.

Le graphique suivant révèle le véritable coupable. Ce graphique présente les codes de réponse HTTP de la base de données back-end du service de livraison, qui dans ce cas est Azure Cosmos DB. La ligne bleue représente les codes de réussite (HTTP 2xx) et la ligne verte les erreurs HTTP 429. Un code de retour HTTP 429 signifie qu’Azure Cosmos DB limite temporairement les requêtes, car l'appelant consomme plus d'unités de ressources (RU) que prévu.

Graphique présentant les requêtes limitées

Afin d'obtenir des insights supplémentaires, l'équipe de développement a utilisé Application Insights pour afficher la télémétrie de bout en bout sur un échantillon représentatif de requêtes. En voici un exemple :

Capture d'écran de la vue des transactions de bout en bout

Cette vue présente les appels liés à une seule requête client, ainsi que des informations sur la durée et les codes de réponse. Les appels de niveau supérieur se font de la passerelle vers les services back-end. L'appel à GetDroneUtilization est développé pour présenter les appels aux dépendances externes - dans ce cas, à Azure Cosmos DB. L'appel en rouge a renvoyé une erreur HTTP 429.

Notez l'écart important entre l'erreur HTTP 429 et l'appel suivant. Lorsque la bibliothèque cliente Azure Cosmos DB reçoit une erreur HTTP 429, elle est automatiquement désactivée et attend avant de retenter l'opération. Cette vue montre que la majeure partie des 672 ms qu'a duré l'opération a été consacrée à l'attente d'une nouvelle tentative d'Azure Cosmos DB.

Voici un autre graphique intéressant pour cette analyse. Il présente la consommation de RU par partition physique et les RU approvisionnées par partition physique :

Graphique présentant la consommation de RU par partition

Pour comprendre ce graphique, vous devez savoir comment Azure Cosmos DB gère les partitions. Dans Azure Cosmos DB, les collections peuvent disposer d'une clé de partition. Chaque valeur de clé possible définit une partition logique des données au sein de la collection. Azure Cosmos DB distribue ces partitions logiques sur une ou plusieurs partitions physiques. La gestion des partitions physiques est automatiquement assurée par Azure Cosmos DB. Au fur et à mesure que vous stockez des données, Azure Cosmos DB peut déplacer les partitions logiques vers de nouvelles partitions physiques, afin de répartir la charge sur celles-ci.

Pour ce test de charge, la collection Azure Cosmos DB a été approvisionnée avec 900 RU. Le graphique montre 100 RU par partition physique, ce qui implique un total de neuf partitions physiques. Bien qu’Azure Cosmos DB gère automatiquement le partitionnement, le fait de connaître le nombre de partitions physiques peut donner un aperçu des performances. L'équipe de développement utilisera ces informations ultérieurement, au fil de leur optimisation. Lorsque la ligne bleue coupe la ligne horizontale violette, cela signifie que la consommation de RU a dépassé les RU approvisionnées. C'est à ce moment qu’Azure Cosmos DB commence à limiter les appels.

Test 2 : Augmenter les unités de ressource

Pour le deuxième test de charge, l'équipe a fait passer la collection Azure Cosmos DB de 900 à 2 500 RU. Le débit est passé de 19 à 23 requêtes/seconde, et la latence moyenne est passée de 669 à 569 ms.

Métrique Test 1 Test 2
Débit (requêtes/s) 19 23
Latence moyenne (ms) 669 569
Demandes ayant réussi 9,8 K 11 K

Ce ne sont pas des gains énormes, mais en observant l'évolution du graphique dans le temps, on obtient une image plus complète :

Graphique des résultats des tests de charge de Visual Studio montrant un débit plus cohérent.

Alors que le test précédent montrait un pic initial suivi d'une forte baisse, ce test montre un débit plus régulier. Cela dit, le débit maximal n'est pas significativement plus élevé.

Toutes les requêtes adressées à Azure Cosmos DB ont renvoyé un état 2xx et les erreurs HTTP 429 ont disparu :

Graphique des appels Azure Cosmos DB

Le graphique présentant la consommation de RU et les RU approvisionnées montre qu'il existe une marge importante. Il y a environ 275 RU par partition physique, et le test de charge a culminé à environ 100 RU consommées par seconde.

Graphique présentant la consommation de RU et les RU approvisionnées et montrant qu'il existe une marge importante.

Le nombre d'appels à Azure Cosmos DB par opération réussie est également une métrique intéressante :

Métrique Test 1 Test 2
Appels par opération 11 9

En supposant qu'il n'y ait pas d'erreurs, le nombre d'appels doit correspondre au plan de requête réel. Dans ce cas, l'opération implique une requête qui soit adressée aux neuf partitions physiques. La valeur plus élevée du premier test de charge reflète le nombre d'appels qui ont renvoyé une erreur 429.

Cette métrique a été calculée en exécutant une requête Log Analytics personnalisée :

let start=datetime("2020-06-18T20:59:00.000Z");
let end=datetime("2020-07-24T21:10:00.000Z");
let operationNameToEval="GET DroneDeliveries/GetDroneUtilization";
let dependencyType="Azure DocumentDB";
let dataset=requests
| where timestamp > start and timestamp < end
| where success == true
| where name == operationNameToEval;
dataset
| project reqOk=itemCount
| summarize
    SuccessRequests=sum(reqOk),
    TotalNumberOfDepCalls=(toscalar(dependencies
    | where timestamp > start and timestamp < end
    | where type == dependencyType
    | summarize sum(itemCount)))
| project
    OperationName=operationNameToEval,
    DependencyName=dependencyType,
    SuccessRequests,
    AverageNumberOfDepCallsPerOperation=(TotalNumberOfDepCalls/SuccessRequests)

Pour résumer, le deuxième test de charge montre une amélioration. Mais l'opération GetDroneUtilization se situe toujours dans un ordre de grandeur supérieur à celui de l'opération la plus lente suivante. L'examen des transactions de bout en bout permet d'obtenir une explication :

Capture d’écran du deuxième test de charge montrant l’amélioration.

Comme mentionné précédemment, l'opération GetDroneUtilization implique une requête Azure Cosmos DB sur plusieurs partitions. Cela signifie que le client Azure Cosmos DB doit répartir la requête sur les différentes partitions physiques et collecter les résultats. Comme le montre la vue des transactions de bout en bout, ces requêtes sont effectuées en série. L’opération dure aussi longtemps que la somme de toutes les requêtes, et ce problème ne fait qu’empirer à mesure que la taille des données augmente et que de nouvelles partitions physiques sont ajoutées.

Test 3 : Requêtes parallèles

En fonction des résultats précédents, un moyen évident de réduire la latence consiste à émettre les requêtes en parallèle. Le kit de développement logiciel (SDK) du client Azure Cosmos DB dispose d'un paramètre qui contrôle le degré maximum de parallélisme.

Valeur Description
0 Pas de parallélisme (par défaut)
> 0 Nombre maximum d'appels parallèles
-1 Le kit de développement logiciel (SDK) client sélectionne un degré optimal de parallélisme

Pour le troisième test de charge, ce paramètre est passé de 0 à -1. Le tableau suivant récapitule les résultats :

Métrique Test 1 Test 2 Test 3
Débit (requêtes/s) 19 23 42
Latence moyenne (ms) 669 569 215
Demandes ayant réussi 9,8 K 11 K 20 K
Requêtes limitées 2,72 K 0 0

D'après le graphique du test de charge, non seulement le débit global est beaucoup plus élevé (ligne orange), mais le débit suit également le rythme de la charge (ligne violette).

Graphique des résultats des tests de charge de Visual Studio présentant un débit global plus élevé qui continue de s’adapter à la charge.

Nous pouvons vérifier que le client Azure Cosmos DB effectue des requêtes en parallèle en examinant la vue des transactions de bout en bout :

Capture d’écran de la vue des transactions de bout en bout indiquant que le client Azure Cosmos DB effectue des requêtes en parallèle.

Il est intéressant de noter que l'augmentation du débit a pour effet secondaire d'entraîner également une augmentation du nombre de RU consommées par seconde. Bien qu’Azure Cosmos DB n'ait limité aucune requête pendant ce test, la consommation était proche de la limite de RU approvisionnée :

Graphique de consommation de RU proche de la limite de RU approvisionnée.

Ce graphique pourrait être un signal pour poursuivre le scale-out de la base de données. Mais il s'avère que nous pouvons optimiser la requête.

Étape 4 : Optimiser la requête

Le test de charge précédent a montré de meilleures performances en termes de latence et de débit. La latence moyenne des requêtes a été réduite de 68 % et le débit a augmenté de 220 %. Toutefois, la requête sur plusieurs partitions pose problème.

Le problème des requêtes sur plusieurs partitions est que vous payez des RU sur chaque partition. Si la requête n’est exécutée qu’occasionnellement (par exemple, une fois par heure), cela n’a pas trop d’importance. Mais chaque fois que vous voyez une charge de travail nécessitant beaucoup de lectures et qui implique une requête sur plusieurs partitions, vous devez déterminer si la requête peut être optimisée en incluant une clé de partition. (Vous devrez peut-être redéfinir la collection pour utiliser une clé de partition différente.)

Voici la requête correspondant à ce scénario particulier :

SELECT * FROM c
WHERE c.ownerId = <ownerIdValue> and
      c.year = <yearValue> and
      c.month = <monthValue>

Cette requête sélectionne les enregistrements qui correspondent à un ID de propriétaire et à un mois/une année particuliers. Dans la conception d'origine, aucune de ces propriétés n'est la clé de partition. Cela oblige le client à répartir la requête sur les différentes partitions physiques et à collecter les résultats. Pour améliorer les performances des requêtes, l'équipe de développement a modifié la conception afin que l'ID du propriétaire soit la clé de partition de la collection. De cette façon, la requête peut cibler une partition physique spécifique. (Azure Cosmos DB gère cela automatiquement ; vous n'avez pas à gérer le mappage entre les valeurs de clé de partition et les partitions physiques).

Après le basculement de la collection vers la nouvelle clé de partition, la consommation de RU s'améliore considérablement, ce qui se traduit directement par une baisse des coûts.

Métrique Test 1 Test 2 Test 3 Test 4
RU par opération 29 29 29 3.4
Appels par opération 11 9 10 1

La vue des transactions de bout en bout montre que, comme prévu, la requête ne lit qu'une seule partition physique :

Capture d’écran de la vue des transactions de bout en bout montrant que la requête ne lit qu'une seule partition physique.

Le test de charge montre une amélioration du débit et de la latence :

Métrique Test 1 Test 2 Test 3 Test 4
Débit (requêtes/s) 19 23 42 59
Latence moyenne (ms) 669 569 215 176
Demandes ayant réussi 9,8 K 11 K 20 K 29 K
Requêtes limitées 2,72 K 0 0 0

L'amélioration des performances se traduit par une très forte utilisation de l'UC des nœuds :

Graphique présentant une utilisation intensive du processeur par le nœud.

Vers la fin du test de charge, l'UC moyenne a atteint environ 90 %, et l'UC maximum 100 %. Cette métrique indique que l'UC est le goulot d'étranglement suivant du système. Si un débit plus élevé est nécessaire, l'étape suivante pourrait consister à effectuer un scale-out du service de livraison pour intégrer davantage d'instances.

Résumé

Pour ce scénario, les goulets d’étranglement suivants ont été identifiés :

  • Requêtes de limitation d’Azure Cosmos DB en raison d'un approvisionnement insuffisant des RU.
  • Latence élevée causée par l'interrogation de plusieurs partitions de base de données en série
  • Requête sur plusieurs partitions inefficace, car celle-ci n'incluait pas la clé de partition

En outre, l'utilisation de l'UC a été identifiée comme un goulot d'étranglement potentiel à plus grande échelle. Pour diagnostiquer ces problèmes, l'équipe de développement a examiné ce qui suit :

  • Latence et débit du test de charge
  • Erreurs Azure Cosmos DB et consommation de RU.
  • Vue des transactions de bout en bout d'Application Insight
  • Utilisation de l’UC et de la mémoire dans Azure Monitor Container Insights.

Étapes suivantes

Consultez Anti-modèles de performance