Anthony Patricio’s Blog

Decrypting another JPA benchmark

Posted in Persistance / Données by apatricio on octobre 23, 2012
Reviewing a benchmark is always an interesting task. Unfortunately it is often useless because benchmarks are rarely a good representation of real life conditions.
Last month, someone told me a new JPA implementation was trying to make noise. The author was making some buzz almost everywhere on social networks, commenting on popular articles and writing 2 articles claiming that this implementation was over 15 times faster than Hibernate. And here we are again. This sort of buzz happens every 2 years, it’s a cycle, there is nothing we can do about that.

The benchmark

Reading more about the implementation, the focus is made on fine grained tuning on every single line of code which is a really brave approach. However the way to announce « The New JPA Implementation That Runs Over 15 Times Faster… » is not a good way to promote the implementation. Anyway let’s have a closer look at the benchmark that allowed the author to conclude « 15 times faster ».
The benchmark is essentially composed of massive inserts, updates, deletes, queries by packet of 250 iterations. The domain model is composed of
  • 4 entities, yes 4 entities,
  • no inheritance, it sounds weird that a fabulous JPA implementation is not including more mapping configurations in its benchmark
  • eager fetching enabled (no subselect fetching for example),
  • cascading enabled,
  • no versioning

Last but not least, the benchmark targets an in-memory database, yes this is where it becomes suspicious.

15 times faster… faster what?

The big problem behind the title is that faster is not at all related to response time. The author tries to explain it, it’s related to CPU load, fair enough but could he clearly explain the consequences on a full system?
The fact is that the results are really hard to read and there is no relative view of the benchmark. You are told that Batoo might take 15 times less CPU time than Hibernate and what? The danger here is to mix internal APIs CPU load with the full persistence cycle composed of JPA internals, JDBC driver, networking, database engine. We all know, at least I hope so, that most of the time is spent on network and database so is this really important to focus _that much_ on JPA internals? Is hibernate that slow? Will Batoo solve your performance issues?
Let’s be extremely clear here, most of the performance issues users are facing are NOT because of JPA internals CPU load, the problems are almost always related to a slow database, too fat entity managers, inefficient queries. There are 3 general ways to solve these issues:
  • check your database !!! JPA is NOT going to fix a database internal problem
  • make a better use of the database and JPA
  • tune your own code, that requires various features and that is going to have a really big impact on the global performance numbers. In order to do so, the frameworks you are using must offer various features, this is exactly what Hibernate does: allows you to tune every single interaction your are triggering with the database.

Impacts: the Global view

I’m not going to rely on the timers provided by the benchmark. People are interested in checking how a system globally reacts when switching from one software option to another. I asked my friend and mate what tool a sysadmin would use to have a good view of the resources consumed by a process. « Easy » he told me, use the time command.
The time command runs the specified program command with the given arguments. When command finishes, time writes a message to standard output giving timing statistics about this program run. These statistics consist of (i) the elapsed real time between invocation and termination, (ii) the user CPU time (the sum of the tms_utime and tms_cutime values in a struct tms as returned by times(2)), and (iii) the system CPU time (the sum of the tms_stime and tms_cstime values in a struct tmsas returned by times(2)).
So I’m going to use time mvn test with the good format to grab the CPU numbers.
Some would say it’s not accurate. I would answer that it allows to measure a real relative impact on a global system not at the thread level.
We’ll see how much CPU% are consumed by the benchmark.
The good thing with mvn test is that we’ll also get the total time execution.

Well used Hibernate is ways faster than noob Hibernate 

In order to do some tuning, I’ll start with a low BENCHMARK_LENGTH iteration number, let’s say 5. That is 5 times each sub test, one subtest globally doing 250 iterations.
Since the in-memory db will be started by the root process, time command will also catch the DB load which we don’t want but we are just tuning the hibernate config for the moment.

Shoot 1: In memory derby, BENCHMARK_LENGTH 5

Hibernate 13 seconds 200% CPU
Batoo 9 seconds 210% CPU
Hibernate based setup _globally_ consumes less CPU but is also slower. Wait, I’ve said that this measure was including the database engine load. Having in mind that Batoo is claiming it consumes 15 times less CPU than hibernate what would that mean? That would mean that the JPA internal  CPU load is extremely low compared to the in-memory database CPU load … and still, we have an unsolvable equation here, we need more tests. Anyway, I already think know that CPU time consumed by JPA may not be a critical issue at all. But wait, small benchmark size and in memory database? This is irrelevant.
Let’s move to a local MySql instance, in-memory databases might be good for development, not for production. If I want a memory based config, I’ll got for Hibernate + Infinispan + a real database instance, not in-memory.
 

Shoot 2: Local MySql, BENCHMARK_LENGTH 10

Hibernate 44 seconds 64% CPU
Batoo 36 seconds 55% CPU
Batoo is the winner. Should we stop here? Certainly not.
 
Something is wrong. The good thing with hibernate, a _mature_ JPA implementation, is that it allows tons of tuning, being in the mapping, in the queries well everywhere. 
I remember 7 years ago, one guy at work was insulting Hibernate saying it was a piece a crap because he was observing memory issues and/or extremely long response times. One quick look at the hibernate documentation, 2 mapping parameter updates, more care of the entity manager (well Hibernate Session at that time), allowed to solve the memory issue and allowed a 10 000 faster execution. I promise it’s true, 10 000 times faster in response time.
A quick look at the documentation informs us that we should use the following global config parameters:
<property name="hibernate.jdbc.batch_size" value="50"/>
<property name="hibernate.order_update" value="true"/>
<property name="hibernate.id.new_generator_mappings" value="true"/>
More important: checking the INFO level logs, you’ll notice that the author of the benchmark left the auto commit mode. For someone who claims to be implementing the fastest JPA implementation ever, he should review the basics.
So let’s add
<property name="hibernate.connection.autocommit" value="false" />
and rerun the test with something bigger, I also raise the BENCHMARK_LENGTH to 100. 

Shoot 3: Local MySql, BENCHMARK_LENGTH 100

Hibernate 5 min 1 seconds 29% CPU
Batoo 4 min 52 seconds 22% CPU
 
29% versus 22% CPU… We are talking about a production server. In my case I used a 8 core CPUs 3 years old server. 
29% of one CPU … 
This benchmark is using 
  • 3.625% of my global System CPU capacity with hibernate
  • 2.75% of my global System CPU capacity with Batoo
It seems we are really far from the 15 times faster!

A more realistic environment

I won’t stop here, the author says that his motivation is to reduce cluster size thanks to his optimized implementation. So let’s say I don’t have 8 core, only 2. Here I might be interested in reducing my CPU load.

Who says cluster, also says remote database. And even without a cluster, production system will host the database in a different server.
So I set up a really fast LAN, excellent ping and bandwidth.
 
Here are the new results

Shoot 4: Remote MySql, BENCHMARK_LENGTH 100

Hibernate 16 min 30 seconds 6% CPU
Batoo 17 min 55 seconds 5% CPU
(Is it me or Hibernate is faster ?)
Which, for a 4 core CPUs system means
  • 1.5% of my global System CPU capacity with hibernate
  • 1.25% of my global System CPU capacity with Batoo
or for a 8 core CPUs system means
  • 0.75% of my global System CPU capacity with hibernate
  • 0.62% of my global System CPU capacity with Batoo
Yes Batoo is better in terms of CPU load but with such low levels, who cares? The persistence engine is always waiting for the database!
In the middleware side we have a very low CPU load (at least for the JPA part), what about the DB server side? We have one CPU over the 4 constantly above 80%. A second one above 40%.
 

Putting all of this together, Batoo MAY consume slightly less CPUthan Hibernate but the actual database load on these benchmarks far outweighs the load imposed by either Batoo or Hibernate.

And, since the internal ORM performance is a relatively minor issue, we should think about the products’ features.
Why is that important?
  • write stupid queries and you’ll get network, database and possibly memory issues (no matter if you use Hibernate or Batoo) -> query oriented advanced features are welcome
  • be idiot using the entity manager and you’ll observe slowness and memory issues (no matter if you use Hibernate or Batoo) -> get some training and certification, persistence is not something easy, you need SKILLS 
  • not talking about the DB slowness … if you do not understand RDBMS, you MUST work with a DBA, this is not an advice, this is a requirement.
  • if you are an expert in JPA, and your application is slow because of the database, you may have hit the RDBMS limitations. Give a try to Hibernate OGM with a NoSql supported database.

Shoot 5: Remote MySql, BENCHMARK_LENGTH 100, 2 concurrent run

I’m not going to provide the results but response times are impacted by 50%. CPU load is close to 2%

Conclusion: I don’t care these CPU optimizations I prefer FEATURES!

I honestly cannot imagine JPA being a cause of CPU problem in a real life scenarios. I’m also not forgetting that Hibernate is a 12 years old project. Talented people are working full time on it, the major version is 4, that means it has been heavily totally reworked 4 times. So that’s enough to understand that it is well written and that efforts are put in the most critical area: offering features.

I did online community support then official Red Hat support. I’ve seen query response times issues (1), memory issues (2) sometimes but I can’t remember about a CPU issue related to JPA internals.

To solve 1 and 2, you need skills but also features that help you tune your code. Like being able to use a specific database syntax (dialects), or  tune associations loading, I could continue on 300 pages here and I would recommend you simply consult the Hibernate documentation’s index.

Tagged with: , , ,

La certification, pourquoi faire ?

Posted in Actu / Anecdote by apatricio on juillet 29, 2011

Qui ne s’est jamais retrouvé dans une querelle d’experts sur une techno, sur le moyen d’implémenter un cas d’utilisation ou je ne sais quel aspect technique avancé. Lorsque l’on n’est pas initié soit même, et que l’on assiste à un tel débat, on est incapable de trancher, comme si l’on se trouvait au milieu d’une partie de poker face à deux gaillards prétendant tous deux avoir la main maximale, statistiquement proche de l’impossible, situation plutôt gênante lorsque l’on est client, chef de projet métier ou même employeur.

Pour toi développeur, technicien, archi avec la tête dans le guidon, ça peut être encore pire, tu peux te voir dé crédibilisé par un autre alors que tu es persuadé qu’il a tord, oui mais voilà, c’est un client, ou un concurrent qui est mieux vu. Le but ici n’est pas d’avoir raison mais bien de faire avancer le projet au mieux.

Que dire du directeur de marché, d’équipe ou responsable RH qui ne peut pas s’y connaître en technique (ce n’est pas son métier) qui va embaucher une personne selon le nombre de mots compliqués ou buzzables qu’il y aura sur son CV et qui, au premier projet livré, devra faire face à une montagne de bugs techniques plus bloquants les uns que les autres et à des réclamations en tout genre. La processus d’embauche mériterait un débat complet tellement il relève d’une problématique complexe: embaucher une personne compétente sur un domaine précis ça peut être très aléatoire. Pour tester un candidat il faut avoir soit même la compétence et/ou le test, le temps, bref ça coûte de l’argent tout ça et c’est une réelle stratégie de l’entreprise, un investissement, voire un risque si on embauche n’importe comment.

Il existe pourtant un moyen simple d’apporter des garantie de crédibilité: les certifications. Attention toutefois, il existe 2 types de certifications. La première s’apparente au code de la route, à savoir un QCM. A vrai dire, il n’y a que très peu d’intérêt à une telle formation, une bonne mémoire, la lecture d’un bouquin ou deux et hop, nous voilà certifiés. La seconde est plutôt semblable à l’examen de conduite, comment le candidat utilise t’il sa connaissance (et surtout son expérience) face à des cas réels ? Ces certifications là valent de l’or, aussi bien pour la personne certifiée que pour la personne qui va l’employer où faire appel à ses services. Pour l’employeur c’est la garantie de ne pas se tromper, pour le certifié c’est une garantie de pouvoir se faire respecter dans ses choix, ses développements, c’est un argument de taille, c’est aussi l’occasion de valoriser son CV et prétendre à un salaire plus élevé.

Chaque année des études sont faites pour lister les certifications les plus remarquables, généralement cela se traduit en % supplémentaire de salaire que l’on peut négocier si l’on a telle ou telle certification. Par exemple: http://www.reseaux-telecoms.net/fichiers/dossierpdf/la-grille-des-remunerations.jpg. Deux paramètres influent sur le résultat: la compétence testée bien sûr mais aussi indéniablement la qualité de l’examen. Etre certifié sur une technologie pointue, rare, très demandée mais via un examen QCM bateau est facile, ne sera pas reconnu et n’apportera rien à personne. Dans le tableau évoqué précédemment vous noterez la présence des certifications Red Hat sur l’OS entreprise RHEL. Depuis plusieurs années, les certifications Red Hat sont reconnues parmi les meilleures, souvent même au top 10 des certifications IT. Pourquoi?

Bien sûr car la compétences est recherchés mais aussi pour la qualité de l’examen lui même. Imaginez que l’on vous teste sur l’installation, le tuning, la réparation d’un système existant, sur des aspects utilisés par toutes les entreprises. Et bien c’est ce que propose Red Hat: que du cas réel, pas de piège. Tout son savoir faire sur la création de certification a été décliné sur les technologies JBoss. D’abord sur le serveur d’application, puis sur JPA, maintenant sur Seam 2, bientôt sur Portal, JPA 2, …

Conclusion, une certification s’est

  • pour le certifié,
    • valider ses compétences,
    • se faire reconnaître pour ses compétences,
    • valoriser son CV,
    • avoir plus de poids dans les débats et prise de décisions
  • pour l’employeur,
    • limiter les risques lors d’une embauche,
    • valider son investissement dans une formation pour un collaborateur,
    • une valeur ajoutée certaine chez le client dans le cadre de prestations, c’est décliner la garantie chez le client
En savoir plus sur les certifications Red Hat:
Si vous souhaitez formuler un souhait de certification, n’hésitez pas à me contacter.
Tagged with: , , , ,

Hibernate Search: la cerise sur le gâteau

Posted in Persistance / Données by apatricio on juillet 15, 2009

imagesSondage

Vous utilisez Hibernate? oui
Vous utilisez les annotations pour définir vos méta données? oui
Vous ne connaissez pas Hibernate Search? honte à vous!

R.O.I. HB-Search

Il y a des frameworks qui proposent des ROI assez impressionnants, Hibernate Search en fait partie.

Comme vous l’avez probablement deviné Hibernate Search permet d’implémenter un moteur de recherche fulltext efficace. Il s’appuie sur Lucene, Hibernate et les annotations.

Lucene est une technologie java d’indexation et de recherche très mature, aboutie et efficace. L’intérêt d’HB Search réside en son intégration avec Hibernate, il en résulte une facilité de mise en œuvre impressionnante.

Nous sommes régulièrement confronté au problème d’implémentation de moteur recherche dans nos applications d’entreprise. Plusieurs soucis:

  • niveau conception : nous sommes très forts pour proposer des formulaires de recherche ciblant toutes les données imaginables de nos modèles, allant parfois implémenter des formulaires de recherche comportant 36 champs. Le problème de ces formulaires étant leur inaccessibilité pour la ménagère de moins de 50 ans –> pour le grand public
  • niveau pertinence: nous savons être pertinents et précis sur des numériques, des dates, des booléens mais lorsque l’on nous demandes de prendre en compte les fautes d’orthographes ou les synonymes sur les chaînes de caractères, on se retrouve généralement démunie

Avec HB Search vous pouvez proposer à vos clients, pour un coût moindre, une ouverture vers un moteur fulltext user-friendly (typiquement champ de formulaire unique « à la google »). Ils seront agréablement surpris et n’auront aucun mal à élargir le spectre des spécifications pour consolider ce moteur.

L’exemple

Imaginez une classe Produit avec diverses propriétés de type String comme le libellé et la marque ou libellePrincipal et libelleSecondaire.

Vous souhaitez que la recherche cible ces deux propriétés.

Ci-dessous l’entité annotée comme vous en avez l’habitude:

@Entity
public class Produit {

	@Id
	private int codeProduit;

	private String libellePrincipal;

	private String libelleSecondaire;
 	...
}

Et effectuer une recherche, par exemple, via HQL:

javax.persistence.Query q =
	em.createQuery(
		"select produit " +
		"from Produit produit " +
		"where produit.libellePrincipal = :param");
q.setParameter("param", "café");
List results = q.getResultList();

Méta données

Que faut-il ajouter pour que l’entité et ses 2 champs soient puissent être ciblées par le moteur fulltext?

@Entity
@Indexed
public class Produit {

	@Id
	@DocumentId
	private int codeProduit;

	@Field
	private String libellePrincipal;

	@Field
	private String libelleSecondaire;
	...
}

@org.hibernate.search.annotations.Indexed stipule que l’entité annotée peut être indexée. Grâce à cette annotation, l’intégration Lucene/Hibernate est activée.

Parmi tant d’autres fonctionnalités gérées, l’indexation automatique vous simplifie la vie: lorsque vous agissez sur une entité de ce type, l’index lucene est automatiquement géré.

org.hibernate.search.annotations.Field déclare qu’une propriété est indexée. L’annotation propose divers leviers pour définir comment la propriété est indexée. Pour le moment appliquons le paramétrage par défaut.

Plutôt facile non? Attardons nous maintenant à l’aspect API

API de recherche

Avant de commencer, notez que des APIs équivalentes existent pour la session Hibernate.

Ici, plusieurs étapes sont nécessaires. Il faut d’abord obtenir un EntityManager fulltext, puis créer une requête Lucene. Enfin, la création d’une requête de recherche JPA depuis la requête Lucene nous permettra de retomber sur une API familière et pratique pour manipuler les entités retournées par la recherche.

Voici ce que ça donne:

// expression littérale de la requête Lucene</pre>
String searchQuery = "cafe~";

org.hibernate.search.jpa.FullTextEntityManager fullTextEm =
	Search.getFullTextEntityManager(entityManager);
SearchFactory sf = fullTextEm.getSearchFactory();

// Construction d'un QueryParser, définition du champ par défaut
// récupération de l'analyseur lié à l'entité
org.apache.lucene.queryParser.QueryParser parser = new QueryParser(
	"libellePrincipal",
	sf.getAnalyzer( Produit.class )
);

// construction de la requête lucene
org.apache.lucene.search.Query luceneQuery = parser.parse(searchQuery);

// création de la requête JPA fulltext
org.hibernate.search.jpa.FullTextQuery ftq =
	fullTextEm.createFullTextQuery(luceneQuery, Produit.class);

// exécution de la requête
List results = ftq.getResultList();

La subtilité ici réside en la recherche Lucene «~cafe ». Le tilde active une recherche par approximation. Ce type de recherche permet d’éviter les problèmes d’accent et de typo que l’on rencontre très souvent. De même si les utilisateurs saisissent des fautes d’orthographes, cette recherche s’en sortira facilement.

Bien plus de fonctionnalités

Cette article n’a pas l’ambition de couvrir toute la puissance d’Hibernate Search, simplement de démontrer la facilité et rapidité de mise en œuvre. L’exploitation d’un graph d’objet (et de ses associations) pour la recherche, la pondération de certains champs, la pertinence de la recherche sont possibles et faciles à utiliser.
Bien entendu, d’autres aspects doivent être pris en compte, notamment l’utilisation des analyseurs (approximation, phonétique, synonymes,…) et la gestion / maintenance des index.
Je vous recommande donc la lecture du guide de référence mais surtout du livre d’Emmanuel Bernard et John Griffin.

search

Darwin et Java, prochaine étape de l’évolution des applications

Posted in Actu / Anecdote by apatricio on juin 10, 2009

Année 2000, ahhh je me souviens le bon vieux temps où je crachais sur les EJBs et prônais le standalone efficace en développant une allergie hypodermique à tout ce qui se rapprochait d’un serveur d’app.

On développait avec JSP, struts et hibernate, le tout en stateless bien sûr, et on était contents, super contents. Grâce à struts, on passait 1h45 heure au lieu de 2 à concevoir notre JSP, la plugger à notre logique de navigation, coder nos validations de surface. Ahhh ce que c’était chia….

Tous ces aspects m’ont vite persuadé de me focaliser sur la persistance, le développement côté serveur, bref TOUT sauf les vues.

Quelques années ont passé, EJB3 est arrivé et j’ai retrouvé les beans session et le stateful et toute sa puissance. C’est Seam, un next generation framework imaginé par Gavin King, qui a d’ailleurs redoré le blason du stateful.

Les annotations nous ont délivrés de l’enfer du XML et struts, après tant de bons et loyaux services, a doucement passé la main à JSF et là… c’est le bonheur surtout avec RichFaces.

J’espère avoir assez de temps libre pour partager des avis/exemples sur toutes ces technologies, EJB3, JPA, Seam, RichFaces mais aussi Hibernate Search qui marquent un pas décisifs dans le monde des applications Java.

Comparer des pommes avec des oranges.

Posted in Actu / Anecdote by apatricio on mai 14, 2009

Comparer des pommes avec des oranges est-il si stupide que cela?

Cette phrase amplement utilisée pour signifier que deux sujets ne peuvent être comparés entre eux est-elle réellement pertinente? Pour ma part, les affirmations absolues n’ont pas leur place dans ce monde, tout est question de contexte. Il est ainsi intéressant de comparer des pommes avec des oranges dans différents contextes comme la nutrition ou le goût.

Il y a quelques semaines, j’ai vérifié et corrigé une application destinée à comparer des outils de persistance d’objets java. Il y avait trois produits: un ORM mystérieux couplé à une BDD relationnelle et 2 Bases Objet.

Cette expérience fût l’objet de longs débats plus sémantiques et méthodologiques que techniques entre l’étudiant (préparant son doctorat) et moi-même qui devais valider son code et ses mappings. Au delà des résultats des tests, ce qui m’intéressait était de permettre à l’étudiant de prendre du recul et si cela ressortait dans sa dissertation alors mon objectif serait atteint. Pour lui faire entendre raison il m’a fallu ruser et utiliser une autre comparaison, place aux engins motorisés.

S’il fallait comparer une moto, une formule 1 et un 4×4, beaucoup de questions me viendraient à l’esprit, et malheureusement l’étudiant en question a foncé tête baissée dans le code sans se les poser:

Qui pilote les engins?

  • une personne sans permis
  • une personne avec une expérience significative pour chacun des véhicules

L’expérience semble nécessaire, malheureusement l’étudiant n’avait pas de réelle expérience pratique et, oh mon dieu!, n’a fait que survoler les guides de référence. Résultats: des erreurs graves un peu partout. Après une première passe sur ces erreurs grossières, les performances étaient déjà 2 à 3 fois meilleures.

Où tester les véhicules ?

  • sur un terrain neutre, très difficile à définir ?
  • sur un circuit ?

Tout dépend de l’objectif du test et de l’utilisation attendue par les destinataires du test. Admettons dans notre cas un test sur piste pour tester la vitesse pure des engins, en ligne droite, courbe, blah blah. Ici, le modèle Java utilisé avait du sens, tous les types d’associations étaient présents, ainsi que l’héritage. Petite chose irritante cependant, au lieu de se pencher sur un listing des fonctionnalités de chaque outil, le test cible les performances dans des cas d’utilisation de type batch. Les connaisseurs comprendront de suite le soucis: on ne choisit pas ce type d’outil pour faire des insertions/extractions massives de données mais bien pour permettre de concevoir une application qui nécessite une modélisation orientée objet.

Autorisation de prendre des raccourcis ?

  • Seul le 4×4 est capable de couper à travers champ pour le test
  • Alors que la formule 1 n’y survivrait pas.

Dans ce cas il semble nécessaire d’expliquer que le 4×4 dispose d’un avantage certains, non négligeable sur ces concurrents.

Si tous les véhicules peuvent prendre le raccourcis, la question se pose aussi, tout dépend de l’exhaustivité et de la lisibilité souhaitées. Ainsi un des deux fournisseurs de base objet a essayé de forcer l’étudiant à configurer son architecture autour du mode « stockage in-memory ». Oui mais voilà, des BDD relationnelles in-memory existent aussi, permettant des performances virtuellement explosives. L’étudiant a fait preuve d’impartialité et n’a pas accepté cette requête.

Quels pneus utiliser ?

Certainement l’aspect pouvant impacter le plus les résultats. Utiliser des pneus en mousse pour la formule 1 invaliderait l’ensemble des tests. De nombreux points techniques relevant de ce domaine ont du être rectifiés dans le code. Dans le cas ORM + BDD relationnelle, ce n’est pas l’ORM qui est testé, c’est bien l’ensemble. Il semble donc intéressant de comparer les résultats avec 2 BDD relationnelles différentes.

Épilogue: après différentes corrections et optimisations de bon sens (pas de triche genre cache de second niveau expressément tunné pour les cas d’utilisation), tous les tests (une 15ene) tournent entre 4 fois et 40 (oui 40!!!) fois plus vite.

Conclusion: même si la solution ORM/BDD relationnelle s’en sort une fois de plus grandie, je persiste à penser que, comme la quasi totalité des comparatifs axés uniquement sur la performance, ce test est un nième benchmark peu utile. Posez-vous toujours et en priorité la question de votre contexte d’applications d’entreprise. Pensez d’abord fonctionnalités, pérennité et réputation des produits. Si un produit est réputé et massivement utilisé, c’est déjà un gage de sécurité.