Recommandations pour les services de backend de test de charge avec des équilibreurs de charge d'application

Lorsque vous intégrez un service de backend à l'équilibreur de charge d'application, il est important de mesurer les performances d'un service de backend seul, en l'absence d'un équilibreur de charge. Les tests de charge dans des conditions contrôlées vous aident à évaluer les compromis de planification de la capacité entre différentes dimensions de performances, telles que le débit et la latence. Étant donné qu'une planification minutieuse de la capacité peut toujours sous-estimer la demande réelle, nous vous recommandons d'utiliser des tests de charge pour déterminer de manière proactive la façon dont la disponibilité d'un service est affectée lorsque le système est surchargé.

Objectifs des tests de charge

Un test de charge type mesure le comportement externe du service de backend selon différentes dimensions de charge. Certaines des dimensions les plus pertinentes de ces tests sont les suivantes :

  • Débit de requêtes : nombre de requêtes diffusées par seconde
  • Simultanéité des requêtes : nombre de requêtes traitées simultanément
  • Débit de connexion : nombre de connexions initiées par les clients par seconde. La plupart des services qui utilisent TLS (Transport Layer Security) ont une surcharge de transport réseau et de négociation TLS associée à chaque connexion, indépendante du traitement des requêtes.
  • Simultanéité des connexions : nombre de connexions clientes traitées simultanément

  • Latence de la requête : temps total écoulé entre le début de la requête et la fin de la réponse.

  • Taux d'erreur : fréquence à laquelle les requêtes génèrent des erreurs, telles que les erreurs HTTP 5xx et les connexions fermées prématurément.

Pour évaluer l'état du serveur en charge, une procédure de test de charge peut également collecter les métriques de service internes suivantes :

  • Utilisation des ressources système : les ressources système, telles que le processeur, la mémoire RAM et les gestionnaires de fichiers (sockets), sont généralement exprimées en pourcentage.

    L'importance de ces métriques varie en fonction de la mise en œuvre du service. Les applications connaissent une baisse de performances, une faible charge ou un plantage lorsqu'elles épuisent leurs ressources. Par conséquent, il devient essentiel de déterminer la disponibilité des ressources lorsqu'un hôte est soumis à une charge importante.

  • Utilisation d'autres ressources limitées : ressources non système pouvant être épuisées sous charge, telles que la couche d'application.

    Voici quelques exemples de ressources de ce type :

    • Un pool limité de threads ou de processus de nœuds de calcul.
    • Pour un serveur d'applications qui utilise des threads, il est fréquent de limiter le nombre de threads de calcul fonctionnant simultanément. Les limites de taille des pools de threads sont utiles pour éviter l'épuisement de la mémoire et du processeur, mais les paramètres par défaut sont souvent très conservateurs. Des limites trop faibles peuvent empêcher l'utilisation adéquate des ressources système.
    • Certains serveurs utilisent des pools de processus au lieu de pools de threads. Par exemple, un serveur Apache configuré avec le modèle de multitraitement Prefork, attribue un processus à chaque connexion client. Ainsi, la taille maximale du pool détermine la limite supérieure de la simultanéité de connexion.
    • Un service déployé en tant qu'interface vers un autre service disposant d'un pool de connexions backend de taille limitée.

Planification des capacités et tests de surcharge

Les outils de tests de charge vous aident à mesurer différentes dimensions de scaling individuellement. Pour la planification de la capacité, déterminez le seuil de charge pour des performances acceptables dans plusieurs dimensions. Par exemple, au lieu de mesurer la limite absolue d'une requête de service tout au long, envisagez de mesurer les éléments suivants :

  • Taux de requêtes auquel le service peut diffuser une latence au 99e centile inférieure à un nombre spécifié de millisecondes. Le nombre est spécifié par le SLO du service.
  • Taux de demandes maximal qui ne provoque pas le dépassement des niveaux optimaux pour l'utilisation des ressources système. Notez que l'utilisation optimale varie selon l'application et peut être considérablement inférieure à 100 %. Par exemple, avec une utilisation maximale de la mémoire à 80 %, l'application peut mieux gérer les pics de charge mineurs que si l'utilisation maximale était de 99 %.

Bien qu'il soit important d'utiliser les résultats des tests de charge pour prendre des décisions en matière de planification de la capacité, il est également important de comprendre le comportement d'un service lorsque la charge dépasse la capacité. Certains comportements du serveur qui sont souvent évalués à l'aide de tests de surcharge sont les suivants :

  • Équilibrage de charge: lorsqu'un service reçoit un nombre excessif de requêtes ou de connexions entrantes, il peut répondre en ralentissant toutes les requêtes ou en rejetant certaines, afin de maintenir des performances acceptables pour les autres requêtes. Nous vous recommandons cette dernière approche pour éviter les délais avant expiration du client avant de recevoir une réponse et pour réduire le risque d'épuisement de la mémoire en diminuant la simultanéité des requêtes sur le serveur.

  • Résilience contre l'épuisement des ressources : un service évite généralement le plantage suite à l'épuisement des ressources, car il est difficile pour les requêtes en attente de progresser davantage si le service a planté. Si un service de backend comporte de nombreuses instances, la robustesse de chacune d'elles est essentielle pour la disponibilité globale du service. Bien qu'une instance redémarre à partir d'un plantage, d'autres instances peuvent rencontrer une charge plus importante et provoquer une défaillance en cascade.

Recommandations générales relatives aux tests

Lorsque vous définissez vos scénarios de test, tenez compte des recommandations suivantes.

Créer des tests à petite échelle

Créez des tests à petite échelle pour mesurer les limites de performances du serveur. Une capacité de serveur excessive engendre un risque qu'un test ne révèle pas les limites de performances du service lui-même, mais peut révéler des goulots d'étranglement dans d'autres systèmes, tels que les hôtes clients ou la couche réseau.

Pour de meilleurs résultats, prenons l'exemple d'un scénario de test qui utilise une seule instance de machine virtuelle (VM) ou un pod Google Kubernetes Engine (GKE) pour tester indépendamment le service. Pour obtenir une charge complète sur le serveur, vous pouvez, si nécessaire, utiliser plusieurs VM, mais n'oubliez pas qu'elles peuvent compliquer la collecte des données de performances.

Choisir des modèles de chargement en boucle ouverte

La plupart des générateurs de charge utilisent le modèle de boucle fermée pour limiter le nombre de requêtes simultanées et retarder les nouvelles requêtes jusqu'à ce que les requêtes précédentes soient terminées. Nous ne recommandons pas cette approche, car les clients de production du service peuvent ne pas présenter un tel comportement de limitation.

En revanche, le modèle de boucle ouverte permet aux générateurs de charge de simuler la charge de production en envoyant des requêtes à un rythme régulier, indépendant de l'arrivée des réponses du serveur.

Nous recommandons les générateurs de charge suivants pour les tests de charge du service de backend :

Nighthawk

Nighthawk est un outil Open Source développé en coordination avec le projet Envoy. Vous pouvez l'utiliser pour générer une charge client, visualiser les analyses comparatives et mesurer les performances du serveur pour la plupart des scénarios de test de charge des services HTTPS.

Tester HTTP/1

Pour tester HTTP/1, utilisez la commande suivante :

nighthawk_client URI \
    --duration DURATION \
    --open-loop \
    --no-default-failure-predicates \
    --protocol http1 \
    --request-body-size REQ_BODY_SIZE \
    --concurrency CONCURRENCY \
    --rps RPS \
    --connections CONNECTIONS

Remplacez les éléments suivants :

  • URI : URI à comparer
  • DURATION : durée totale d'exécution du test en secondes
  • REQ_BODY_SIZE : taille de la charge utile POST dans chaque requête
  • CONCURRENCY : nombre total de boucles d'événements simultanées.

    Ce nombre doit correspondre au nombre de cœurs de la VM cliente.

  • RPS : taux cible de requêtes par seconde et par boucle d'événement

  • CONNECTIONS : nombre de connexions simultanées, par boucle d'événement

Consultez l'exemple ci-dessous :

nighthawk_client http://10.20.30.40:80 \
    --duration 600 --open-loop --no-default-failure-predicates \
    --protocol http1 --request-body-size 5000 \
    --concurrency 16 --rps 500 --connections 200

Le résultat de chaque exécution de test fournit un histogramme des latences de réponse. Dans l'exemple de la documentation de Nighthawk, notez que la latence du 99e centile est d'environ 135 microsecondes.

Initiation to completion
    samples: 9992
    mean:    0s 000ms 113us
    pstdev:  0s 000ms 061us

    Percentile  Count       Latency
    0           1           0s 000ms 077us
    0.5         4996        0s 000ms 115us
    0.75        7495        0s 000ms 118us
    0.8         7998        0s 000ms 118us
    0.9         8993        0s 000ms 121us
    0.95        9493        0s 000ms 124us
    0.990625    9899        0s 000ms 135us
    0.999023    9983        0s 000ms 588us
    1           9992        0s 004ms 090us

Tester HTTP/2

Pour tester HTTP/2, utilisez la commande suivante :

nighthawk_client URI \
    --duration DURATION \
    --open-loop \
    --no-default-failure-predicates \
    --protocol http2 \
    --request-body-size REQ_BODY_SIZE \
    --concurrency CONCURRENCY \
    --rps RPS \
    --max-active-requests MAX_ACTIVE_REQUESTS \
    --max-concurrent-streams MAX_CONCURRENT_STREAMS

Remplacez les éléments suivants :

  • URI : URI à comparer
  • DURATION : durée totale d'exécution du test en secondes
  • REQ_BODY_SIZE : taille de la charge utile POST dans chaque requête
  • CONCURRENCY : nombre total de boucles d'événements simultanées.

    Ce nombre doit correspondre au nombre de cœurs de la VM cliente.

  • RPS : taux cible de requêtes par seconde pour chaque boucle d'événements

  • MAX_ACTIVE_REQUESTS : nombre maximal de requêtes actives simultanées pour chaque boucle d'événements

  • MAX_CONCURRENT_STREAMS : nombre maximal de flux simultanés autorisés sur chaque connexion HTTP/2

Consultez l'exemple ci-dessous :

nighthawk_client http://10.20.30.40:80 \
    --duration 600 --open-loop --no-default-failure-predicates \
    --protocol http2 --request-body-size 5000 \
    --concurrency 16 --rps 500 \
    --max-active-requests 200 --max-concurrent-streams 1

ab (outil de benchmark Apache)

ab est une alternative moins flexible à Nighthawk, mais elle est disponible en tant que package sur presque toutes les distributions Linux. ab n'est recommandé que pour des tests simples et rapides.

Pour installer ab, utilisez la commande suivante :

  • Sous Debian et Ubuntu, exécutez sudo apt-get install apache2-utils.
  • Sur les distributions basées sur RedHat, exécutez sudo yum install httpd-utils.

Après avoir installé ab, utilisez la commande suivante pour l'exécuter :

ab -c CONCURRENCY \
    -n NUM_REQUESTS \
    -t TIMELIMIT \
    -p POST_FILE URI

Remplacez les éléments suivants :

  • CONCURRENCY : nombre de requêtes simultanées à effectuer
  • NUM_REQUESTS : nombre de requêtes à effectuer
  • TIMELIMIT : nombre maximal de secondes à consacrer aux requêtes
  • POST_FILE : fichier local contenant la charge utile HTTP POST
  • URI : URI à comparer

Consultez l'exemple ci-dessous :

ab -c 200 -n 1000000 -t 600 -P body http://10.20.30.40:80

Dans l'exemple précédent, la commande envoie des requêtes avec une simultanéité de 200 (modèle de boucle fermée), et s'arrête après 1 000 000 de requêtes (un million) ou 600 secondes de temps écoulé. La commande inclut également le contenu du fichier body en tant que charge utile HTTP POST.

La commande ab produit des histogrammes de latence de réponse semblables à ceux de Nighthawk, mais sa résolution est limitée à quelques millisecondes, au lieu de microsecondes :

Percentage of the requests served within a certain time (ms)
    50%     7
    66%     7
    75%     7
    80%     7
    90%    92
    95%   121
    98%   123
    99%   127
    100%  156 (longest request)

Étapes suivantes