Before I ask my question, please note that actual numbers are not representative for performance. What matters is their value relative to each other and the fact that I get consistent numbers (in a small range, of course) between runs.
So, I have the following spring boot application running on Jetty:
// Application.java
package hello;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
// HelloController1.java
package hello;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
@RequestMapping("/v1/foo/{foo}/bar/{bar}/dog")
@RestController
public class HelloController1 {
@RequestMapping(method = RequestMethod.GET)
@ResponseBody
public ResponseEntity<String> m() {
return ResponseEntity.ok("dog");
}
}
With these Maven dependencies:
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>LATEST</version>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jetty</artifactId>
<version>LATEST</version>
</dependency>
</dependencies>
The only configuration property set is: logging.level.org.springframework.web=ERROR
I benchmark the application with wrk:
wrk -t 10 -c 10 -d 40s http://localhost:8080/v1/foo/foo/bar/bar/dog
And I get a consistent 45K requests per second.
Now, I add 4 more controllers, identical with the one above with the exception of the class name and request path:
HelloController2->/v1/foo/{foo}/bar/{bar}/giraffeHelloController3->/v1/foo/{foo}/bar/{bar}/catHelloController4->/v1/foo/{foo}/bar/{bar}/tacocatHelloController5->/v1/foo/{foo}/bar/{bar}/parrot
Now, if I run the test again I get only 35K RPS on average.
I expect the second test to get lower values, especially because of those 5 routes sharing the same prefix - I can imagine some implementations that could perform worse that others - but 20ish % seems a lot.
Before I start digging through the spring (boot) source code, I'm hoping that maybe there's someone here, familiar with the router/matcher implementation and can figure out what's happening. Why such a big RPS difference?
Edit after looking a bit through the Spring source code. Please correct me if I'm wrong:
It seems that the route path matcher does a linear search through all the available route paths for every request. For each route path, if it doesn't have patterns, it just does a dictionary lookup and everything is good. However, it the path has parameters (like {foo} and {bar} in my example), it has to go to the AntMatcher to match those with the actual path. The matcher does a left to right search so it fails as soon as it finds something that doesn't match. Since all my routes start with the same pattern, it has to do a lot of work on each thus the performance gets worse when adding more routes.
By the way, I'm open to suggestions on how to make this perform better. Our application is suffering because of this.