I. Introduction▲
Mettons-nous d'abord d'accord sur la définition de « tests de charge à caractère technique (ou test de robustesse) ».
Il s'agit de tester un système en mode dégradé : ralentissement de l'application, un des serveurs est non disponible, réseau saturé, panne mémoire, disque plein, réseau défaillant, etc. sous forte charge.
Maintenant que nous sommes d'accord, commençons.
Votre application a réussi tous les tests, qu'ils soient unitaires, d'intégration, fonctionnels ou de charge et nous voilà enfin prêts à la mettre en production.
Mais vous vous posez toujours des questions sur comment va réagir votre application en cas de fonctionnement dégradé.
Les délais font que l'application est mise en production sans la réalisation de ces tests.
Deux jours plus tard, après une campagne de publicité qui vous a apporté beaucoup de nouveaux utilisateurs, on vous réveille à deux heures du matin pour que vous veniez d'urgence au bureau, car l'application est dans un état instable.
Dommage, car des tests de robustesse auraient sûrement pu vous éviter ce désagrément.
Vous ne me croyez pas, vous avez raison.
Et si je vous dis que lors de mes missions, j'ai rencontré :
- des crashs de l'application à cause d'un consommateur JMS non démarré ;
- des crashs de l'application à cause d'un load balancer mal configuré ;
- des problèmes de répartition de charge dans un cluster d'Apache ActiveMQ après l'arrêt/relance d'un nœud.
Toujours pas. Et si je vous dis que ces problèmes sont arrivés dans de grosses structures sur des projets plus ou moins gros et avec des gens compétents ?
Bon, vous ne me laissez plus le choix, nous allons voir un exemple qui vous prouvera (enfin je l'espère) l'importance de ces types de tests.
Mais avant cela, comment allez-vous reproduire ce bug qui est arrivé en environnement de production sur votre environnement de test ?
Avec un test de robustesse bien sûr.
II. Une preuve▲
Notre application est la fameuse Spring PetClinic.
Pour simuler la charge, nous allons utiliser Apache JMeter.
Notre script réalisera les actions suivantes :
- Aller sur la page d'accueil :
- Aller sur la page de recherche :
- Exécuter une recherche :
- Notre script aura la forme suivante :
Maintenant, nous voulons savoir : « Que se passe-t-il si votre fonctionnalité de recherche (module critique de votre applicatif) met plus de huit secondes à répondre ? »
Pour simuler ce ralentissement, nous allons utiliser JBoss Byteman qui se présente sous la forme d'un agent Java capable de modifier le comportement de notre application en instrumentant son bytecode.
Les avantages de JBoss Byteman sont nombreux :
- on peut déclencher ce que l'on veut (ralentissement de l'application, perte de connexion à la base de données, disque plein, etc.) lorsqu'on veut (il suffit d'uploader les règles Byteman sur l'application cible) ;
- on peut simuler des choses difficilement testables avec un test de charge (ralentissement d'une partie de l'application, disque dur plein, avoir un problème de connexion à la base une fois sur cinq, etc.) ;
- le tout sans intervention extérieure (pas besoin de l'équipe d'exploitation pour arrêter la base de données, il suffit d'avoir le même compte pour le lancement de Byteman et de l'application).
Ajoutons Byteman à notre script (connexion de Byteman à la JVM, chargement des règles de modification du comportement de l'application dix minutes après le début du test, suppression de ces mêmes règles vingt minutes après le début du test).
Avec ce script, Apache JMeter s'occupera de tout (simulation des utilisateurs, de la charge et du lancement de JBoss Byteman).
Lançons notre test avec une charge faible.
On voit bien l'impact sur les temps de réponse.
À ce moment-là, on peut se dire : « c'est dommage pour les utilisateurs, mais ça pourrait être pire ».
Exécutons le test à nouveau, mais avec une charge plus élevée et regardons plus en détail cette fois-ci.
Changement de comportement, les temps de réponse ne reviennent pas à la normale après la suppression des règles de modification du comportement de l'application.
Si on regarde le nombre de requêtes traitées par notre application, on remarque une baisse importante de celui-ci (et adieu à notre prime indexée sur les ventes réalisées dans le cas d'un site de e-commerce).
Et comme pour les temps de réponse, elles ne reviennent pas à la normale.
Afin de comprendre ce comportement, nous allons utiliser JProfiler qui va nous faciliter l'analyse de notre application.
On remarque que la mémoire de notre application est moins stressée (moins de passage du GarbageCollector).
Ce qui est normal, car notre application traite moins de requêtes.
La contention ne venant pas de la gestion de la mémoire, on regarde du côté des threads.
Bingo, on remarque :
- qu'un ou plusieurs threads sont bloqués à partir du chargement des règles Byteman ;
- que les threads répondant aux requêtes HTTP (en bleu) sont utilisés de manière « chaotique » lorsque les règles Byteman sont actives (encadré en rouge) ;
- que l'utilisation des threads répondant aux requêtes HTTP (en bleu) atteint un plafond après la suppression des règles Byteman.
Le « Thread Views » de JProfiler nous confirme bien nos remarques.
Ici l'utilisation « chaotique » (beaucoup plus de threads en pause sur la partie encadrée en rouge) puis l'atteinte du plafond (plus aucun thread en pause).
Et ici la saturation du thread qui accepte les connexions HTTP.
On en déduit que l'on a saturé notre pool de threads (paramétré à dix afin de faciliter la lecture des captures d'écran et notre analyse) et que l'application n'arrive plus à « sortir la tête de l'eau ».
Et en voilà la preuve.
On vient de voir l'effet boule de neige (blocage de notre application) provoqué par le ralentissement d'une partie de cette même application.
Plusieurs solutions existent pour corriger ce problème :
- optimisation du code (solution à court terme, car la probabilité que le problème arrive en production ne sera jamais nulle) ;
- tuning d'Apache Tomcat ;
- duplication de votre module de recherche sur le même serveur (Vertical Scaling) ;
- duplication de votre module de recherche sur d'autres serveurs (Horizontal Scaling) ;
- changement de technologie pour votre module de recherche (par exemple ElasticSearch).
III. Conclusion▲
Ma réponse à la question « Les tests de charge à caractère technique (robustesse) sont-ils importants ? » est oui.
Mes conseils sont :
- de faire des tests de charge à caractère technique en plus des tests de charge ;
- si possible les automatiser (dans cet exemple avec Apache JMeter et JBoss Byteman) ;
- de bien vous outiller (ici JProfiler) ;
- ces tests ne sont qu'une simulation et donc de bien superviser l'environnement de production (par exemple avec AppDynamics, Nagios).
IV. Remerciements▲
Les remerciements vont à Antonio Gomes-Rodrigues pour le partage de ce tutoriel avec les lecteurs de Developpez.com.
Nous tenons à remercier f-leb pour la relecture orthographique attentive de cet article et Mickael BARON pour la mise au gabarit.