Publié par
Il y a 8 années · 6 minutes · Java / JEE, Performance

Les caches, ces armes à manipuler avec précaution

Il n’y a rien de plus simple qu’un framework de cache … enfin il paraît. La majorité d’entre eux fonctionne sur un même principe élémentaire, celui que vous décideriez probablement d’adopter si l’on vous demandait d’en développer un : une bonne vieille HashMap ! Logique puisqu’il ne s’agit finalement que d’organiser manuellement un index d’objets vous permettant d’accéder le plus rapidement possible à vos données plutôt que d’aller les piocher dans des sources de données plus gourmandes. Et pourtant, leur utilisation est soumise à un certain nombre de cas d’utilisations offrant de belles occasions de se planter en beauté ! Cet article décrit quelques-uns de ces problèmes, plus précisément les problèmes liés au cache L2 hibernate et caches distribués.

Incohérences des données, est-ce un problème ?

C’est probablement l’une des phobies les plus répandues dans le monde informatique, partagées aussi bien par les développeurs, les architectes que les clients : l’incohérence des données. Phobie compréhensible puisque les données représentent la valeur ajoutée même de votre métier, sans elles, pas de transactions donc pas de rentrées d’argent. Et pourtant, nous constatons trop souvent que cette phobie est le point de départ de bien des problèmes. Combien d’usines à Shadocks ont été montées à tort ? Combien de fausses bonnes idées ont coulé des projets ? Ceci vient souvent d’une mauvaise interprétation des besoins du client : la perte ou l’incohérence des données est dans bien des cas acceptables. Alors, pourquoi s’obstiner à se rajouter systématiquement des aiguilles dans la poupée vaudou de votre projet.

En résumé, les problématiques liées à la cohérence des données d’un cache distribué sont les mêmes que celles d’une source de données partagée telle qu’une base de données : mises à jour concurrentes, échecs de mise à jour, fiabilité des contraintes… L’ajout d’un cache distribué n’est donc pas anodin, vous rajoutez avec lui une complexité complémentaire à votre système.

Stratégies de notifications, mises à jour synchrones ou asynchrones

En cas de modification locale d’un élément d’un des caches d’un cluster, deux solutions s’offrent à vous pour répercuter celle-ci sur les différents nœuds de votre cluster :

  • par copie, vous envoyez un message contenant le nouvel objet (ou un différentiel de celui-ci)
  • par invalidation, vous envoyez une notification demandant aux autres membres du cluster d’effacer tout ou partie de leurs caches liés à cet élément.

Si vous êtes le seul à connaître l’existence d’un élément (i.e. l’objet a été créé localement et n’est directement disponible dans aucune source de données), vous n’avez d’autre choix que de propager vos modifications par copie. Dans tous les autres cas, c’est à vous de choisir la meilleure option en fonction du type de vos données.

Il est souvent recommandé de privilégier les mises à jour par copie. Celles-ci évitent le rechargement complet des caches de votre cluster. Suivant le type de données, vider entièrement un cache peut s’avérer désastreux : l’invalidation peut provoquer une demande massive d’objets simultanée à partir des différents nœuds d’un cluster. Les caches regroupent souvent des données homogènes dont les objets sont liés les uns aux autres. Il est facile de noyer la base sous une pluie de requêtes gourmandes en ressources et simultanée. Le but premier de votre cache distribué n’était pas de limiter les accès à la base de données ? Cet effet est encore amplifié par la fréquence de mise à jour de vos données. Dernier conseil, une mise à jour par copie perd de son intérêt lorsque que la durée de vie maximale des objets de vos caches est faible, surveillez votre time-to-live.

Pourtant, la mise à jour par copie n’est pas sans risque, vous exposez vos objets à de potentielles désynchronisations d’états entre les différents nœuds de votre cluster. En effet, la majorité des mécanismes de mise à jour par copie n’assurent pas la réussite de celle-ci. Par exemple, si une micro coupure réseau intervient lors de la demande d’une mise à jour d’un objet entre 2 serveurs, il est courant de constater qu’au final les deux objets ne soient pas identiques. (il en est d’ailleurs le même pour les mécanismes d’invalidation). Si les mécanismes de copies asynchrones sont rapides, ils ne garantissent pas la bonne exécution de leur service. Au contraire, d’autres mécanismes synchrones le permettent, mais à quel prix pour vos utilisateurs ? Êtes-vous prêt à stopper l’activité de votre serveur en attendant d’obtenir les accusés de réception de l’ensemble des nœuds de votre cluster.

Mise à jour Asynchrone VS Synchrone Mise à jour par copie VS invalidation
Réponse rapide contre cohérence des données Multiplicité des notifications contre rechargement massif de la base

Attention au Cache L2 Hibernate

Lorsque l’on sort le Cache L2 de la trousse à outils, on en attend souvent un résultat miraculeux. Pourtant, si cette solution est simple à mettre en place, elle n’est pas magique pour autant. Il est donc important d’en connaître les principales caractéristiques.

Les manipulations de données des caches s’effectuent le plus souvent par l’ajout ou la récupération d’un seul et unique objet. Les caches proposent tous une manipulation simple par le biais de ces simples méthodes : cache.put( Element element ) pour l’ajout d’un objet dans le cache et cache.get ( Object key ) pour la récupération. Et ce sont ces mêmes méthodes que le cache L2 Hibernate va utiliser. Ainsi, la mise à jour du cache, quelque soit son implémentation, va passer par une mise à jour objet par objet. Ceci s’avère peu performant lorsque le cache doit mettre à jour de volumineuses listes d’objets.

Vous l’avez compris, les effets peuvent vite devenir catastrophiques en cas de mauvaise utilisation des associations Hibernate. Il est parfois pertinent d’utiliser un système de cache externe sans utiliser le cache L2 Hibernate. Afin d’appuyer cela, je vous invite à lire le billet de blog suivant dans lequel l’auteur effectue une comparaison des performances en fonction des différentes utilisations du cache dont voici les résultats :
Pour 50 objets chargés contenant chacun 2 relations One-To-Many

  • Hibernate Simple sans cache : 192 secondes
  • Hibernate avec un cache L2 basé sur Ehcache : 60 secondes
  • Accès direct à Ehcache, utilisation d’Hibernate pour les objets non trouvés dans le cache : 20 secondes
  • Accès direct à Ehcache, utilisation d’Hibernate pour les objets non trouvés dans le cache et optimisation du nombre d’ouvertures de sessions : 1 seconde

Le caching, ce n’est pas si simple

Ce billet n’avait pas pour but de lister l’ensemble des problématiques liées aux caches distribués, j’en suis d’ailleurs incapable ; mais juste de mettre en lumière les principales problématiques liées au caching. Qu’il soit distribué ou non, la mise en place d’un cache ne doit pas s’effectuer à la légère. Si elle est censée améliorer les performances, elle en augmente grandement l’entropie de votre système. C’est pourquoi il est nécessaire d’avoir les moyens d’estimer le gain de performances lié à la mise en place de vos caches. La majorité des API proposent pour cela de la consultation des statistiques via JMX. Compter le nombre de hits (éléments tirés du cache) et de misses (éléments non trouvés dans la cache) vous permettra d’évaluer facilement le rendement de celui-ci.

Si vous ne deviez retenir qu’une chose, un cache c’est comme un pare-chocs de voiture pour une base de données :

  • Mal placé, ça sert à rien
  • Mal configuré, c’est inefficace

Attention aux accidents :-)

Erwan Alliaume
Passionné par les technologies Java/JEE depuis sa sortie de l'EPITA, Erwan Alliaume est aujourd'hui consultant chez Xebia. Il intervient depuis 2 ans sur des missions de développement et d'architecture pour l’un des leaders mondiaux de la production et de la distribution de contenus multimédia. Expert technique polyvalent, Erwan a été amené très tôt à prendre des responsabilités sur des projets de taille significative. Il a notamment développé des compétences clé dans la mise en place de socle de développement, la métrologie et les audits de performance et de qualité. Erwan participe également activement au blog Xebia ou il traite aussi bien de sujets d’actualités que de problématiques techniques.

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *