viernes, 26 de diciembre de 2008

JPA implementations comparison: Hibernate, Toplink Essentials, Openjpa, Eclipselink


Summary

This article is a response to the lack of information on the net about the performance differences among the 4 most well known Java Persistence API (JPA) implementations: Toplink Essentials, EclipseLink, Hibernate and OpenJPA.
Besides results and conclusions, the full test code is also available in case you want to repeat the test yourself.
I wrote a relatively simple program which executes some queries and inserts in a MySQL database through JPA. Four fixed-time tests were done with exactly the same code, just changing the JPA implementation library and the persistence.xml. I monitored the resources used by the JVM and counted the inserts and queries executed during the tests. Finally, I show here my conclusions and also the results of these tests, so that you can draw your own. I consider the differences found among the different implementations truly relevant.
For the tests performed for this article, nothing except JPA was used. No web pages, no web or application server. Just java threads, JPA and MySQL. I give more details in the next sections.
Note: In case you are using JPA with Axis and/or the Google Web Toolkit (GWT), this other article focused on working with JPA, Axis and GWT could be of interest for you.

Description of hardware and software
The tests have been done in an Acer Extensa 5620G laptop, with a pentium Core 2 Duo T5250 Processor with 2 Gb Ram DDR2, being monitored by a standard PC.
For the tests I have used the following software:
  • Ubuntu 8.10 Intrepid Ibex
  • MySQL database, version 5.0 (installed from the official Ubuntu repositories).
  • Java Virtual Machine 1.6
  • Driver jdbc for MySQL 5.1.
  • Eclipse Ganymede
  • The employees database example for MySQL, courtesy of Patrick Crews and Giuseppe Maxia (url below in the references section)
  • JConsole for resources monitoring
  • GIMP 2 to capture screens
The database and the JVM were running in the Acer machine. But both JConsole and GIMP were executed in a PC (also equiped with Ubuntu 8.10) connected via tcp/ip to the test machine. I did it so that I did not overload the machine running the tests.
Versions of the JPA implementations tested:
  • Hibernate EntityManager and Annotations 3.4.0
  • Toplink Essentials version 2 build 41
  • Openjpa 1.2.0
  • Eclipselink 1.0.2
Description of code and tests
The code developed for the tests is available to download here. All you have to do is import the zip file in Eclipse. You will need at least one of the JPA implementation libraries. You can download them from the urls in the references section below.
The code is made up of two type of threads, one for inserting and one for querying. each of them containing a loop.
Inserting thread loop gets an arbitrary employee and makes a copy of him/her, letting MySQL generate a new emp_no. This was the only modification I did to the employees database: the emp_no is auto-generated.
Querying thread loop executes these queries in sequence:
  • A query returning the number of female employees.
  • A query returning the number of male employees.
  • A query returning all employees hired since an arbitray date.
  • A query returning all employees born after an arbitrary date.
  • A query returning all women who have earned more than an arbitrary salary.
I have also created an independent class JPAManager, which is in charge of creating the static EntityManagerFactory and the EntityManager for each of the threads. You have the details of that class in this other article focused on the problems derived from sharing EntityManager among different objects.This is the starting sequence:
  1. When the program starts, it waits 2 minutes for the monitoring infraestructure to be ready (connecting JConsole to the JVM, basically).
  2. It then starts 2 of the so-called inserting threads. I start the inserting threads before the querying threads trying that the queries do not always return the same (which will eventually happen, anyway).
  3. After starting the inserting threads, the program starts running 18 of the querying threads, inserting a pause of 10 seconds before starting next. This is so that they do not execute the same query at the same time.
  4. The program runs the threads for 30 minutes. After that time, it sends a stop signal to the threads, which will safely make them stop after the next inserting or querying round. The main program waits 15 minutes for the threads to stop and the jvm memory to stabilize.
  5. Before stopping, the threads provide information about the number of inserts/queries they have executed.
The only change from test to test was the JPA implementation library and the persistence.xml. It is important to notice that the persistence.xml was left by default for each of the implementations, omitting on purpose any kind optimization that the implementation could accept.

Before every test, the inserted records were deleted. In this way, every implementation started with the database exactly in the same situation.

Results
These were the results of the tests per JPA implementation library. Notice that the time was fixed: 30 minutes running.

Number of queries+inserts executedNumber of queries executedNumber of inserts executedMax mem occupied during the test(Mb)Mem occupied after the test(Mb)
OpenJPA3928
353039896
61
Hibernate12687
3080960713079
Toplink Essentials
5720
374019805525
Eclipselink
5874
373521395725

The maximum memory occupied is the maximum amount that the JVM reserved during the test.
The memory occupied after the test is the amount of memory that remained reserved after finishing the test.
I have emphasized the highest and lowest values for each of the columns.
You can see this graphically in the following images showing the data monitored during the different tests.

OpenJPA monitoring data

Hibernate monitoring data

Toplink Essentials monitoring data

Eclipselink monitoring data

Conclusions

My intention is that anyone can draw their own conclusions looking at the results or using the code to do a test of their own.
Nevertheless, I consider that there are a number of conclusions that one can draw watching the monitored data:
  1. There is not an implementation that clearly has the best performance. Some had a very good CPU or memory performance and some did it very well when inserting or querying. But none of them was outstanding as a whole.
  2. The number of records inserted by Hibernate was extremely higher than it was for any other implementation (4 times more compared to Eclipselink and 24 times more compared to OpenJPA). However, Hibernate was also the JPA implementation that executed the lowest number of queries, although the differences in this value (3080 for Hibernate vs 3740 for Toplink Essentials) are not so extreme as for the number of inserts.
  3. Hibernate was also the implementation that consumed more memory. But having into account that it inserted many more records than the others, it sounds reasonable.
  4. OpenJPA had the lowest value of inserts+queries.
  5. The number of inserts executed by OpenJPA was extremely low, compared to the others.
  6. The usage of CPU in the case of Toplink Essentials and Eclipselink was extremely low.
Note for the JPA implementations responsible/developers: I am aware that some optimization can be obtained by changing the persistence.xml and/or changing the code somehow. If you give me some advice on how to improve the performance of any of the implementations, I will be glad to update this post with that information.

References
Ubuntu: http://www.ubuntu.com/
Employees database: http://dev.mysql.com/doc/employee/en/employee.html, https://launchpad.net/test-db/
Openjpa: http://openjpa.apache.org/
Toplink Essentials: http://www.oracle.com/technology/products/ias/toplink/jpa/download.html
Hibernate JPA: http://www.hibernate.org/397.html
Eclipselink: http://www.eclipse.org/eclipselink/
MySQL: http://www.mysql.com/
Eclipse: http://www.eclipse.org/

Lessons learned using GWT, Axis and JPA simultaneously

Note: This article is solely focused on some issues related to the EntityManager that may appear working with GWT, Axis and JPA. If you are interested in the performance of JPA, this other post about a comparison among different JPA implementations (OpenJPA, Hibernate, OpenJPA and Eclipselink) may be of some interest for you.

In my experience with the three technologies which appear in the title of this article, I had to face a number of problems, actually quite easy to solve once you know what all is about.
So I decided to publish this, for others not to have to face these same problems.
Here you have some lessons learned that should be taken into account when working with the Google Web Toolkit, Axis and any of the JPA implementations out there.
You have this same article in Spanish here.

These are the facts:
  • EntityManager can not be shared among threads. This is specified in JPA, but you don't give it enough importance until you start using JPA from the web (which will probably be the usual case).
  • Second, Axis creates a new object (and thread) for each call to the server.
  • Third, contrary to Axis, when you use one of the so-called GWT server remote services, only one object and thread are created and this object is reused everytime the remote service is called. That is to say, GWT does not create a new object for every new call.
As a result of these facts, we have these consequences:
  • If we use a static EntityManager in a call to Axis, the code will fail as soon as two threads cross with each other (quite usual in a web application), given that they will be using the same EntityManager object.
  • Contrary to Axis, in the case of GWT it is not necessary that EntityManager is static to start generating problems; it will be enough that it is a global attribute belonging to the object that implements the GWT remote service. Being one only object that answers all petitions, two calls to the server will use the same EntityManager and this will make the code fail.
So, we can conclude two rules:
  • An Axis service must therefore create a new EntityManager and this can not be static.
  • Each method of a GWT remote service must create a new EntityManager for individual use (it is not enough that it creates it at a global level).
If any of these two rules are not followed, 2 simultaneous calls will use the same EntityManager at the same time, producing exceptions.
Unfortunately, JPA exceptions are not especially explicit. This is common to almost every implementation of JPA: they give almost no concrete information (including Hibernate).

The EntityManagerFactory can and should be shared among different threads, given that it is a quite expensive object to create.

In short, if you don't want to have problems and you would like to have a piece of code that can be used both by GWT and Axis, you could write something like this:
public class JPAManager {
private final static EntityManagerFactory emf =
Persistence.createEntityManagerFactory("JPAImplementationTest");
private EntityManager em = emf.createEntityManager();
public EntityManager getEntityManager () {
return em;
}
}
Each method called in an invocation to a web service or to a GWT server remote service should create its own JPAManager and get the EntityManager with the public method. This way you will avoid having problems among the different threads created in the invocations.

You could also use more sophisticated solutions like implementing a singleton (with the problems associated to them) or using the ServletContextListener, as someone suggested in the comments. But this simple solution worked for me and resisted some load tests.

martes, 23 de diciembre de 2008

Lecciones aprendidas en el uso simultáneo de GWT, Axis y JPA

En mi experiencia con las tres tecnologías que dan nombre al artículo, me he encontrado con algunos problemas bastante fáciles de resolver una vez sabes de qué se trata (como siempre). Por eso, y para evitar que otros se peguen con lo mismo, aquí van una serie de lecciones aprendidas que deben ser tenidas en cuenta a la hora de trabajar con el Google Web Toolkit, Axis y cualquiera de las implementaciones de JPA. Para una comparativa detallada entre diferentes implementaciones de JPA (Hibernate, Toplink, Eclipselink y Openjpa), tienes este otro artículo disponible.
Si quieres ver este artículo en inglés, lo tienes disponible aquí.

Pues bien, los hechos:
  • En primer lugar, así como el EntityManagerFactory puede ser compartido por diferentes objetos (e hilos), el EntityManager no puede ser compartido entre diferentes threads. Esto está especificado en JPA, pero no le das importancia hasta que empiezas a usar JPA desde la web (que será el caso más habitual).
  • En segundo lugar, Axis crea un nuevo objeto (e hilo) por cada petición que entra al servidor.
  • En tercero, y al contrario que con Axis, al utilizar uno de los llamados servicio remotos de servidor del GWT se crea un objeto (y thread) que es posteriormente reutilizado cada vez que se llama a dicho servicio. Es decir, no se crea uno nuevo por cada uno que entra.
Las consecuencias a las que dan lugar los hechos:
  1. Si utilizamos un objeto EntityManager estático en una llamada a Axis, el código fallará en el momento en que dos hilos se crucen (bastante normal en la web), al utilizar el mismo objeto los 2.
  2. En el caso de GWT, al contrario que en Axis, no es necesario que el EntityManager sea estático para que de problemas (que también los dará, claro); bastará con que sea un atributo global dentro del objeto que implementa el servicio remoto. Al ser un objeto único el que responde a las peticiones, 2 llamadas al servidor utilizarán el mismo EntityManager y esto hará que código falle.
  3. Un servicio de Axis deberá, por tanto, crear un nuevo EntityManager, y éste no podrá ser estático.
  4. Cada método de un servicio remoto de GWT tiene que crear un nuevo EntityManager para uso individual (no vale que lo cree el objeto a nivel global, ver punto 2. Si no lo hace, 2 llamadas simultáneas al mismo servicio remoto utilizarán a la vez el mismo EntityManager, dando inmediatamente lugar a excepciones.
Por desgracia, las excepciones de JPA son muy poco explícitas. Esto es común para casi todas las implementaciones de JPA: dan poca información concreta de errores (incluyendo a Hibernate).

El EntityManagerFactory sí puede ser compartido por los diferentes hilos.

En definitiva, si no queréis tener problemas y queréis usar un código que puedan utilizar tanto GWT como las llamadas a servicios web a través de Axis, podríais usar un código como el siguiente:

public class JPAManager {
private final static EntityManagerFactory emf =
Persistence.createEntityManagerFactory("JPAImplementationTest");
private EntityManager em = emf.createEntityManager();
public EntityManager getEntityManager () {
return em;
}
}

Cada método llamado en la invocación del web service o del servicio remoto de servidor de GWT deberá crear su propio JPAManager. De esta forma, os evitáis tener problemas entre los diferentes hilos creados en las invocaciones.

Se pueden usar métodos más sofisticados, como implementar un singleton o usar el ServletContextListener, como ha puesto alguno en los comentarios. Pero lo cierto es que este sencillo método funcionó y resistió algunas pruebas de carga.

lunes, 22 de diciembre de 2008

Comparativa de implementaciones de JPA: Toplink, EclipseLink, Hibernate y OpenJPA

Este artículo es una respuesta a la falta de información existente en internet en lo que se refiere a las diferencias entre las 4 implementaciones más conocidas de la llamada Java Persistence API (JPA de ahora en adelante).
He realizado 4 pruebas exactamente iguales, una con cada implementación de JPA. Las diferencias entre las diferentes implementaciones han sido muy considerables, dando como ganador indiscutible a Hibernate, y como perdedor a Eclipselink. Puedes leer las conclusiones detalladas más abajo, si quieres.

También tenéis disponible este otro artículo acerca del uso de GWT, Axis y JPA de manera simultánea, porque tiene sus particularidades y merece la pena dedicarle uno completo. El actual, en cambio, se referirá exclusivamente a las diferencias en rendimiento detectadas en las diferentes implementaciones más utilizadas de JPA: Toplink, EclipseLink, Hibernate y OpenJPA.

Descripción del proyecto y las pruebas

El servidor con que se han hecho las pruebas (un Tomcat 5.5) ha ejecutado de manera concurrente dos proyectos desarrollados con Eclipse consistentes en lo siguiente:
  • Un proyecto que no tiene pantallas y sí acceso a base de datos (Oracle), tanto de inserción como de búsqueda. Utiliza JPA para el acceso a bbdd y Axis para la exposición de un web service. 
  • Un proyecto que sí tiene pantallas. Utiliza igualmente JPA para acceder a Oracle  (sólo búsqueda) y el Google Web Toolkit para la parte cliente (GWT de ahora en adelante), haciendo uso intensivo de ajax.
Para lanzar carga se ha utilizado:
  • JMeter para el servicio web, configurado con 5 hilos que lanzan 100 peticiones cada uno. Se configuró un tiempo de subida de 30 segundos. En total, por tanto, 500 llamadas , habiendo siempre 5 simultáneas.
  • Google Chrome para hacer peticiones, abriendo 10 ventanas simultáneas. Hay que tener en cuenta que cada ventana dispone de varias peticiones Ajax de actualización que se ejecutan cada 5 segundos (se ha forzado un tiempo tan corto la prueba). Es decir, cada 5 segundos se ejecutan 10x3=30 peticiones ajax simultáneas. No he utilizado JMeter para lanzar la carga web debido a que no hubiera replicado la parte ajax de la página, que es lo que realmente introduce carga en el servidor.
  • Para la medición de la memoria, he utilizado el JConsole, incluido en el jdk 1.6.
  • Para el mínimo tratamiento de imágenes (cortar, fundamentalmente) he utilizado el GIMP 2.
La prueba da comienzo con el Tomcat recién arrancado. Seguidamente se abren las 10 ventanas del navegador. Después se espera un minuto aproximadamente, para posteriormente lanzar los 5 hilos con JMeter, que lanzarán las 500 peticiones. Al acabar las peticiones se dejan pasar unos 5 minutos y después se activa a mano el Garbage Collector desde Jconsole. Esto último se hace para no tener que esperar (cuestión logística, no tengo tanto tiempo :-).

Diferencias en el rendimiento

Pues bien, éste ha sido el comportamiento de las diferentes implementaciones, de peor a mejor.

Eclipselink

Eclipselink parece ser la siguiente versión de Toplink, que Oracle habría donado a la comunidad de Eclipse. Hay que tener en cuenta que Toplink lleva mucho tiempo funcionando, no como implementación JPA, sino como capa de acceso a dato. Oracle añadió capas para que fuera accesible vía JPA, exactamente igual que hicieron los de Hibernate.
Supongo, por tanto, que Eclipselink es aún una implementación muy nueva que aún tiene que madurar, exactamente igual que Openjpa.

Eclipselink dio el peor rendimiento durante las pruebas, con mucha diferencia. La memoria ocupada no hizo más que aumentar desde el principio de la prueba, poniendo de manifiesto que hay algún tipo de memory leak sin controlar en dicha implementación.
En concreto, el máximo de memoria utilizada (60 mb) triplica al utilizado por Hibernate o Toplink (poco más de 20 mb).

La memoria ocupada al final de la prueba, aun después de forzado el recolector de basura (GC), quedó en 43 mb, marcando también un record máximo, y casi triplicando la ocupada por Hibernate o Toplink (13 y 15 mb respectivamente).

Los tiempos de respuesta, por otro lado, fueron el doble que los de Hibernate o Toplink.

Apache Openjpa

Openjpa es la respuesta de Apache en lo que se refiere a JPA, y es la implementación por defecto en Gerónimo, el servidor de aplicaciones de Apache que incluye JPA y EJB3. En cualquier caso, me sorprende que la gente de Apache haya liberado algo tan aparentemente inmaduro.

Openjpa se comportó algo mejor que Eclipselink, sobre todo en lo que se refiere a los memory leaks, pero no tanto como para considerarla una alternativa viable para su uso en producción.
Por un lado, se observa el típico comportamiento de diente de sierra del Garbage Collector a lo largo de la prueba (aunque no de manera tan correcta como en Hibernate o Toplink), y por otro lado la memoria ocupada en el heap al final de la prueba es menor que la implementación que mostró peor rendimiento (32 mb frente a los 43 mb de Eclipselink), pero el doble que en el caso de Hibernate o Toplink.
Otro dato a tener en cuenta sobre Openjpa es que no informa en el log del servidor cuando arranca, al contrario que todas las demás implementaciones. Esto al menos ocurre con la configuración por defecto.

Toplink Essentials

Toplink Essentials es un subconjunto gratuito del software java de acceso a datos de Oracle. Dicha empresa le añadió varias capas de abstracción para hacerlo compatible con JPA, pero la base continúa siendo la original, estando por tanto bastante maduro.

El comportamiento de Toplink se acerca mucho al de Hibernate. La diferencia en memoria ocupada es poco apreciable. Por un lado, el máximo a lo largo de la prueba fueron unos 25 mb, al igual que Hibernate, y el total ocupado al finalizar fue de 15 mb, 2 megas por encima de Hibernate. Si tenemos en cuenta la enorme diferencia con Eclipselink y Openjpa, podemos decir que el rendimiento es muy similar al de Hibernate, y es desde luego una alternativa viable para su uso en producción.

Hibernate

Hibernate es probablemente el ORM más utilizado, el que más influencia tuvo en la especificación de EJB3, y la implementación de JPA que viene incluida en las últimas versiones de JBoss. La gente de Hibernate, al igual que la de Oracle, incluyó varias capas de abstracción en su software para hacerlo compatible con JPA. Lo bueno es que el software que hay detrás de esas capas está sobradamente probado, al haber muchísima gente que lo utiliza ya en entornos de produccción, lo que lo hace bastante maduro. Esto, entiendo yo, explicaría el excepcional rendimiento que dio durante la prueba.

El rendimiento en Hibernate fue muy superior al de sus competidores, a excepción de Toplink. Hibernate necesitó 3 veces menos de cantidad de memoria para funcionar que Eclipselink y Openjpa, y al terminar la prueba estaba ocupando 3 veces menos también que dichas implementaciones.
Aunque su rendimiento se asemeja al de Toplink, lo supera en memoria ocupada al final de la prueba (13 mb de Hibernate frente a 15 de Toplink).
El diente de sierra debido al funcionamiento del recolector de basura es perfectamente apreciable en la imagen, pero funciona ligeramente mejor que para Toplink cuando se deja de lanzar carga. Toplink queda con unos 24 mb ocupados, frente a los 20 de Hibernate. Lo cierto es que cuando se fuerza la ejecución del garbage collector, la diferencia entre ambos se reduce a 2 mb. Según la carga que vaya a recibir una aplicación, esa diferencia podría llegar a ser importante.

Conclusiones
  • El ganador de esta competición de implementaciones de JPA es, sin lugar a dudas, Hibernate. Fue la implementación que mejor resultado dio en cuanto a consumo de memoria durante y después de la prueba y en cuanto a tiempos de respuesta. Toplink le sigue muy de cerca en rendimiento.
  • En cuanto al perdedor, Eclipselink tuvo el peor comportamiento de todos, seguido muy de cerca por Openjpa. En mi humilde opinión, ninguna de las 2 implementaciones parece lo suficientemente madura como para ser utilizada en producción, al menos con la configuración por defecto. El consumo de memoria sin freno y los aparentes memory leaks llevarán a dejar colgado al servidor de aplicaciones cuando se ocupe toda la memoria heap disponible.
  • Mi recomendación, hoy por hoy, por madurez y seguridad en el rendimiento, es sin duda para Hibernate, teniendo en cuenta que las pruebas se han hecho con Toplink Essentials, un subconjunto de la implementación de JPA de Oracle, que si se quiere completa hay que pagar por ella. Hibernate, en cambio, es completamente libre y gratuita.