pic
Dans un précédent post, nous avions vu les grands concepts d’Hadoop (cf. http://www.opensides.fr/2011/03/10/hadoop-en-moins-de-5-minutes).  Aujourd'hui nous allons consacrer 5 nouvelles minutes avec Hadoop pour passer à la pratique. Les objectifs: Monter un environnement opérationnel en moins de 2 minutes Tour d'horizon de Cloudera en 1 minute Tester quelques commandes HDFS en 1 minute Tester un Map Reduce en 1 minute Monter un environnement opérationnel en 2 minutes 30 secondes pour parler de Cloudera Apache distribue une version packagée de l'écosystème complet Had...
pic
Dans ce tutorial, nous allons découvrir Hadoop au travers de son système de fichiers distribués et son mécanisme de Map/Reduce. Objectifs Comprendre les grands concepts de Hadoop Comprendre le HDFS et le mécanisme de Map/Reduce 2 minutes 30 pour comprendre les grands concepts Hadoop est un projet Open Source écrit en java, distribué par la fondation Apache. Ce framework est adapté dans le stockage et le traitement par lots de très grandes quantités de données (à partir du pétaoctet). Il a été mis en avant par des grands noms du web comme Yahoo! ou Facebook. Son sys...
pic
Dans cet article nous allons faire la connaissance rapide des serveurs web asynchrones. Le but de ce post est de vous faire découvrir cette nouvelle génération de serveur en montrant comment installer et configurer de façon basique celui qui me semble le plus aboutit et le plus performant. Nous verrons plus tard tirer profit de ce type d'architecture pour servir de hautes volumétries. Quelques rappels Un serveur http a pour vocation de servir du contenu en fonction des requêtes clientes. Ce contenu est distribué via le protocole http et peut être statique (images, css, javascript, â€...
pic
Tout le monde connaît Memcached ? Non ? Memcached est un cache Open Source distribué et non répliqué. Cela veut dire que que nous pouvons utiliser plusieurs instances de Memcached mais que chaque instance est autonome. Si l'une d'elle tombe, ses données seront donc perdues (pas de réplication entre instance). Pour aller plus loin, je vous conseille le wiki de Memcached: http://memcached.org/ Nous allons voir maintenant comment l'installer sur un unix: Installation de libEvent (une dépendance de Memcached) Vérifier que libEvent ne soit pas déja installé en tapant la ...
pic
Dans cet article nous allons voir comment apporter très facilement une solution de haute disponibilité à une application avec HAProxy. Quelques définitions Haute disponibilité (cf. wikipedia) La haute disponibilité est un terme souvent utilisé en informatique, à propos d'architecture de système ou d'un service pour désigner le fait que cette architecture ou ce service a un taux de disponibilité convenable. La disponibilité est aujourd'hui un enjeu important des infrastructures informatiques. On estime aujourd'hui que la non-disponibilité d'un service informatique peut avoi...
mar

25

Introduction

Une architecture SOA a pour principe d’exposer un ensemble de services à destination des utilisateurs. Ces services peuvent éventuellement être relayés par ces mêmes utilisateurs qui peuvent les décorer de nouvelles fonctionnalités. Un des facteurs critique de ce genre d’architecture est la «scalabilité». C’est à dire que le système doit pouvoir répondre linéairement à une charge de requêtes croissante.

Pour garantir une bonne scalabilité, l’enjeu est d’optimiser les ressources matérielles (calcul, mémoire, i/o) de façon à servir toutes les requêtes.

Conséquence d’une mauvaise gestion des ressources:

  • dégradation des temps de réponses
  • rejet des requêtes utilisateur
  • effondrement du serveur par manque de mémoire (il faudra alors l’intervention manuelle des nos amis les administrateurs IT pour les redémarrer).

Objectif

Cet article a pour objectif de montrer comment optimiser les performances de notre service en donnant quelques pistes pour une bonne gestion des ressources au sein de notre architecture.

Petite piqûre de rappel

Rendez-vous aux pages suivantes pour un rappel sur les ressources matérielles: http://www.opensides.fr/?p=293

Et sur le fonctionnement d’une JVM (hotspot): http://www.opensides.fr/?p=271

Maintenant que nous maîtrisons ces bases, voyons comment ces ressources sont sollicitées aux travers d’un cas pratique.

Cas pratique

Pour illustrer la gestion des ressources, nous allons prendre l’exemple d’un musée. Ce musée représentera notre application. Des visiteurs formeront une file d’attente à l’entrée pour acheter leur ticket. Il représenteront les requêtes utilisateurs qui attendent d’acquérir une connexion au système. Lorsqu’un visiteur achètera son ticket, un agent lui préparera un casque d’écoute. Ce casque représentera l’accès à un service externe et la préparation par l’agent, l’acquisition de cette ressource. Le visiteur pourra alors faire sa visite avec son casque.

Voici le déroulement de la séquence:

  1. les visiteurs forment une file d’attente à l’entrée du musée (> requête au système)
  2. le visiteur  arrive au guichet, il achète son ticket (> connexion au serveur, le processeur (guichet) est sollicité et le visiteur entre dans le musée donc la mémoire est consommée)
  3. le guichet prend alors un casque dans un sac (> récupération de la ressource à partir de la mémoire morte, le sac peut être une base de donnée par exemple)
  4. le guichet initialise et le donne au visiteur (> la processeur est sollicité pour initialise un Web Service et utilise les I/O pour l’acquisition de la connexion)
  5. le client possède son casque et peut donc faire sa visite guidée pour une durée aléatoire (> utilisation du service)
  6. il interrompt ensuite sa visite mais ne sort pas du musée et ne rend pas son casque (pas bien…)

Précisions:

  • le musée ne possède qu’un seul guichet qui est en charge de délivrer les tickets et de distribuer les casques aux visiteurs (> un thread gérant une fille d’attente)
  • la récupération du casque oblige le guichetier à fouiller dans un sac et représente donc une opération longue  (1 minute) (>occupation du thread pour acquérir la connexion à la base de donnée)
  • l’initialisation du casque est aussi une opération longue (2 minute) pendant laquelle le guichet ne peut servir les autres visiteurs (>occupation du thread pour acquérir la connexion au WS externe)
  • un casque coûte 10$ (>  empreinte mémoire de l’objet (1024*1024 byte))
  • chaque client ne rend pas systématiquement son casque à la sortie,  il le laisse dans une salle du musée  (> non relâchement des objets,  fuite mémoire)
  • le musée possède 1000 casques (>taille de la zone Eden dans la JVM)
  • le musé à une capacité d’accueil de 1000 visiteurs simultanés (>capacité mémoire du système)

Limites du système actuel

  • pour chaque visiteur le guichet doit prendre un casque dans un sac ce qui fait perdre 1 minute à l’ensemble des visiteurs
  • le guichet initialise ensuite le casque ce qui fait à nouveau perdre 2 minutes à l’ensemble de la file d’attente
  • les visteurs ne ressortent pas du musée (>fuite mémoire potentielle)
  • les visteurs ne rendent ps leur casque (>fuite mémoire potentielle)

Passons à la pratique

Lançons l’application pour en observer le fonctionnement:

[0 ms] Le visiteur #1  achète un ticket
[3 ms] Le guichetier récupère le casque #1 ...
[1000 ms] Le guichetier initialise le casque #1 ...
[2001 ms] Le visiteur #1  prend le casque le casque #1
[0 ms] Le visiteur #1 entre dans le musée
[1 ms] Le visiteur #2  achète un ticket
[0 ms] Le visiteur #1  commence sa visite ...
[3 ms] Le guichetier récupère le casque #2 ...
[1000 ms] Le guichetier initialise le casque #2 ...
[2000 ms] Le visiteur #2  prend le casque le casque #2
[0 ms] Le visiteur #2 entre dans le musée
[0 ms] Le visiteur #3  achète un ticket
[1 ms] Le visiteur #2  commence sa visite ...
[1 ms] Le guichetier récupère le casque #3 ...
[1000 ms] Le guichetier initialise le casque #3 ...
[2000 ms] Le visiteur #3  prend le casque le casque #3
[1 ms] Le visiteur #3 entre dans le musée
[0 ms] Le visiteur #3  commence sa visite ...
[0 ms] Le visiteur #4  achète un ticket
[707 ms] Le visiteur #2  arrête sa viste
...
[1001 ms] Le guichetier initialise le casque #79 ...
[1000 ms] Le visiteur #79  prend le casque le casque #79
[0 ms] Le visiteur #79 entre dans le musée
[0 ms] Le visiteur #79  commence sa visite ...
[0 ms] Le visiteur #80  achète un ticket
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
 at fr.opensides.lab.musee1.Guichetier.accueilVisiteur(Guichetier.java:16)
 at fr.opensides.lab.musee1.Main.main(Main.java:11)
[322 ms] Le visiteur #75  arrête sa viste
[3248 ms] Le visiteur #79  arrête sa viste
[3 ms] Le visiteur #77  arrête sa viste
[1527 ms] Le visiteur #78  arrête sa viste

On remarque sans surprise que l’initialisation des casques est très coûteuse ( 1 seconde pour la récupération / 2 secondes pour l’initialisation) et surtout systématique. Elle pénalise donc l’ensemble de la file d’attente.

Penchons nous maintenant sur la mémoire. Pour cela, ouvrons un VisualVM et regardons la gestion de la heap. (cf. ci-dessous)

On observe que les Garbage Collector (GC) représenté sur le graph par des creux sont réguliers, ce qui est normal mais ne sont pas profond, ce qui l’est moins… Cela indique que le GC est inefficace car il ne parvient pas à récupérer de la mémoire.

Conséquences

>Le musée est saturé à 80 visiteurs et nous renvoie l'erreur ci-dessous:
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
 at fr.opensides.lab.musee1.Guichetier.accueilVisiteur(Guichetier.java:16)
 at fr.opensides.lab.musee1.Main.main(Main.java:11)

Explication: qu’est ce qu’une fuite mémoire ?

C’est le pattern typique d’une fuite mémoire, la JVM ne parvient pas à relâcher les objets car ils sont référencés (en référence forte) par les visiteurs. La mémoire va donc se remplir jusqu’à saturation( palier en haut de la courbe), ce qui se traduit par le message ci-dessus.

Dans notre exemple, le gardien du musée (la JVM) interrompt le travail du guichetier pour aller chercher les casques dans le musée (Garbage Collector).
Le problème c’est que lorsqu’il trouve un casque il n’a aucun moyen de savoir si le casque est en cours d’utilisation par un visiteur. Dans le doute, il ne peut pas le récupérer car le casque appartient au visiteur (référence forte), il le laisse donc à sa place et les casques s’accumulent dans le musée.

1 ère optimisation: relâcher les objets

Quels sont les objet qui encombrent le musée ? Les casques et les visiteurs. Il faut donc les faire sortir pour corriger notre fuite mémoire.

Cela nous semble logique dans un cas de la vie réel mais étrangement cela parait beaucoup moins intuitif en programmation..

Comment faire ?

A la fin de la visite, nous allons simplement demander au visiteur de :

1/ rendre leur casque:

–> en pratique on remet le casque à null

2/ de sortir du musée:

-> en pratique ils se supprime de la map « Musée » au niveau de la méthode « interrupt » du thread visteur:

getMusee().remove(this.getId());//le visiteur sort du musée

Conséquence:
La fuite mémoire est corrigée, le musée ne sature plus.

2 ème optimisation: préparation des casques (mise en place d’un mécanisme de cache)

Il reste un problème de lenteur au niveau de la récupération l’initialisation des casque. Pour réduire ce temps d’attente des visiteurs, l’agent peut sortir à l’avance un ensemble de casques de son sac, les initialiser et les disposer sur un présentoir de façon à les distribuer plus rapidement.

  • Utilisation d’un présentoir (cache en SoftReference)
  • Le visiteur remet son casque sur le présentoir (cache en SoftReference)

Conséquence:

L’utilisation du présentoir permet de réduire considérablement les temps d’attentes des visteurs. Malgré le fait que les visteurs ne renden pas relement leur casque car ils sont référencés dans le cache, le cache en SoftRéférence permet de relâcher les objets lorsque la mémoire sature.

Le guichetier prend un casque sur son présentoir [0 ms]
[0 ms] Le visiteur #244  prend le casque le casque #244
[0 ms] Le visiteur #243  commence sa visite ...
[0 ms] Le visiteur #244 entre dans le musée
[0 ms] Le visiteur #245  achète un ticket
Le guichetier prend un casque sur son présentoir [0 ms]
[1 ms] Le visiteur #245  prend le casque le casque #245
[0 ms] Le visiteur #244  commence sa visite ...
[1 ms] Le visiteur #245 entre dans le musée
[0 ms] Le visiteur #246  achète un ticket
Le guichetier prend un casque sur son présentoir [0 ms]
[1 ms] Le visiteur #246  prend le casque le casque #246
[0 ms] Le visiteur #245  commence sa visite ...
[1 ms] Le visiteur #246 entre dans le musée
[0 ms] Le visiteur #247  achète un ticket
Le guichetier prend un casque sur son présentoir [0 ms]
[2 ms] Le visiteur #247  prend le casque le casque #247
[1 ms] Le visiteur #246  commence sa visite ...
[3 ms] Le visiteur #247 entre dans le musée
[0 ms] Le visiteur #248  achète un ticket
[0 ms] Le visiteur #247  commence sa visite ...
[0 ms] Le visiteur #240  commence sa visite ...
[51 ms] Le visiteur #238  rend dépose le casque sur le présentoir
[64 ms] Le visiteur #198  rend dépose le casque sur le présentoir

>L’application tourne en continue sans saturation

Mémoire

On observe que la heap se remplie jusqu’à atteindre son seuil max. Proche du max, les SoftReference joue leur rôle en permettant à la JVM de libérer les objets au lieu d’atteindre le max (générant une OOME).

cpu

On voit que ce mécanisme à un coût en terme de GC (en bleu l’activité cpu liée au GC).

thread

Au bout de 14 minutes, 10377 threads on pu être traités par l’application. Soit autant de visiteurs on pu visiter le musée.

Remarques:
On remarque une augmentation de la heap jusqu’à se rapprocher du max. C’est la SoftReference qui fait bien son job en permettant de relacher les objets quand la mémoire sature.
Il est important de noter que ce fonctionnement a un cout. Cette fois c’est la processeur
qui travaille en déplaçant les objets de la heap vers la perm et en relaçhant les objets (cf. cpu)

Explication

Cette opération consiste à utiliser un pool (ou cache) représenté par le comptoir qui permet d’éviter de répéter les opérations coûteuses de connexion à la base de donnée. On parle dans ce cas de « cacher » les informations pour les rendre accessible plus rapidement lors des prochaines utilisations. Dans notre exemple, le présentoir aura une capacité limité à 100 casques. Ce qui implique que le guichetier devra nettoyer régulièrement son présentoir.

Il devra donc gérer les problématiques suivantes:

  • lorsque les visteurs auront pris l’ensemble des casques il devra à nouveau récupérer les casques dans le sac et les initialiser
  • lorsque un visiteur quittera le musée rendra son casque sur la table il pourra le redistribuer à un autre visiteur
  • lorsque la table sera pleine de casque les anciens casques présents sur la table seront remis dans le sac

La technique que nous utiliserons pour implémenter cet exemple est un pool qui aura pour caractéristique de libérer les ressources les moins récemment utilisées. Ce type d’algorithme de relâchement des ressources est appellé LRU pour Last Recently Used (cf. http://fr.wikipedia.org/wiki/Algorithmes_de_remplacement_des_lignes_de_cache#LRU_.28Least_Recently_Used.29).

Les références Java pour les nuls

Il existes différents types de références selon leur degrés d’attachement (ici classés du plus fort au moins fort) (cf. http://blog.developpez.com/adiguba/p2107/java/comprendre-les-references-en-java/):

  • les références fortes (strong reference): C’est la référence par défaut. L’objet est lié fortement à l’autre comme c’est le cas actuellement avec nos casques du fait que le visteur paye la location de son casque il ya une notion d’appartenance au moment de la visite. Le guichetier à ce titre ne peut pas décider de récupérer le casque qu’il trouve car il pénaliserait le visteur si celui-ci l’utilise encore
  • les références douces (soft reference, cf. SoftReference): une référence douce indique à la jvm que celle-ci peut supprimer l’objet si le besoin s’en fait sentir. Dans notre exemple cela s’apparenterait au fait de prêter le casque au lieu de le louer. Dans ce cas l’utilisation du casque n’est pas nominative, les casques sont laissés dans le musée (seulement) lorsque le guichetier n’a plus de casque disponible il peut récupérer sans remord l’ensemble des casques présents dans le musée partant du principe qu’ils n’appartienne pas aux viseurs.
  • les références faible (weak reference, cf. WeakReference): une référence faible permet de stocker une référence, mais ne permettent pas d’assurer a elle seule que l’objet stocké sera conservé en mémoire. Les références faibles permettent ainsi de faire dépendre la durée de vie d’un objet par rapport à la validité d’un autre objet. Dans notre exemple, lorsque le visiteur n’est plus présent, le casque est automatiquement récupéré par le guichetier.
  • les références fantômes (phantom reference, cf. PhantomReference): les références fantômes sont un peu particulières dans le sens où leur méthode get() renverra toujours null. En effet l’objectif des références fantômes est d’être avertis de la suppression complète d’un objet par le GC. En effet, contrairement aux WeakReference et SoftReference qui sont « invalidées » dès que commence le processus de libération les PhantomReferences ne sont ajoutées à la ReferenceQueue associée que lorsque l’objet référencé est complètement supprimé de la mémoire (cela permet d’éviter des « résurrections obscures » d’objets dans la méthode finalize()). Bon il va falloir un peu d’imagination pour intégrer ce type de référence dans notre exemple mais pour le moment elle ne nous sera pas utile.

Nous avons vu que le cache en SoftReference permettait de libérer les objets lorsque la mémoire sature. Cette solution est pratique car elle évite les OOME mais n’est pas optimale puisqu’elle génére une forte activité de Garbage Collection pour libérer les ressources.

3 ème optimisation: rendre les casques à la fin de la visite (retour des objets dans le pool)

Nous avons pu constater plus haut une ofrte activité du GC pour libérer les ressources du pool. Pour éviter ce problème il suffit de retourner les objets dans le pool en demandant aux visteurs de reposer leur casque sur le présentoir à la fin de la visite.

//On remet le casque dans le pool pour éviter un overhead cpu lors de la libération des SoftReference au moment du garbage
presentoire.returnObject(this.get(key).getCasque());

Conséquence

Le résultat est assez spectaculaire

On remarque que l’activité du garbage a considérablement baissé. L’activité CPU est maintenant mobilisée sur l’application.

Niveau mémoire, on remarque des GC réguliers sans saturation.

Au bout de 10 minutes, l’application a pu gérer près de 100 000 threads (soit autant de visteurs) contre 10 000 en utilisant uniquement les SoftReference.

4 ème optimisation: optimiser les options de la JVM

Ajoutons quelques options à la JVM:

-Xms1024m -Xmx1024m -Xmn512m -XX:+AggressiveOpts -XX:+UseParallelGC -XX:ParallelGCThreads=16

Et observons le résultat:

Le GC occupe à peine 1% de l’activité CPU.

Les minor GC sont très réguliers jusqu’à atteindre le seuil (ce qui declenchera un full GC)

Le nombre de threads traité passe de 100 000 à 211 124. Nous avons doublé le nombre de visiteurs dans le musée  (throughput).

Conclusion

L’objectif de l’article était de donner quelques pistes pour améliorer les performances d’une application. Dans un premier temps, nous avons vu qu’il était important de libérer les ressources pour éviter, au mieux, les garbages coûteux, au pire, les fuites mémoires. Un mécanisme de cache s’est révélé utile pour éviter la (re)création d’objets coûteux mais pouvait potentiellement engendrer une fuite mémoire en marquant une référence à un objet. Cela peut être évité avec l’utilisation d’un cache en SoftReference qui garantit la libération des objets en cas de saturation mémoire. Nous avons vu ensuite que ce seul mécanisme n’était pas suffisant pour garantir de bonnes performance et qu’il fallait nécessairement relâcher les objets afin d’éviter un overhead cpu lié au Garbage collector. Enfin un petit tunning de la JVM permet d’améliorer nos performances (elle a permis de doublé le nombre de visiteurs dans le musée). Nous verrons dans un prochain article comment améliorer encore les performances de notre application en recrutant de nouveaux guichetiers et en ouvrant de nouveaux musées…

Bibliographie

  • http://fr.wikipedia.org/wiki/Ressource_%28informatique%29
  • http://blog.developpez.com/adiguba/p2107/java/comprendre-les-references-en-java/
  • http://fr.wikipedia.org/wiki/Algorithmes_de_remplacement_des_lignes_de_cache#LRU_.28Least_Recently_Used.2



Leave a Reply