Performances Hibernate, JDBC et Java sur un ensemble de résultats moyen et grand

Problème

Nous essayons d’optimiser notre application de serveur de données. Il stocke les stocks et les devis sur une firebase database mysql. Et nous ne sums pas satisfaits des performances de récupération.

Le contexte

- database - table stock : around 500 lines - table quote : 3 000 000 to 10 000 000 lines - one-to-many association : one stock owns n quotes - fetching around 1000 quotes per request - there is an index on (stockId,date) in the quote table - no cache, because in production, querys are always different - Hibernate 3 - mysql 5.5 - Java 6 - JDBC mysql Connector 5.1.13 - c3p0 pooling 

Tests et résultats

Protocole

  • Les temps d’exécution sur le serveur mysql sont obtenus en exécutant les requêtes SQL générées dans la ligne de commande mysql.
  • Le serveur est dans un contexte de test: aucune autre lecture de firebase database, aucune écriture de firebase database
  • Nous récupérons 857 devis pour le stock AAPL

Cas 1: Hibernate avec association

Ceci remplit notre object stock avec un object 857 quotes (tout est correctement mappé dans hibernate.xml)

 session.enableFilter("after").setParameter("after", 1322910573000L); Stock stock = (Stock) session.createCriteria(Stock.class). add(Ressortingctions.eq("stockId", stockId)). setFetchMode("quotes", FetchMode.JOIN).uniqueResult(); 

SQL généré:

 SELECT this_.stockId AS stockId1_1_, this_.symbol AS symbol1_1_, this_.name AS name1_1_, quotes2_.stockId AS stockId1_3_, quotes2_.quoteId AS quoteId3_, quotes2_.quoteId AS quoteId0_0_, quotes2_.value AS value0_0_, quotes2_.stockId AS stockId0_0_, quotes2_.volume AS volume0_0_, quotes2_.quality AS quality0_0_, quotes2_.date AS date0_0_, quotes2_.createdDate AS createdD7_0_0_, quotes2_.fetcher AS fetcher0_0_ FROM stock this_ LEFT OUTER JOIN quote quotes2_ ON this_.stockId=quotes2_.stockId AND quotes2_.date > 1322910573000 WHERE this_.stockId='AAPL' ORDER BY quotes2_.date ASC 

Résultats :

  • Temps d’exécution sur le serveur mysql: ~ 10 ms
  • Temps d’exécution en Java: ~ 400ms

Cas 2: Hibernate sans association sans HQL

En songeant à augmenter les performances, nous avons utilisé ce code qui ne récupère que les objects de guillemets et nous les ajoutons manuellement à un stock (afin que nous ne récupérions pas des informations répétées sur le stock pour chaque ligne). Nous avons utilisé createSQLQuery pour minimiser les effets des alias et des erreurs HQL.

 Ssortingng filter = " AND q.date>1322910573000"; filter += " ORDER BY q.date DESC"; Stock stock = new Stock(stockId); stock.addQuotes((ArrayList) session.createSQLQuery("select * from quote q where stockId='" + stockId + "' " + filter).addEntity(Quote.class).list()); 

SQL généré:

 SELECT * FROM quote q WHERE stockId='AAPL' AND q.date>1322910573000 ORDER BY q.date ASC 

Résultats :

  • Temps d’exécution sur le serveur mysql: ~ 10 ms
  • Temps d’exécution en Java: ~ 370ms

Cas 3: JDBC sans Hibernate

 Ssortingng filter = " AND q.date>1322910573000"; filter += " ORDER BY q.date DESC"; Stock stock = new Stock(stockId); Connection conn = SimpleJDBC.getConnection(); Statement stmt = conn.createStatement(); ResultSet rs = stmt.executeQuery("select * from quote q where stockId='" + stockId + "' " + filter); while(rs.next()) { stock.addQuote(new Quote(rs.getInt("volume"), rs.getLong("date"), rs.getFloat("value"), rs.getByte("fetcher"))); } stmt.close(); conn.close(); 

Résultats :

  • Temps d’exécution sur le serveur mysql: ~ 10 ms
  • Temps d’exécution en Java: ~ 100ms

Nos compréhensions

  • Le pilote JDBC est commun à tous les cas
  • Il y a un coût de temps fondamental dans la conduite JDBC
  • Avec des requêtes SQL similaires, Hibernate consacre plus de temps que le code JDBC pur à la conversion de jeux de résultats en objects.
  • Hibernate createCriteria, createSQLQuery ou createQuery ont le même coût en temps
  • Dans la production, où nous avons beaucoup d’écriture en même temps, la solution JDBC pure semblait être plus lente que la solution Hibernate (peut-être parce que nos solutions JDBC n’étaient pas regroupées)
  • Mysql sage, le serveur semble se comporter très bien, et le coût en temps est très acceptable

Nos questions

  • Existe-t-il un moyen d’optimiser les performances du pilote JDBC?
  • Et Hibernate bénéficiera-t-il de cette optimisation?
  • Existe-t-il un moyen d’optimiser les performances d’Hibernate lors de la conversion d’ensembles de résultats?
  • Sommes-nous confrontés à quelque chose de non réglable en raison de la gestion fondamentale des objects et de la mémoire Java?
  • Sommes-nous manquons un point, sums-nous stupides et tout cela est vain?
  • Sommes-nous français? Oui.

Votre aide est la bienvenue.

Pouvez-vous faire un test de fumée avec la requête simple possible comme:

 SELECT current_timestamp() 

ou

 SELECT 1 + 1 

Cela vous indiquera quelle est la surcharge réelle du pilote JDBC. En outre, il n’est pas clair si les deux tests sont effectués à partir de la même machine.

Existe-t-il un moyen d’optimiser les performances du pilote JDBC?

Exécutez la même requête plusieurs milliers de fois en Java. La JVM a besoin de temps pour s’échauffer (chargement de classe, JIT). De plus, je suppose que SimpleJDBC.getConnection() utilise le regroupement de connexions C3P0 – le coût de l’établissement d’une connexion est assez élevé, de sorte que la première exécution risque d’être lente.

Préférez également les requêtes nommées aux requêtes ad hoc ou aux requêtes de critères.

Et Hibernate bénéficiera-t-il de cette optimisation?

Hibernate est un framework très complexe. Comme vous pouvez le constater, cela prend 75% du temps d’exécution total par rapport au JDBC brut. Si vous avez besoin d’ORM brut (pas de chargement paresseux, de vérification en profondeur , de mise en cache avancée), envisagez mybatis . Ou peut-être même JdbcTemplate avec RowMapper abstraction de RowMapper .

Existe-t-il un moyen d’optimiser les performances d’Hibernate lors de la conversion des ensembles de résultats?

Pas vraiment. Consultez le chapitre 19. Améliorer les performances dans la documentation d’Hibernate. Il y a beaucoup de reflection qui se passe là-bas + génération de classe. Une fois encore, Hibernate pourrait ne pas être la meilleure solution lorsque vous souhaitez extraire chaque milliseconde de votre firebase database.

Cependant, c’est un bon choix lorsque vous souhaitez augmenter l’expérience utilisateur globale en raison d’une prise en charge étendue de la mise en cache. Découvrez à nouveau le document de performance . Il parle surtout de la mise en cache. Il existe un cache de premier niveau, un cache de second niveau, un cache de requêtes … C’est l’endroit où Hibernate peut en fait surpasser les performances de JDBC simple – il peut mettre en cache beaucoup de choses d’une manière que vous ne pouvez même pas imaginer. D’autre part, une configuration de cache médiocre entraînerait une configuration encore plus lente.

Check out: Caching avec Hibernate + Spring – Quelques questions!

Sommes-nous confrontés à quelque chose de non réglable en raison de la gestion fondamentale des objects et de la mémoire Java?

La JVM (particulièrement dans la configuration du serveur ) est assez rapide. La création d’objects sur le tas est aussi rapide que sur la stack en C, par exemple, la récupération de place a été grandement optimisée. Je ne pense pas que la version Java exécutant JDBC en clair serait beaucoup plus lente par rapport à une connexion plus native. C’est pourquoi j’ai suggéré quelques améliorations à votre repère.

Est-ce qu’il nous manque un point, sums-nous stupides et tout cela est vain?

Je crois que JDBC est un bon choix si la performance est votre plus gros problème. Java a été utilisé avec succès dans de nombreuses applications nécessitant beaucoup de bases de données.