Spring Boot performance battle: blocking vs non-blocking vs reactive
I would like to talk about interesting stuff that I faced on my project. For our client, we wrote some lightweight microservices in AWS that just proxies requests to some underlying services via HTTP and returns it back to the client.
The main flow:
At first glance, what could be simpler than writing a REST proxy service?
Important condition: we didn’t control underlying service.
So, of course, we started with Spring Boot and wrote simple RestControllers. We made the POC and the results were good. Third party service had SLA with the service response time and we used this value for performance tests. The response time of the third party service was quite good ~ about 10–100ms. We also decided to use CPU as a scaling policy for our microservice which was running in Docker as AWS ECS service. We configured autoscaling in AWS and went live.
You guessed it, not everything went smoothly. We often had AWS ECS task restarting due to health check timeout. Also, we were wondering that scaling didn’t work so good and we always had a minimal number of running task. In addition, we saw that CPU and memory are low but our service was too slow and sometimes even had timeout error.
You are right, the problem was in the third party service. Third party service response time became 500–1000ms. BUT, it never had a timeout issue and was able to handle more clients then we had.
So the problem was in our service. We didn’t scale up our application when it was needed. We made the performance test for 500–1000ms and were shocked.
CPU was low, memory was good, but we were able to handle only 200 requests/sec.
This was Servlet thread per connection issue. The default thread pool is 200 that was why we have 200 requests/sec for 1000ms response time.
But we needed an elastic service: we should handle as many requests as underlying service can. And response time should be almost the same as underlying service.
We investigated it and found several options:
- Increase the thread pool size
- DeferredResult or CompletableFuture with Servlet
- Spring reactive with WebFlux