11

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:

  1. HelloController2 -> /v1/foo/{foo}/bar/{bar}/giraffe
  2. HelloController3 -> /v1/foo/{foo}/bar/{bar}/cat
  3. HelloController4 -> /v1/foo/{foo}/bar/{bar}/tacocat
  4. HelloController5 -> /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.

1

2 Answers 2

0

What you can do without change Spring code, is to refactor your code to use only one RequestMapping which matches all problematic patterns and write some perfomant algorithm by yourself to delegate the calls to the right classes and pass the parameters.

You can start the algorithm with a if tree and some regex, don't forget to compile the regex and save it to a constant, this way you will not have performance issues.

Sign up to request clarification or add additional context in comments.

Comments

0

You can perform better in a couple of ways:

  • write a single controller and manually handling the path /v1/foo/{foo}/bar/{bar}/{animal} and perform a switch case inside each method of the controller
  • use a single controller and delegates the pattern to single methods instead of class. If there is a method that is performed more frequently than others put it at the beginning of the class so that it will be checked as first

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.