Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Slow first call after restarting Spring Boot application

We have a React - Spring Boot application using Postgres, Hibernate, Jackson and Spring Data Rest.

After each application restart, the first call to a post route hitting one of our back-end servers is long (more than 4 seconds). Every subsequent call to the same route hitting each server is under 100 ms.

Our goal is to guarantee that none of our users is ever impacted by these slow calls after each redeployment.

We are considering triggering the calls automatically after each deployment so that the application "warms up" and our clients do not have the long calls.

Since there are several back-end servers behind a load balancer, we would like to trigger these calls directly from the back-end and not from a client.

We would like understand better what can be happening so that we to this more efficiently: could it be Hibernate? Spring lazy-loading beans? Jackson?

Hibernate L2 cache is not activated.

We expect to have the same fast response time each time (< 100 ms) instead of the initial 4s calls.

like image 754
Marc Janvier Avatar asked Aug 01 '19 15:08

Marc Janvier


1 Answers

Quick update we followed @willermo's answer plus a couple of tips from another forum have led us in the right direction to fix the problem.

We logged the class loading using the -verbose:class flag, which made it clear that the problem were classes being lazy loaded at the moment of the first call.

To pre-load these classes, we used an ApplicationRunner to trigger the calls on application startup, as suggested by @willermo; this allowed us to deterministically warm up all the servers behind the load balancer with a single call for each.

There were a couple extra obstacles that were easy to fix:

  • Adding the ApplicationRunner broke all our tests, so we had to exclude it from the test profile.
  • We did not want to persist the effects on the DB of these "fake" calls, so we wrapped them in a transaction that we rollback in the end.

This was our final solution:

@Component
@Profile("!test")
public class AppStartupRunner implements ApplicationRunner {

  // [Constructor with injected dependencies]

  @Transactional
  @Override
  public void run(ApplicationArguments args) throws Exception {
    // [Make the calls]
    TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();    
  }
}

like image 69
g3org3 Avatar answered Sep 21 '22 15:09

g3org3