This is part 5 of our 5-part series on improving the performance of the Spring-petclinic application. Here are part 1, part 2, part 3 and part 4.
Adding more cache
To increase application performance, one of the classic solutions is to add more cache. We already have a cache configured in the application, it is on the JpaVetRepositoryImpl.
We are going to generalize this cache on 2 different parts of the application:
- Using the Spring Cache abstraction, like on the JpaVetRepositoryImpl, to cache frequently used business methods. The JpaOwnerRepositoryImpl.findByLastName() method is a perfect candidate for this: doing a “like” clause is very slow on a database, and people’s last names do not change frequently. So we decided to cache this result for 60 seconds.
- Using the Hibernate second level cache: we have cached most entities and collections of the application, and re-coded parts of the application to use this cache. As a result, using Hibernate’s “showSql=true” parameter, we can see that the application does not execute any unnecessary SQL request (as long as there is no write, requests are only executed once).
You can see those changes in the following commit:
Our final result goes up to 1225 req/sec. That’s only a 15% performance boost, but please note that we have a very small database, and that it is running locally. On a real-world system, the improvement should be better, especially for the JpaOwnerRepositoryImpl.findByLastName() method.
We decided to do an extreme testing session, in order to see if we really stabilized the application (using Tomcat’s NIO connector and going stateless, specifically).
So we ran our test again on the Macbook, still with 500 threads, but this time we let it run until we had 100 000 user sessions.
Our results are extremely good:
- No HTTP error at all
- Memory stays at the same level, no matter how many users are using it
- The application runs smoothly, and stabilizes at 1565 req/sec !!
This is, of course, an excellent result.
Conclusion and final thoughts
During those five days, we have gone through the classical steps we use at Ippon Technologies when auditing a project:
- Creating a “real” test case
- Removing data in the HTTP Sessions
- Removing the JVM locks
- Tuning the persistence layer
- Adding a cache
For each of these steps, JMeter and YourKit were our best tools to stress test the application and monitor how it responded to the test.
Of course, more work could have been done on JVM tuning, and on database tuning (but changing the database schema is outside the scope of this article).
At the beginning of the tests, we had to increase our heap memory size to 1 Gb, and could only serve 548 req/sec, with some HTTP errors. After completing our audit, we are now back to 128 M, and can serve 1225 req/sec with no error at all. We expect those results to be even better on a real server, with many cores and threads, and where removing JVM locks will have a more significant impact.
We also had a great improvement in the application stability, having 0 errors and 1565 req/sec with 100 000 users on our “extreme tests”. The application is now ready to handle a lot of users without any trouble.
Last but not least, we have switched the persistence layer from JDBC to JPA: a quick look at the code shows how much clearer and smaller the JPA code is. And the Spring Data JPA code is even clearer and smaller. It’s great to see that quality code can also be more performant than low-level, hard-to-code classes.
You can find the other episodes of this series here : part 1, part 2, part 3 and part 4.