10

so this question has been asked a few times already but seems like no one answered it in a way that could help me. I'm currently making a backend for a simple application handling product data. It's not even with JSP, just a plain Rest backend. Using RestControllers by Spring.

The "problem" is: the first request after startup takes way longer to get an answer from the server than all others. (I'm just testing with Postman with a simple JPA User Entity)

Some things to consider:

  • it's probably not a database problem in itself since it clearly just initializes something upon first incoming request instead of on startup
  • In the log, it says "Initializing Spring DispatcherServlet 'dispatcherServlet'" when the first actual request comes in (via Postman).
  • If I pull all users from the database (which currently is only one user), the first request after startup takes 140ms (according to Postman). After that, the same request needs always 10ms at max.
  • There's a flag some answer to a similar question suggested: spring.mvc.servlet.load-on-startup=1. Though this only removes the logging (of the initialization of the DispatcherServlet) mentioned above.
  • It seems like this is standard behavior which has nothing to do with how I actually coded my entities and/or RestControllers.

How do I make the first request faster / how do I force Spring to actually initialize everything before the first request comes in?

Some code anyways:

User.java:

@Entity
@Table(name = "users")
@Data
public class User {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private long id;

    @NonNull
    private String firstName;
    @NonNull
    private String lastName;

    @NonNull
    @OneToOne(cascade = CascadeType.ALL)
    private Address billingAddress;

    //a bit more. a list and another address
}

UserController.java:

@RestController
@RequestMapping("users")
public class UserController {

    private final UserRepository userRepository;

    @Autowired
    public UserController(UserRepository userRepository) {
        this.userRepository = userRepository;
    }

    @GetMapping()
    public List<User> getAllUsers() {
        return (List<User>) userRepository.findAll();
    }

    //more stuff
}
dnault
  • 8,340
  • 1
  • 34
  • 53
Panossa
  • 371
  • 5
  • 17
  • How are you running/deploying the application? – chrylis -cautiouslyoptimistic- Jul 21 '20 at 17:13
  • IntelliJ just created a run configuration for me. (Spring Boot Application) – Panossa Jul 21 '20 at 18:05
  • 1
    Spring or Spring Boot? That makes quite a difference, also with debugging/devtools enabled or without (that also makes a difference). You should be testing an actually deployed one and not one burdend with debugging etc. Also you should really set the `spring.mvc.servlet.load-on-startup` to 1 or higer (else it will be delayed). It won't remove the logging, but it will now be eagerly instantiated and started. – M. Deinum Jul 21 '20 at 18:29
  • Yeah, I meant Spring Boot. And afaik it's without dev tools, just normal logging. K, I'll set it to 1. ¯\_(ツ)_/¯ – Panossa Jul 21 '20 at 20:30

1 Answers1

6

You answered the question yourself. A lot of stuff in Spring is lazy init for performance and resource consumption optimizations. Is a user really going to even notice that ONE request took 140ms vs 10ms. Probably not. And mind you, that's not one request per user, that's one request per init path per start up.

All that being said... you also answered how to "fix" it. After you deploy (which I assume is automated through CI/CD), you submit a request or requests (part of your CI/CD) that will trigger the initialization down all the paths you need. I.e. if you have 5 db connections, you might need to submit 5 requests to initialize everything.

This is perfectly acceptable and is a process known as "warming up the system".

SledgeHammer
  • 7,338
  • 6
  • 41
  • 86
  • 2
    Hm, ok, I get why this lazy loading might be good but I still don't get why I can't just set a flag in the application properties so it doesn't do that. Sending a request manually seems like a workaround imo. But yeah, you're right, it's not THAT important if it's only the first one. – Panossa Jul 21 '20 at 18:07
  • 2
    @Panossa "warming up" is not a workaround. Its standard procedure in things like bench marking, deployments, your car's engine, your oven/bbq (pre-heat), your iron, etc. Could go on and on. Very few things are instant on at full performance. – SledgeHammer Jul 21 '20 at 20:30
  • 1
    This solution is not acceptable in some use cases.Real-world example: REST endpoint that verifies credit card when making card payments. First call is 1500, ms second 200 ms. Users will notice the difference, and there is the matter of SLA. Also warm up by calling REST endpoint is tricky because of the data that you have to provide and data that is left in the database after warm-up. – zoran Oct 21 '22 at 10:09
  • Hello @zoran I agree with you.. Did you find a solution to automatically 'warmup' all components and proxies on startup to avoid this first-call-delay? – Paolo Biavati Oct 25 '22 at 16:26
  • 1
    @PaoloBiavati answer is from like 3 yrs ago and SLAs are typically AVERAGE response time. If you have ONE req at 1500ms and a million at 200ms its a non issue. Anyways, you can also try use spring.main.lazy-initialization=false but that will make your app start up time a lot slower. You can also cherry pick beans to not lazy init with @Lazy(false). But like I said, you are losing sleep over ONE request which would not affect any SLA. You will likely have a much worse startup experience when you turn off lazy init lol. – SledgeHammer Oct 26 '22 at 05:22
  • @Paulo the best solution i have so far is to create a Service with '@EventListener(ApplicationReadyEvent.class)' that calls REST endpoint that is part of same spring app. SledgeHammer, lazy initialization is disabled by default, so you can only have a slower first call (but faster start up time that is not the issue in this thread) if you play around with it – zoran Oct 28 '22 at 12:17