Marc Janvier
Marc Janvier

Reputation: 93

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.

Upvotes: 9

Views: 10450

Answers (4)

Akshay Shinde
Akshay Shinde

Reputation: 159

In my case, the DB connection pool was taking time for initialization on first request.

I have added minpoolsize=10 in database string, now the first request is as quick as the subsequent requests.

The default minpoolsize is 0.

Upvotes: 0

g3org3
g3org3

Reputation: 136

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();    
  }
}

Upvotes: 8

jlar310
jlar310

Reputation: 649

I would look at your JDBC pool. Is Spring waiting for that first request before it populates the pool? Creating database connections is an expensive operation.

If you are using a Tomcat pool, look at the initial-size and min-idle properties. For HikariCP pool, look at minimum-idle.

You should be able to observe in a test environment if JDBC connections are opened to your database before any calls are made to the application.

The built-in Spring Boot Actuator health check endpoint also has options for checking the status of the database and may help bootstrap the pool.

Upvotes: 2

willermo
willermo

Reputation: 503

You can try to make the warm up call using an application runner.

public interface ApplicationRunner

Interface used to indicate that a bean should run when it is contained within a SpringApplication. Multiple ApplicationRunner beans can be defined within the same application context and can be ordered using the Ordered interface or @Order annotation.

e.g.

@Component
public class AppStartupRunner implements ApplicationRunner {


  @Override
  public void run(ApplicationArguments args) throws Exception {
    System.out.println("Running");
    //Make the first call here 
  }
}

Application Runner

Upvotes: 2

Related Questions