The following Stackoverflow question refered to the slowness of first requests made with a WebClient: https://stackoverflow.com/questions/53943499/workaround-for-the-slowness-of-the-webclient-first-request

I opened a an issue in reactor netty's repo which has been recently solved, adding a warmup functionality for TCP resources

I've upgraded the sample I provided to use Netty and Spring Boot 2.4.2. First request times are ~800ms and the subsequent ones ~60ms.

Would you consider adding Netty's warmup configuration as an option for Spring Boot/WebClient?

Comment From: wilkinsona

Thanks for the suggestion.

I think it may be worth providing a configuration property for this to enable the call to warmup(). In the meantime, you should be able to achieve the same with a NettyServerCustomizer, possibly ordered with lowest precedence, that calls warmup().block(). On the client-side, Boot doesn't create the WebClient instance for you, it only provides a Builder so I think you'll always have to make the call to warmup() in your own code there.

There's also the sharing of resources to consider here. IIRC, in a WebFlux app a WebClient will share some resources with the server. This'll have an effect on what, if anything, benefits from a warmup() call.

Comment From: benjaminknauer

Hi there :wave:

We upgraded Spring Boot to 2.4.2 with reactor-netty 1.0.3 but still encounter this issue. Here is our upgraded configuration:

   @Bean
    public WebClient createWebClient(WebClient.Builder webClientBuilder) {
        log.info("Initializing WebClient Bean");

        final int timeoutInMillis = Long.valueOf(TimeUnit.SECONDS.toMillis(timeout)).intValue();
        final HttpClient httpClient = HttpClient.create()
                .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, timeoutInMillis)
                .responseTimeout(Duration.ofMillis(timeoutInMillis))
                .doOnConnected(conn ->
                        conn.addHandlerLast(new ReadTimeoutHandler(timeoutInMillis, TimeUnit.MILLISECONDS))
                                .addHandlerLast(new WriteTimeoutHandler(timeoutInMillis, TimeUnit.MILLISECONDS)));
        final ClientHttpConnector connector = new ReactorClientHttpConnector(httpClient);
        final WebClient webClient = webClientBuilder
                .clientConnector(connector)
                .defaultHeader("x-clientname", clientname)
                .build();

        httpClient.warmup().block();
        log.info("WebClient initialized");

        return webClient;
    }

Our call with WebClient:

  ResoponseObject doCall() {
        return this.webClient
                .get()
                .uri("http://***.de/api/rest/***")
                .accept(MediaType.APPLICATION_JSON)
                .retrieve()
                .bodyToMono(ResponseObject.class)
                .block();
    }

With debugging enabled via application.yaml: logging.level.reactor.netty: debug

We now see this during application startup:

2021-02-10 17:02:31,922 INFO  d.t.e.b.c.c.WebClientAutoConfiguration - Initializing WebClient Bean 
2021-02-10 17:02:31,959 DEBUG r.n.r.DefaultLoopIOUring - Default io_uring support : false 
2021-02-10 17:02:31,967 DEBUG r.n.r.DefaultLoopEpoll - Default Epoll support : true 
2021-02-10 17:02:31,997 INFO  d.t.e.b.c.c.WebClientAutoConfiguration - WebClient initialized 

This should be an indication that the warmup works as expected?

But on first request this happens:

2021-02-10 17:05:16,045 DEBUG o.s.w.r.f.c.ExchangeFunctions - [73d400c8] HTTP GET http://***.de/api/rest/***
2021-02-10 17:05:16,050 DEBUG r.n.r.PooledConnectionProvider - Creating a new [http] client pool [PoolFactory{evictionInterval=PT0S, leasingStrategy=fifo, maxConnections=500, maxIdleTime=-1, maxLifeTime=-1, metricsEnabled=false, pendingAcquireMaxCount=1000, pendingAcquireTimeout=45000}] for [***.de/<unresolved>:80] 
2021-02-10 17:05:29,619 DEBUG r.n.r.DefaultPooledConnectionProvider - [id: 0x71b840f4] Created a new pooled channel, now 1 active connections and 0 inactive connections 
2021-02-10 17:05:29,635 DEBUG r.n.t.TransportConfig - [id: 0x71b840f4] Initialized pipeline DefaultChannelPipeline{(reactor.left.httpCodec = io.netty.handler.codec.http.HttpClientCodec), (reactor.right.reactiveBridge = reactor.netty.channel.ChannelOperationsHandler)} 
...

In our case, creating the client pool is the problem. On a decent machine, it takes about 13 seconds.

Can you give us any comment on that? This is very frustrating for us.

Thanks a lot!

Comment From: wilkinsona

@benjaminknauer That's really a question for the Reactor Netty maintainers. You can find them on Gitter.

Comment From: bclozel

@benjaminknauer 13 seconds for a connection pool seems like a lot. I'm not sure the connection pool is really part of the initialization process. Maybe this is related to the change of DNS resolver? Did you check reactor/reactor-netty#1431 ?

Comment From: benjaminknauer

Yup, I changed the DNS resolver, but it did not help.

I created a demo repo and an issue for reactor-netty. You might also want to have a look?

https://github.com/reactor/reactor-netty/issues/1509

https://github.com/benjaminknauer/spring-mvc-web-client-first-request-slow-demo

Comment From: benjaminknauer

It was a problem with our network. I'm sorry... Thank you for your help!

https://github.com/reactor/reactor-netty/issues/1509#issuecomment-777449843

Comment From: k163377

Are there any plans to add this feature?

I understand that it can be achieved in the following ways, for example, but it would be nice to see it implemented as a feature related to WebClient. https://github.com/reactor/reactor-netty/issues/1431#issuecomment-822998766

Comment From: philwebb

Are there any plans to add this feature?

@k163377 Any triaged issue still open is something we think is a valid idea. This particular issue is in the 3.x milestone which means we think it could be added in any 3.x release, but it isn't a particular priority when compared to other work we're dealing with.

Comment From: k163377

At least, the problem of slow first request for WebClient has not yet been solved as of Spring Boot 2.7.3. Although the verification is quite inaccurate, the first request was less than 1 second slower than the second and subsequent requests in my environment.

This is especially problematic for endpoints that make multiple requests. Personally, I feel that even in limited situations, a delay of about 1 second per request is an important issue.

Comment From: wilkinsona

@skumaridas10 How does that exception relate to the subject of this issue which is to eagerly initialize Netty's TCP resources?