Reputation: 93
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
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
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:
ApplicationRunner
broke all our tests, so we had to exclude it from the test profile.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
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
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
}
}
Upvotes: 2