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

Lors de l'intégration d'un service de backend avec Application Load Balancer, 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 entre différentes dimensions de performances lors de la planification de la capacité, tels 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 l'impact de la surcharge du système sur la disponibilité d'un service.

Objectifs des tests de charge

Un test de charge typique mesure le comportement visible de l'extérieur du service backend sous 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 selon la mise en œuvre du service. Les applications connaissent une réduction des performances, l'épuisement de la charge ou des plantages 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 calcul.
    • Pour un serveur d'applications utilisant des threads, il est courant 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 limite de taille du pool détermine la limite supérieure de la simultanéité de connexion.
    • Service déployé en tant que frontend d'un autre service disposant d'un pool de connexions de backend de taille limitée.

Planification des capacités et tests de surcharge

Les outils de test de charge vous aident à mesurer différentes dimensions de scaling individuellement. Pour la planification de la capacité, déterminez le seuil de charge pour les 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. Ce 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 de planification de la capacité, il est tout aussi 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 :

  • Élimination de la charge : lorsqu'un service reçoit un nombre excessif de requêtes ou de connexions entrantes, il peut ralentir toutes les requêtes ou en rejeter certaines pour maintenir des performances acceptables pour les autres. Nous recommandons la 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 réduisant 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 chaque instance est essentielle pour la disponibilité globale du service. Lorsqu'une instance redémarre après un plantage, d'autres instances peuvent subir une charge plus importante, ce qui peut entraîner 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. Avec une capacité de serveur excessive, un test risque de ne pas révéler les limites de performances du service lui-même, mais de découvrir 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, si nécessaire, vous pouvez utiliser plusieurs VM, mais n'oubliez pas qu'elles peuvent compliquer la collecte des données de performances.

Choisir des modèles de charge en boucle ouverte

La plupart des générateurs de charge utilisent le modèle en boucle fermée pour limiter le nombre de requêtes simultanées et retarder les nouvelles requêtes jusqu'à ce que les 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 en 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épendamment du taux d'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 des benchmarks 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

La sortie 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 les tests rapides et simples.

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