시작이 반

[Kotlin] SpringBoot + Coroutine 성능테스트 본문

Programming/Kotlin

[Kotlin] SpringBoot + Coroutine 성능테스트

G_Gi 2023. 6. 6. 20:03
SMALL
  • Tool : Jmeter
  • 환경 : CPU 4Core 8Thread 
  • 1000명의 유저가 30초가 걸리는 api를 동시 호출

 

TEST1

구성 : netty + coroutine ( context : Dispatchers.IO )

@PostMapping("/api/test")
suspend fun testApi():MutableMap<String, Any> = withContext(Dispatchers.IO){
    log.info(">>>>>>>>>>>> start suspend")
    performService.measureTestPerform()
}
suspend fun measureTestPerform(): MutableMap<String, Any> = withContext(Dispatchers.IO) {
    val map = mutableMapOf<String, Any>()

    val time = measureTimeMillis {
        val deferredValue = async { busyApi() }

        val await = deferredValue.await()

        map.putAll(await)
    }

    map["Time"] = time

    map
}

suspend fun busyApi(): MutableMap<String, Any> {
    delay(30000)

    val map = mutableMapOf<String, Any>()
    map["A"] = "a"
    map["B"] = "b"
    map["C"] = "c"

    log.info(">>>>>>>>>> finish call api busy Test <<<<<<<<<<<")
    return map
}

Dispathcers.IO는 필요에 따라 추가적으로 스레드를 더 생성하거나 줄일 수 있으며 최대 64개까지 생성이 가능하다.

또한 Default Dispatcher와 스레드를 공유하기 때문에 switching으로 인한 오버헤드를 일으키지 않는다고 한다.

스레드 번호가 64를 초과해서 찍히는 것은 Default Dispatcher와 스레드를 공유하기에 발생하는 현상이다.

TPS는 940정도 나왔다.

coroutine을 사용하였기 때문에 block당하지 않았다. 때문에 모든 호출이 끝나는 시간은 약30초가 걸렸다.

 

 

TEST2

구성 : netty + coroutine ( context : Dispatchers.Default )

@PostMapping("/api/test3")
suspend fun testApi3():MutableMap<String, Any> = coroutineScope{
    log.info(">>>>>>>>>>>> start suspend")
    performService.measureTestPerform3()
}
suspend fun measureTestPerform3(): MutableMap<String, Any> = coroutineScope {
    val map = mutableMapOf<String, Any>()

    val time = measureTimeMillis {
        val deferredValue = async { busyApi() }

        val await = deferredValue.await()

        map.putAll(await)
    }

    map["Time"] = time

    map
}
suspend fun busyApi(): MutableMap<String, Any> {
    delay(30000)

    val map = mutableMapOf<String, Any>()
    map["A"] = "a"
    map["B"] = "b"
    map["C"] = "c"

    log.info(">>>>>>>>>> finish call api busy Test <<<<<<<<<<<")
    return map
}

Dispathcers.Default는 코어 스레드 수만큼 스레드풀이 생긴다.

4Core 8Thread CPU 서버이기 때문에 8개의 스레드 풀이 생겨서 실행되었다.

TPS는 730정도 나왔다.

coroutine을 사용하였기 때문에 block당하지 않았다. 때문에 모든 호출이 끝나는 시간은 약30초가 걸렸다.

 

 

TEST3

구성 : netty

@PostMapping("/api/test2")
fun testApi2():MutableMap<String, Any>{
    log.info(">>>>>>>>>>>> start normal")
    return performService.measureTestPerform2()
}
fun measureTestPerform2(): MutableMap<String, Any> {

    val map = mutableMapOf<String, Any>()

    val stopWatch = StopWatch()

    stopWatch.start()
    val value = busyApi2()
    log.info(">>>>>>>>>> finish call api busy Test <<<<<<<<<<<")
    stopWatch.stop()

    map.putAll(value)

    map["Time"] = stopWatch.totalTimeMillis

    return map
}

fun busyApi2(): MutableMap<String, Any> {
    sleep(30000)

    val map = mutableMapOf<String, Any>()
    map["A"] = "a"
    map["B"] = "b"
    map["C"] = "c"

    return map
}

springBoot Netty서버는 기본적으로 스레드 풀이 cpu 스레드 수이다 때문에 8개씩 호출되고 호출이 끝나면 다음 호출이 이어서 호출된다.

결과는 너무 오래 걸려서 중간에 끊었다.

TPS는 100도 못넘긴걸로 보여진다...

block되는 것을 볼 수 있다. 1000명의 유저로 테스트를 진행하였는데 해당 테스트가 모두 끝날려면 많은 시간(약 1000/8 * 30s)이 걸릴것이다... 실패 호출도 많았다.

 

 

 

TEST4

구성 : tomcat + coroutine ( context : Dispatchers.IO )

TEST1과 소스 코드 동일

TEST1과 같은 성능이 나왔다. 스레드 풀도 TEST1과 동일하게 사용하였다.

680TPS정도 나왔다.

coroutine을 사용하였기 때문에 block당하지 않았다. 때문에 모든 호출이 끝나는 시간은 약30초가 걸렸다.

 

 

 

TEST5

구성 : tomcat + coroutine ( context : Dispatchers.Default )

TEST2와 소스 코드 동일

SpringBoot에서 tomcat의 기본설정으로 스레드 풀의 개수는 200개이다. 

때문에 200개의 스레드에서 해당 api가 호출된 것을 확인할 수 있다.

680TPS 정도 나왔다.

coroutine을 사용하였기 때문에 block당하지 않았다. 때문에 모든 호출이 끝나는 시간은 약30초가 걸렸다.

 

 

TEST6

구성 : tomcat 

TEST3과 소스코드 동일

TEST3보다는 빨리 끝났다

spring boot를 tomcat으로 실행시키면 기본적으로 스레드 풀이 200개이다.

때문에 200명이 api를 호출이 끝나면 다음 사람이 스레드를 얻어 해당 api를 호출한다.

(1000 / 200) * 30  초의 시간이 걸렸다.

TPS는 최대 197정도 나왔다.

LIST