Hello.
We upgraded from Spring Boot 2.1.7.RELEASE to 2.3.12.RELEASE.
In our workload we have to make http requests to a huge number of external services (10k+). We use a single WebClientBuilder
to create a WebClient
for each external service when it's needed.
With an updated Spring Boot we noticed that heap usage and gc time grew significantly.
Some top objects from a heap dump:
org.springframework.http.MediaType 3,531,788 (8.2%) 197,780,128 B (9.2%) n/a
org.springframework.core.log.CompositeLog 1,724,850 (4%) 110,390,400 B (5.1%) n/a
org.springframework.util.MimeType[] 2,053,150 (4.8%) 74,241,920 B (3.5%) n/a
etc.
Digging into the heap dump I discovered that with SpringBoot 2.1.7.RELEASE we had a single DefaultExchangeStrategiesBuilder
object, that were refferenced by all WebClient
s, but now we have 1 builder for each client.
Overridding WebClientCodecCustomizer
to it's previous implementation helped, gc time decreased to the same level as it was in SpringBoot 2.1.7.RELEASE:
@Override
public void customize(WebClient.Builder webClientBuilder) {
webClientBuilder.exchangeStrategies(ExchangeStrategies.builder()
.codecs((codecs) -> this.codecCustomizers.forEach((customizer) -> customizer.customize(codecs)))
.build());
}
As far as I understood, the root cause is that DefaultWebClientBuilder.initExchangeStrategies
creates a new ExchangeStrategies.Builder
for each build
method call, if strategiesConfigurers
list is not empty. Previous WebClientCodecCustomizer
implementation used to set strategies
field directly and thus used a single DefaultExchangeStrategiesBuilder
object.
If I'm not mistaken, a possible solution is to cache mutated ExchangeStrategies
in initExchangeStrategies
method.
Thanks.
Comment From: rstoyanchev
Thanks for the report.
We could store the result of this.strategies.mutate()
but it would be used only when ExchangeStrategies
is set and it looks like Boot's auto config doesn't do that anymore. Furthermore, such an optimization would be incomplete if we don't apply a similar change for the case where ExchangeStrategies
isn't set, or otherwise we would call ExchangeStrategies.builder()
every time, and if we do cache that too, it would lead to side effects. The same reasoning applies for other fields too that could be less than optimal for creating many WebClient
instances.
Generally, creating 10K+ WebClient
instances is somewhat unexpected, and an application might have to set the WebClient.Builder
settings more explicitly to ensure a more optimal creation.
Comment From: DanilaVaratyntsev
Got you. Thanks for your response. Should I close the issue?
Comment From: rstoyanchev
No problem.