Can somebody elabarate some strange issue I ran into with Spring Boot? I have a reactive Spring MVC Controller such as:
private final Scheduler dvScheduler = Schedulers.newParallel("pool", 1000);
@Override
@SneakyTrows
@GetMapping
public Mono<String> method(Mono<String> mono) {
return mono
.publishOn(dvScheduler)
.flatMap(body -> {
Thread.sleep(8000L);
return Mono.just("");
});
}
This is an external system stub for testing the main system in cases of failure (8sec delay emulates external system's high reponse time). The goal is to measure main system's throughput in 'almost failing' cases such as high reponse time. So the stub is sleeping emulating high response time while the main system calls it using web-client.
The problem is that this app (the stub) can serve no more than 200 concurrent requests no matter how I configure the thread pool. E.g. using tomcat:
tomcat:
threads:
max: 1000
min-spare: 1000
accept-count: 1000
max-connections: 10000
I can run some profiler (or get a thread dump) and see that all 1000 threads are created but only 200 of them are staying on Thread.sleep() method from my controller. All other 800 are free in the thread pool. The load is simulated using Soap-UI load test where I can see something about 15-20tps due to this issue.
I am using Spring Boot 2.3.4.RELEASE, tested with Tomcat, Netty and Undertow both reactive and classic approach. The result is always the same - a bottleneck with 200 concurrent requests.
800 threads like this:
jdk.internal.misc.Unsafe.park(boolean, long)
java.util.concurrent.locks.LockSupport.park(java.lang.Object) (line: 194)
java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await() (line: 2081)
java.util.concurrent.ScheduledThreadPoolExecutor$DelayedWorkQueue.take() (line: 1170)
java.util.concurrent.ScheduledThreadPoolExecutor$DelayedWorkQueue.take() (line: 899)
java.util.concurrent.ThreadPoolExecutor.getTask() (line: 1054)
java.util.concurrent.ThreadPoolExecutor.runWorker(java.util.concurrent.ThreadPoolExecutor$Worker) (line: 1114)
java.util.concurrent.ThreadPoolExecutor$Worker.run() (line: 628)
java.lang.Thread.run() (line: 834)
200 threads
java.lang.Thread.sleep(long)
java.lang.Thread.sleep(long, int) (line: 339)
my.org.controller.stub.StubController.method(Mono<String>) (line: 111)
The idea is just to make sure that the stub app will not limit TPS of the main system.
What restricts Spring Boot app's throughput to such a number? Where could be that setting = 200?
UPDATE: Seems a Soap-UI issue. After switching to JMeter TPS is higher and the threads are busy in I/O operations. I have -Dsoapui.threadpool.max=500 in its properties but for some reason the tool restricts something and 400 threads with 0 delay in Soap-UI result in 20tps while the same 400 in JMeter - 50tps.