Wednesday, May 25, 2011

Performance optimizing a Hibernate application

A few weeks ago, I was out helping a customer of my company. They suffered from  seriously bad performance on a smaller web application. The web application was build using Hibernate, Wicket, Guice, Warp-persist and more. The architecture of the application was sound. It consisted of 3 layers:
  • A (wicket) Web layer
  • A service layer (business functionality in Guice POJOs, bypassed if not needed)
  • A persistence layer (implemented as Guice/Hibernate DAOs)
The amount of data in the MySql database did not cause any alarms with respect to performance. Well except that performance problems should not be present.

After having been introduced to the domain model I therefore started out using the strategy often recommended by others. I added Hibernate Query Logging by modifying log4j config: org.hibernate.SQL=DEBUG and occasionally also org.hibernate.type=TRACE to se JDBC in- and output parameters.

With Lund&Bendsens Hibernate performance slides in my hand I started searching for problems in prioritized order
  1. Wrong Hibernate inheritance strategy
  2. N+1 select problems
  3. Loading of to big object hierarchies
  4. Long query times because of complex dynamic queries
  5. No caching of rarely modifying objects

1) I quickly found a couple of object hierarchies mapped into the database using the TABLE_PER_CLASS inheritance strategy. These were replaced by SINGLE_TABLE and JOINED which already improved performance on most pages by a magnitude of ~5.

2) The next problem was the well known N+1 select problem in Hibernate applications. As in most Hibernate application Object relations were often mapped using @OneToOne and @ManyToOne resulting in an eager load of the related entity. E.g. A person is related to his company

public class Person{
   Company worksInCompany;

Executing the query "from Person p where p.username = :p" will result in one SQL selecting all persons, where after Hibernate will traverse through the list a execute a query to fetch the company for each person.This may result in N extra queries.

The problem was most often solved by
  • Marking some relations lazy to prevent Hibernate loading the related objects by default, e.g. @ManyToOne(fetch=LAZY)
  • Of these relations fetch some of the eagerly by using fetch join, e.g. instead of "from Person p where p.username = :p" then execute "from Person p fetch join p.worksInCompany where p.username = :p"

2.5) The third problem which we stumbled into was unexpected, but admittedly I should have seen it from the beginning. The warp-persist @Transaction annotation was used on DAO findXXX methods starting and stopping transactions when searching for data. A poor web application architecture resulted in another set of N+1 problems, which could not be solved by tuning Hibernate queries. Re engineering the web application architecture was not an option due to project time constraints. Instead we found that executing findXXX without a transaction resulted in acceptable performance. As always remember to only use transaction when necessary (often only when modifying the database content)!!

3) was solved while working on 2)

4-5) I never got to look at before acceptable performance were reached.

There are also other Hibernate performance tuning techniques, which I haven't even mentioned.

More information :

No comments:

Post a Comment