How to properly warmup connection pool
See original GitHub issueHey team. I am creating an application that needs to call another microservice, and add the minimum possible amount of latency doing so. So for that I wanted to give netty and the Spring webclient a try.
On the first try i saw that calling the first time the netty version was much slower than the simple RestTemplate based ones, after some debugging and trace logging I realized that the reason this initial call was so slow is because the connection pool is only created on the first call.
I tried adding a warmup call, which helped but I find it a not so elegant solution: Without warmup
2020-03-09 22:28:10.696 INFO 4483 --- [nio-8080-exec-1] com.example.demo.ControllerConfigs : Total took 317
2020-03-09 22:28:14.647 INFO 4483 --- [nio-8080-exec-2] com.example.demo.ControllerConfigs : Total took 16
2020-03-09 22:28:15.746 INFO 4483 --- [nio-8080-exec-4] com.example.demo.ControllerConfigs : Total took 14
2020-03-09 22:28:17.923 INFO 4483 --- [nio-8080-exec-6] com.example.demo.ControllerConfigs : Total took 14
With warmup:
2020-03-09 22:29:05.307 INFO 4489 --- [nio-8080-exec-1] com.example.demo.ControllerConfigs : Total took 32
2020-03-09 22:29:09.161 INFO 4489 --- [nio-8080-exec-2] com.example.demo.ControllerConfigs : Total took 14
2020-03-09 22:29:09.558 INFO 4489 --- [nio-8080-exec-3] com.example.demo.ControllerConfigs : Total took 13
2020-03-09 22:29:09.817 INFO 4489 --- [nio-8080-exec-4] com.example.demo.ControllerConfigs : Total took 14
2020-03-09 22:29:09.992 INFO 4489 --- [nio-8080-exec-6] com.example.demo.ControllerConfigs : Total took 13
Is there anything else I can do to optimize the first call performance? In my production I am running the app with very low CPU settings, so the difference is between 200ms for a normal call and 6-8 seconds for the initial one.
package com.example.demo
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.reactive.awaitSingle
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withContext
import org.slf4j.Logger
import org.slf4j.LoggerFactory
import org.springframework.beans.factory.annotation.Value
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.http.client.reactive.ReactorClientHttpConnector
import org.springframework.web.bind.annotation.PostMapping
import org.springframework.web.bind.annotation.RequestBody
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RestController
import org.springframework.web.client.RestTemplate
import org.springframework.web.reactive.function.BodyInserters
import org.springframework.web.reactive.function.client.WebClient
import reactor.core.publisher.Flux
import reactor.netty.http.client.HttpClient
import reactor.netty.resources.ConnectionProvider
@RestController
@RequestMapping("/sync")
class SyncController(@Value("\${target}") private val target: String, private val restTemplate: RestTemplate) {
@PostMapping
fun call(@RequestBody body: String): List<String> {
val (time, result) = measureTimeMillis {
body.split("+").fold(arrayListOf<List<String>>(arrayListOf())) { acc, s ->
acc.add(acc.last().plus(s)); acc
}.map { it.joinToString(" ") }
.map {
val (time, result) = measureTimeMillis {
restTemplate.postForEntity(target, it, String::class.java)
.body!!
}
LOG.info("Call for $it took $time")
result
}
}
LOG.info("Total for took $time")
return result
}
companion object {
val LOG: Logger = LoggerFactory.getLogger(SyncController::class.java)
}
}
@RestController
@RequestMapping("/coroutine")
class CoroutineController(@Value("\${target}") private val target: String, private val webClient: WebClient) {
init {
runBlocking {
webClient.post()
.uri(target)
.body(BodyInserters.fromValue("warmup"))
.exchange()
.flatMap { it.toEntity(String::class.java) }
.map { it.body!! }
.awaitSingle()
}
}
@PostMapping
fun call(@RequestBody body: String): List<String> {
val (time, result) = measureTimeMillis {
runBlocking {
withContext(Dispatchers.IO) {
body.split("+").fold(arrayListOf<List<String>>(arrayListOf())) { acc, s ->
acc.add(acc.last().plus(s)); acc
}.map { it.joinToString(" ") }
.map {
webClient.post()
.uri(target)
.body(BodyInserters.fromValue(it))
.exchange()
.flatMap { it.toEntity(String::class.java) }
.map { it.body!! }
.awaitSingle()
}
}//.map { it.awaitSingle() }
}
}
LOG.info("Total took $time")
return result
}
companion object {
val LOG: Logger = LoggerFactory.getLogger(ControllerConfigs::class.java)
}
}
@Configuration
class ControllerConfigs {
@Bean
fun restTemplate(): RestTemplate = RestTemplate()
@Bean
fun webClient(configuredHttpClient: HttpClient): WebClient =
WebClient
.builder()
.clientConnector(ReactorClientHttpConnector(configuredHttpClient))
.build()
@Suppress("ComplexMethod")
@Bean
fun configuredHttpClient(
): HttpClient = HttpClient
.create()
companion object {
val LOG: Logger = LoggerFactory.getLogger(CoroutineController::class.java)
}
}
inline fun <T> measureTimeMillis(block: () -> T): Pair<Long, T> {
val start = System.currentTimeMillis()
val t = block()
return Pair(System.currentTimeMillis() - start, t)
}
Issue Analytics
- State:
- Created 4 years ago
- Reactions:5
- Comments:13 (6 by maintainers)
Top Related StackOverflow Question
Isn’t this issue duplicate of this one? https://github.com/reactor/reactor-netty/issues/560
https://github.com/reactor/reactor-netty/pull/1455#issuecomment-776842252