Une démarche de tests de performance
Une démarche naïve de réalisation de tests de performance est d'effectuer des améliorations successives sur un système donné, donc d'avoir un processus pseudo-itératif. Donc, pourquoi ne pas se baser sur les processus développés dans les méthogologie Agiles, voir même d'utiliser les cycles d'améliorations continues issue du Lean.
En effet, on peut très facilement se rapprocher de la roue de Deming, appelée plus communément la démarche PDCA : Plan, Do, Check, Act.
Le but des tests de charge est de trouver les points faibles d'une architecture dans le but de l'améliorer : performance, stabilité sous charge, coût, etc. On rapproche donc les étapes du PDCA avec un processus de tests de charge :
- Le P (ou plan) a pour but d'établir, préparer et de planifier un plan d'action. Que doivent mesurer, améliorer nos tests pour cette itération.
- Le D (ou do) est la phase de faire, celle où on effectue les tests à proprement parler.
- Le C (ou check) est le moment où l'on vérifie les résultats renvoyés par nos tests. C'est aussi dans cette phase que l'on se préoccupe du rapprochement et de l'agrégation des métriques recueillies dans la phase précédente.
- Le A (ou act) sert à améliorer le système que l'on test. C'est la phase d'optimisation.
Avant de faire des tests
Avant de se lancer dans ce cercle vertueux d'amélioration, il est important de déterminer le but de ces tests. Est-il de :
- S'assurer qu’un système fonctionne sans erreur sous certaines conditions de charge ?
- S'assurer qu’un système répond aux critères d’acceptabilité des performances ?
- Optimiser les temps de réponse pour augmenter la satisfaction utilisateur ?
- Optimiser les coûts d’infrastructure et du run du système testé ?
- Connaître la capacité du système ?
En fonction des réponses à ces questions, on peut déterminer quel type de tests effectuer.
Type(s) de tests à effectuer
Tests de performances
Objectif : Tests sur un ou plusieurs scénarios sous une charge modérée du système complet et/ou d’un sous-système nécessitant un point d’attention
Exemple : La souscription est testée pour 1 utilisateur et, pour chaque étape du use case, on mesure le temps passé dans les différents composants de l’application
Test de vieillissement
Objectif : Déterminer la capacité de l’application à fonctionner sur une période étendue
Exemple : On simule l’utilisation de l’application pendant 48h, avec une charge constante et égale à la charge moyenne
Test de charge
Objectif : Mesurer la tenue en charge de l’application sur la population cible
Exemple : On simule l’utilisation de l’application par 200 utilisateurs (avec des scénarios différents) en parallèle pendant 2h
Test de rupture / Stress test
Objectif : Déterminer les limites de l’application
Exemple : On augmente le nombre d’utilisateurs en parallèle sur l’application jusqu’à ce que le taux d’erreurs / les temps de réponse ne soient plus acceptables
Une fois choisis le ou les types de tests à effectuer, il faut déterminer les profils de charge des tests. C'est-à-dire les scénarios à tester, ainsi que leur typologie.
Choix des scénarios
Le choix des scénarios doit être fait en prenant en compte :
- Que veut-on tester ? Quelles fonctionnalités ? Ex : Le tunnel de paiment d'un site d'e-commerce
- Quels scénarios utilisateurs ? Ex : Mes utilisateurs s'enregistrent sur mon site, ils passent 10 secondes sur la page d'inscription, etc.
- Qu'est ce que les utilisateurs utilisent à l'heure actuelle sur le système ? Comment l'utilisent-ils ? Ex : Mes utilisateurs ont un think-time (temps passé entre deux actions d'un utilisateur) de 15 secondes sur la page de login
- Est-ce que l'utilisation de certaines fonctionnalités va augmenter dans un proche futur (action marketing, publicité, montée en charge, etc.) ? Ex : Le marketing va lancer une grande campagne sur le parrainage de ma banque.
Choix des métriques
La dernière étape est de décider quelles métriques mesurer et celles que l'on veut améliorer. Il faut garder à l'esprit que trop de métriques tue les métriques. En effet, si on regarde trop de données, on risque de perdre de vue le but de notre campagne de tests. De plus, il faut séparer les métriques "business" qui sont celles à améliorer, et les métriques techniques qui vont nous permettre d'investiguer a posteriori les points de contentions de notre système. Les métriques les plus classiques sont :
- Métriques business :
- Temps de réponse d'une fonctionnalité
- Nombre d'écrans affichés
- Nombre d'opérations par unité de temps
- Métriques techniques :
- Quantité de mémoire vive utilisée
- Volume de données transféré sur le réseaux
L'intégration des métriques est un point critique de la démarche d'amélioration des performances. Nos tests vont générer une très grosse quantités de données, qu'il va falloir agréger.
Sans faire dans un cours de statistiques, on peut déjà calculer facilement le minimum, le maximum et la moyenne d'une métrique. Il est aussi intéressant de calculer l'écart type, qui va nous donner comment nos valeurs sont réparties autour de la moyenne. Plus cette valeur est petite plus les données sont uniformes (proche de la moyenne). Mais si vous n'avez qu'un calcul à faire, c'est le percentile ou centile. Le percentile X est la Xème mesure sur 100 triées par ordre croissant, c'est donc la valeur où X% des mesures lui sont inférieures. De manière générale on calcule le percentile 90, qui est plus représentatif du ressenti utilisateur qu'une simple moyenne ou que l'écart-type. Cette valeur de 90 est déduite de la distribution de Gauss (pour plus de détails). Pour un service, dont dépend d'autres services ou applications, un bon percentile 99 (ou plus) sera nécessaire pour limiter l'impact de ce service sur les systèmes qui l'utilisent.
Préparation de la campagne de tests
Une fois toutes ses données intégrées, il faut préparer notre campagne de tests. Tout d'abord il faut choisir les outils pour :
- Effectuer les tests : les injecteurs
- Tracer les métriques : les sondes
- Recueillir et agréger les métriques
- Présenter les métriques
Le but de notre article n'est pas de présenter ni de comparer ses outils, mais il faut garder en tête quelques points important en les choisissant. Il est important de pouvoir mettre en relation les données envoyées par l'injecteur et les sondes, ce qui va permettre de relever rapidement les relations entre les données remontées et les actions faites par l'injecteur. De même, il faut pouvoir générer un rapport, à l'usage des non-experts, à partir des métriques.
Ainsi, avant de commencer toute campagne de test il est important d'automatiser au maximum toute la chaîne des tests. Le but ultime étant que toute la chaîne test + agrégation + génération du rapport se fasse automatiquement.
Il faut néanmoins raccourcir au maximum la phase entre la fin des tests et la génération du rapport, ce qui permet de faire une modification rapidement pour relancer des tests le plus vite possible.
Établir une mesure de référence (Plan du PDCA)
La première étape d'une itération de test de performance est de savoir où l'on veut aller. Nous verrons à la dernière phase (celle du A) que l'on ne doit changer qu'un seul paramètre dans notre système entre chaque test. Ce faisant, on doit être capable de globalement prévoir comment nos métriques vont évoluer. De plus, cette étape doit nous garantir que 2 tests consécutifs sans modification doivent avoir des résultats équivalents (à quelques pour cents près)
Si cette itération est la première, elle va nous servir de référence pour toutes les itérations suivantes, c'est le point de départ de notre système. Il est même préférable de faire quelques itérations de "rodages", donc sans amélioration du système à tester, mais uniquement pour effectuer des améliorations sur le processus de tests.
Dans un cas idéal, on peut réaliser plusieurs itérations de constructions de notre démarche de tests, avec la méthode des petits pas. On commence par un scénario simpliste qui permet de mettre en place les outils. Puis on fait grossir ce scénario jusqu'au résultat souhaité. Ensuite on met en place les sondes puis toute la démarche d'automatisation de l'agrégation et présentation des métriques. Ce faisant, on est assuré d'avoir une process fluide et surtout adapté à nos besoins. On peut rapprocher ce processus incrémental de construction de la démarche du TDD.
Exécuter les tests (Do du PDCA)
La phase d'éxecution des tests de performance est l'étape que l'on pense naturellement à automatiser. Il est en effet peu envisageable de réaliser un test de charge manuellement.
Même s'il est possible de réaliser un test d'optimisation unitaire manuellement, l'automatisation permettra de :
- Fiabiliser le résultat obtenu. En effet, on s'assure ainsi que le test est systématiquement exécuté selon la même procédure
- Accélérer l'exécution des tests
- Exécuter les tests régulièrement dans une intégration continue. Ceux-ci permettent d'assurer la non-regression de la performance du système testé
Afin de fiabiliser le résultat des tests et de s'assurer qu'ils sont exécutés dans les mêmes conditions à chaque fois, la phase d'initialisation de l'environnement testé gagnera à être automatisée. En effet, ces étapes d'initialisation sont crutiales afin de garantir que les tests soient répétables (ie. avec les même résultats) et que l'on puisse en tirer les bonnes conclusions. Réalisées manuellement, elles peuvent devenir rapidement fastidieuses (initialiser la base de données, vider les caches, purger les files de messages, ...) et sujettes à erreurs et/ou oublis faussant ainsi les résultats.
De plus, l'environnement de tests doit être entièrement dédié pour garantir la stabilité des tests.
Recueillir et analyser les mesures de performance (Check du PDCA)
Le recueil des métriques
Une fois les tests exécutés, il faut recueillir les métriques à la fois côté injecteur mais aussi sur le système testé. Cette phase peut-être manuelle ou automatique. Tout dépend de la complexité du système testé : si vous n'avez qu'un seul serveur d'application, une base de données et l'injecteur, les métriques pourront être exploitées en allant voir chacun des outils de collecte des métriques; si l'architecture du système se complexifie avec des reverses proxy, un cluster de serveurs d'application, un broker de messages, l'injecteur doit être distribué, ..., l'aggrégation automatique des métriques de chacun des sous-systèmes devient nécessaire pour ne pas perdre son temps à collecter ces resources.
Certains outils de tests de performance vous propose de le faire pour vous en installant leur propre sonde de monitoring sur le système testé. Une autre possibilité pourrait être de requêter (avec un aggrégateur de logs tel que Splunk) les logs du système testé qui contiennent généralement de précieuses informations.
Tracer les résultats des tests de performance
Si les mesures de chaque tir doivent être tracées (utile pour suivre l'impact sur les performances des optimisations et du développement), il est judicieux de tracer :
- La révision du gestionnaire de source du code applicatif mais aussi de la configuration de l'infrastructure (si celle-ci est versionné dans un gestionnaire de sources),
- Les paramètres du test (scénario, nombre d'utilisateurs, think time, arrival rate, ...),
- La configuration de l'environnement complet (configuration des machines, nombre de serveurs, ...).
Cette tracabilité permettra d'identifier les différences entre chaque tir et de comprendre l'origine des résultats constatés. En effet, un tir de performance doit avoir des résultats prédictifs :
- Aucune évolution par rapport au tir précédent lorsqu'aucune modification n'a été effectuée : c'est ce que l'on doit obtenir lorsqu'on établie la mesure de référence.
- Les mesures de performance sont par nature stochastiques : 2 tirs successifs ne donneront pas exactement les même résultats. Cependant une variation supérieure à 10% entre 2 tirs est signe d'une instabilité (légitime ou non) dans le système ou dans le test lui même :
- Les tests doivent donc être suffisamment longs pour lisser les fluctuations,
- Il faut s'assurer que les tests sont effectués dans les mêmes conditions et selon la même procédure,
- Il faut exclure les variations légitimes des mesures. Exemple : Un client est mis en attente en faisant du polling d'une page mise en cache jusqu'à ce qu'il soit redirigé. Plus le polling dure, plus le nombre de requêtes de polling seront nombreuses et servir une page en cache étant peu coûteux, celà a tendance à diminuer la moyenne des temps de réponse globaux du système. Celà signifie donc que plus un client attend, plus les métriques de notre système sont bonnes, ce n'est clairement pas ce à quoi on s'attend. Dans ce cas, il vaut mieux ne pas prendre en compte les temps de réponse des requêtes de polling mais plutôt mesurer le temps d'attente des clients.
- À l'opposé si aucun gain n'est perçu suite à une optimisation, ou que le gain est inférieur à 5%, c'est que "l'optimisation" n'en est pas une. Un gain de plus de 10% est un résultat "classique", un entre 5% et 10% doit être interprété avec précaution.
Identification des points de contention
Lorsque l'on a recueilli les métriques, que l'on a validé que celles-ci sont stables d'un tir à l'autre et que les modifications apportées au système (optimisation ou ajout de fonctionnalités) ont permis d'améliorer le système, il faut identifier le point de contention suivant.
Pour cela, la loi d'Amdahl nous dit qu'il vaut mieux investir sur une petite optimisation d'un sous-système gourmant au global plutôt qu'une grosse optimisation d'un sous-système peu consommateur. Ça parait évident dit comme ça mais ça ne fait pas de mal de se reposer la question lorsque l'on se lance dans une optimisation. Il faut aussi bien sûr tenir compte du coût d'implémentation de l'optimisation. S'il faut revoir complètement l'architecture pour une amélioration somme toute pas peu importante, il vaut peut-être mieux chercher une autre amélioration.
Un dernier point à prendre en compte lorsque l'on veut identifier un point de contention est de ne pas oublier l'outil d'injection lui-même qui peut devenir lui-même le point de contention. Par exemple, s'il n'arrive plus à simuler le nombre d'utilisateurs virtuels voulus, alors que le système testé peut répondre sans problème, il se peut que l'injecteur ne puisse plus récupérer les réponses du système à la cadence souhaitée. Dans le cas où il s'agit de requêtes HTTP, le serveur HTTP affichera des temps de réponse mauvais alors que c'est l'injecteur qui ne suit pas.
Analyse des mesures de performance
Afin d'obtenir une vision plus global du système testé pour identifier les points de contention, observer les impacts des optimisations ou pour communiquer ses résultats au métier, il est nécessaire de présenter de façon conscise et compréhensible les mesures recueillies. Sans ce type de communication, l'effort de test de performance risque de ne pas être reconnu à sa juste valeur.
En effet, un tableau plein de chiffres peut être difficile à analyser et très peu compréhensible par un non-expert. Les outils de test de performance éditeur, accompagnés de sondes, sont souvent complétés par des outils de reporting offrant une représentation graphique des mesures de performance. Sans ces derniers, Excel vous rendra de grands services malgrès une exploitation plus manuelle.
Optimiser le système (Act du PDCA)
Une fois que l'on a identifié le sous-système à optimiser, il faut creuser plus profond afin de comprendre l'origine du point de contention et pouvoir ainsi imaginer une solution pour lever la limite. Cependant, il s'avère que de temps à autre la cause profonde du problème de performance n'est pas si évidente qu'il n'y parait. C'est pour cela qu'il faut se mettre en position de tester rapidement nos hypothèses avant d'effectuer des changements radicaux qui pourraient s'avérer au final inutiles. Pour cela, il est possible de monter un micro-benchmark d'un sous-système ou d'exécuter le test de performance complet avec une modification quick & dirty sur une branche séparée.
Que ce soit, pour une optimisation en bonne et due forme ou une optimisation quick & dirty, chaque tir de performance ne doit tester qu'une modification à la fois afin de valider le gain (ou la dégradation) apporté et tirer les bonnes conclusions ("c'est bien ce sous-système le problème et ma solution quick & dirty valide que je suis sur la bonne voie"). ex: rajout d'un seul index, modification d'une requête, mise à jour d'une brique, etc.
De même chaque modification ayant pour objectif d'améliorer les performances ou ayant un potentiel impact sur les performances (tuning DB, VM, ...) doit être testé pour au moins confirmer que ces modifications n'ont pas un impact négatif (non-assumé) sur les temps de réponse du système.
Enfin, toutes les optimisations ne valent pas le coût d'être entreprises (Constant Tweak Syndrome). En effet, il y aura toujours des optimisations possibles, mais si celles-ci n'ont pas d'impact réel sur le système ou si elles coûtent plus à implémenter que la valeur qu'elles pourraient apporter, il est nécessaire de savoir se poser la question sur leur utilité.
Conclusion
La mise en place d'une campagne de tests de performance est généralement coûteuse. Afin de la rendre la plus efficace possible, il convient d'adopter une démarche stricte pour ne pas se perdre dans les méandres de l'optimisation sans fin et/ou inutile.
Une démarche d'amélioration continue de type PDCA apporte un cadre aidant à se concentrer sur l'objectif même de ces tests : l'amélioration des performances, de la stabilité ou encore du coût du système testé. D'autre part, l'aspect itératif de cette démarche permet d'introduire les tests de performance au plus tôt dans les cycles de développement et ainsi éviter tous les écueils rencontrés en déroulant les tests à la fin des développements. De plus, il est important de travailler en étroite collaboration avec les équipes de développement, voir que ça soit l'équipe de développement elle-même qui réalise les tests. En effet, cela permet d'avoir connaissance au fil de l'eau des futurs fonctionnalités qui pourraient impacter les scénarios, de pouvoir mettre en place des bonnes pratiques de performances au sein de l'équipe et d'éviter que les efforts d'optimisations réalisés soient détruits par une action involontaire d'un développeur mal (in)formé.
Enfin, l'automatisation de l'ensemble de la démarche (initialisation de l'environnement, exécution des tests, collecte des mesures et reporting) permet d'exécuter des tests de non régression de performance dans son intégration continue.
Le but ultime est d'intégrer les tests de performances dans sa démarche de déploiement continue, afin de valider la conformité de toute nouvelle version du système vis a vis de ses SLA.