Improving the performance of the Spring-Petclinic sample application (part 5 of 5)

This is part 5 of our 5-part series on improving the performance of the Spring-petclinic application. Here are part 1part 2part 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:

[Source code]

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.

Extreme testing

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.

[edit]

You can find the other episodes of this series here : part 1part 2part 3 and part 4.

Tweet about this on TwitterShare on FacebookGoogle+Share on LinkedIn

6 réflexions au sujet de « Improving the performance of the Spring-Petclinic sample application (part 5 of 5) »

    1. Thanks for the feedback. I couldn’t comment on your blog, so I’m doing it here.

      It wasn’t that much work for the performance tuning per se : probably not more than 1 day. However, the real issue here is authoring : explaining everything in a “story” that is easy to read.

      So indeed, I haven’t put everything I have done, and I didn’t include all the details, as this was going to pollute the story.

      Anyway, there’s a big issue in your comments… I was ALWAYS with 128M of RAM, excepted for one test (which had 1 G). Maybe my article wasn’t clear enough, but that’s a huge difference…

      Concerning the goals of the test, it was just to make everything faster. It’s just a sample app, there’s no real goal here.

      Concerning HTTP errors, I didn’t go into much details : they were solved by using the NIO connector, which seemed logical (with 500 threads hitting the server, I doubt the BIO default configuration was good enough). When I say HTTP errors, it’s errors reported by JMeter, not OOME.

      Concerning locks, my method is just to remove them once at a time. In the end it always pays off, it’s as simple as that.

      Same thing with going stateless : it can decrease performance (in fact it does in my example), as the state is a sort of “cache”. My goal is to be more scalable and use less memory, as I know that this will be a good thing in the end (and we will have a real cache in the last step).

      Maybe I should also clarify that I was not aiming at pure performance, but also better scalability and less errors. But once again, those posts were just a short story, and I didn’t want them to be too big or annoying to read, so I didn’t put all those details.

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *


*