Actualités

[08/09/2017] Breaking news ! Smile décroche le label Happy Trainees 2018

Après le label HappyAtWork, Smile s’offre celui décerné par ses stagiaires et alternants !

[21/07/2017] Smile lance les premiers vélos solaires connectés à l’occasion du Sun Trip Tour 2017

Smile, leader des solutions IoT et open source, confirme sa solide expertise sur le marché de l’embarqué en participant activement à la course de vélos solaires du Sun Trip Tour.

[03/07/2017] Smile remporte le Drupagora d'Or 2017 du meilleur site e-commerce

Le vendredi 30 juin, la 3ème édition des Drupagora d'Or s'est déroulée à Paris.

Toutes les actualités picto
       

Vous avez besoin de mettre à jour votre Lecteur Flash Flash 7

Guillemet ouvrant l'actualité des solutions
et des technologies open source Guillemet fermant
picto

Optimisation d'Hibernate pour les batchs d'importation de données

Les batchs d'import de données sont très souvent des points critiques pour une application. Ils sont régulièrement sujets à de fortes contraintes de performance dues à leur importance (sans import, pas de données et donc pas de site) et au volume qu'ils manipulent (s'il nous faut un système d'import de données, c'est qu'il y a trop de données pour qu'elles soient traitées à la main).

Hibernate est un framework ORM (Object Relational Mapping) qui facilite grandement le développement d'applications Java / J2EE liées à une base de données. Ce dernier met en place des systèmes qui permettent de fortement s'abstraire de la base de données lors du développement Java. Cependant, ceci n'est que la partie émergée de l'iceberg qu'est Hibernate. J'ai régulièrement entendu dire qu'Hibernate ralentit les batchs et qu'il faut à tout prix l'éviter lorsque l'on travaille sur un batch. Cette réaction est majoritairement due à une méconnaissance du framework ou a une mauvaise utilisation de celui-ci. Le simple fait que vous ayez développé des entités Hibernate pour le reste de votre projet et une raison suffisante en soit pour justifier l'utilisation d'Hibernate dans votre batch. Se passer d'Hibernate au profit de SQL par soucis de performance est du même acabit que de se passer de Java au profit du C par soucis de performance.

Un certain nombre de bonnes pratiques sont donc à mettre en place afin qu'Hibernate reste un atout lors de la conception de votre batch.

Que vous développiez un batch, une application légère, une application lourde ou le programme de pilote automatique d'un Airbus, commencez toujours par écrire votre programme en respectant le principe KISS (Keep It Simple, Stupid). Une optimisation prématurée est dangereuse :

  • vous ne saurez pas si vos optimisations ont porté leurs fruits ou non
  • vous aurez des maux de tête avant même que votre programme ait été lancé une première fois
  • vous ferez souvent plus de mal que de bien

Ceci se traduit dans notre cas par l'utilisation tel quel d'Hibernate : utilisez les entités et les DAOs que vous avez déjà écrits ! Faites un programme capable d'importer au moins deux éléments. Les performances ne sont pas encore votre problème.

Un premier point d'optimisation est le nombre de requètes envoyées à la base de données. Lorsque vous travaillez sur une même entité, faites tout le travail que vous pouvez faire avec avant de le synchroniser avec la base de données. Vous réduirez ainsi significativement le nombre d'updates que vous enverrez à la base.

Un autre axe d'amélioration est la segmentation de votre transaction. Dans un premier temps, il y a fort à parier que vous n'ayez ouvert qu'une seule transaction, fait tous vos traitements puis fermé cette transaction. Il y a ici un juste milieu à trouver. En effet, si votre transaction est volumineuse, celle-ci doit être maintenue du côté de la base de données. Cela ne coûte rien sur des transactions à volume moyen mais peut devenir coûteux sur des transactions importantes. En effet, ces transactions sont conservées en RAM et si vous modifiez 50 % de votre base de données lors d'une transaction, vous êtes en train de monter 50 % de votre base de données en RAM. Si votre machine ne possède pas assez de RAM alors elle va commencer à SWAPer (écrire sur le disque dur) ce qui est la pire des choses que vous puissiez imaginer en terme de performances. Il est donc conseillé d'essayer de découper son import par lots (par exemple mille entrées par mille entrées) et de créer une nouvelle transaction pour chacun de ces lots. Un autre avantage à cela est que les autres utilisateurs de la base de données verront les nouvelles valeurs arriver au fur et à mesure et auront des données plus régulièrement à jour.

Il est fréquent de devoir faire des vérifications avec ce qui est déjà présent en base de données lors de l'import de nouvelles données. Lorsque vous écrivez un batch, il ne faut remonter que ce dont vous avez besoin et avec le moins de requêtes possible. Ceci est une bonne pratique générale. Que vous utilisiez Hibernate ou non, elle doit être mise en place. Si vous n'avez besoin que d'un seul champ dans une table qui en contient 10, il ne faut remonter que le champ dont vous avez besoin. Dans Hibernate, un seul cas pose problème : quand il s'agit de lier deux entités entre elles. En effet, pour pouvoir lier une entité avec une autre, avec Hibernate, il vous faut les deux entités ce qui implique le chargement de toutes les données non lazy. Dans ce cas là, à cette étape, conservez le comportement par défaut d'Hibernate. En effet, lors du chargement d'entités, Hibernate utilise un cache. Dans une grande majorité des cas, ce cache compensera cette perte de performance. Pour les autres cas, il vous est toujours possible de déléguer la mise en relation d'entités à des requêtes update gérées par Hibernate. Vous ne chargerez alors que les ids des entités et votre requête SQL se résumera à une simple

 UPDATE FROM MyTable SET foreignKey = :idEntitéeLiée WHERE id = :idEntitée

Comme dit précédemment, Hibernate utilise un cache de session pour les entités qu'il gère. Ceci a deux buts bien précis :

  • améliorer les performances : si vous faites deux fois de suite un get(XX), Hibernate n’exécutera qu'un seul select et vous renverra l'objet qu'il possède en cache lors du second appel.
  • traquer les objets qui ont été modifiés : si vous modifiez un objet géré par Hibernate et que vous fermez la session Hibernate, celui-ci synchronisera sa représentation de l'objet avec la base de données ce qui impliquera un update reflétant vos modifications.

Ces deux points font toute la puissance d'Hibernate ce qui justifie la présence et l'utilisation de ce cache. Cependant, il est important de connaître son existence afin de le gérer correctement. En effet, pour les points présentés précédemment, Hibernate n'a d'autres moyens que de vérifier tout son cache pour savoir ce qu'il doit faire. Si votre cache contient un million d'entités inutiles, il les vérifiera tout de même et donc perdra du temps. Très régulièrement, vous ne manipulerez un même objet qu'un court laps de temps lors de votre batch. Inutile donc de le conserver en cache. Hibernate propose la méthode evict qui vous permet de supprimer du cache un objet. Encore mieux, il peut arriver que votre batch arrive à une étape où tous les objets précédemment chargés ne seront plus utilisés, c'est notamment le cas lors d'un import par lots. Hibernate propose alors la méthode clear qui permet de vider tout le cache et ainsi repartir à zéro.

Enfin, un dernière optimisation peut être ajoutée afin de réduire le nombre d'allers retours avec la base de données : utiliser le mode bulk d'Hibernate. En effet, votre batch d'import va globalement se résumer à une succession d'inserts et/ou d'updates. Faire 1000 inserts coûte presque 1000 fois plus cher que de faire un seul insert contenant les 1000 données. En effet, cela veut dire que vous subissez 1000 fois plus la latence réseau, que vous passez 1000 fois le parseur de votre base de données et qu'elle mettra 1000 fois à jour ses index là où tout cela ne peut être fait qu'une seule fois. De plus, passer en mode bulk est une fonctionnalité d'Hibernate et n'a aucune conséquence sur votre code alors pourquoi s'en priver ?

Grâce à ces pistes vous pourrez continuer à utiliser Hibernate (et le code que vous avez déjà écrit pour votre application) au sein de votre batch d'import tout en ayant des performances proches de celles d'un batch écrit uniquement en SQL.

Xavier DETANT
picto

Commentaires

Soyez la premiere personne à ajouter un commentaire sur cet article.
Ecrire un nouveau commentaire