성능 비교 - 스프링 부트 애플리케이션의 스레드 풀과 가상 스레드(Project Loom)

        이 기사에서는 Spring Boot 애플리케이션의 요청 처리에 대한 다양한 접근 방식인 ThreadPool, WebFlux, 코루틴 및 가상 스레드(Project Loom)를 비교합니다.

        이 기사에서는 Spring Boot 애플리케이션에서 사용할 수 있는 다양한 요청 처리 방법의 성능을 간략하게 설명하고 대략적으로 비교해 보겠습니다.
        효율적인 요청 처리는 고성능 백엔드 애플리케이션을 개발하는 데 중요한 역할을 합니다. 전통적으로 대부분의 개발자는 백그라운드에서 요청을 처리하는 데 사용되는 기본 스레드 풀과 함께 Spring Boot 애플리케이션에 포함된 Tomcat을 사용합니다. 그러나 최근 몇 년 동안 대체 방법이 인기를 끌었습니다. WebFlux는 반응형 요청 처리를 위해 이벤트 루프를 활용하며 Kotlin 코루틴과 해당 일시 중지 기능이 인기를 얻고 있습니다.
        또한, 가상 스레드를 도입한 프로젝트 룸(Project Loom)도 자바 21에서 출시될 예정이다. 하지만 아직 Java 21이 출시되지는 않았지만 Java 19부터 Project Loom을 사용해 볼 수 있었습니다. 따라서 이 기사에서는 가상 스레드를 사용하여 요청을 처리하는 방법도 살펴보겠습니다.
        또한 JMeter를 사용하여 고부하 테스트를 위한 다양한 요청 처리 방법의 성능 비교를 수행합니다.

성능 테스트 모델

요청 처리 방법을 비교하기 위해 다음 모델을 사용합니다.

성능 테스트 모델

과정은 케이크만큼 간단합니다.

  • 클라이언트(JMeter)는 500개의 요청을 병렬로 보냅니다. 각 스레드는 다른 요청을 반복적으로 보내기 전에 응답을 기다립니다. 요청 제한 시간은 10초입니다. 테스트 세션은 1분 동안 지속됩니다.
  • 테스트 중인 Spring Boot 애플리케이션은 클라이언트로부터 요청을 받고 느린 서버의 응답을 기다립니다.
  • 느린 서버는 임의의 시간 초과로 응답합니다. 최대 응답 시간은 4초입니다. 평균 응답 시간은 2초입니다.

더 많은 요청을 처리할수록 성능 결과가 더 좋아집니다.

 

1. 스레드 풀

기본적으로 Tomcat은 요청을 처리하기 위해 스레드 풀(연결 풀이라고도 함)을 사용합니다.
개념은 간단합니다. Tomcat에 요청이 오면 요청을 처리하기 위해 스레드 풀에서 스레드를 할당합니다. 이 할당된 스레드는 응답이 생성되어 클라이언트로 다시 전송될 때까지 차단된 상태로 유지됩니다.
기본적으로 스레드 풀에는 최대 200개의 스레드가 포함됩니다. 이는 기본적으로 단일 시점에 200개의 요청만 처리할 수 있음을 의미합니다.
하지만 이 매개변수와 다른 매개변수는 구성 가능합니다.

기본 스레드 풀

Tomcat과 기본 스레드 풀이 내장된 간단한 Spring Boot 애플리케이션의 성능을 측정해 보겠습니다.

스레드 풀은 200개의 스레드를 보유합니다. 각 요청에 대해 서버는 평균 응답 시간이 2초인 다른 서버에 차단 호출을 보냅니다. 따라서 초당 100개의 요청 처리량을 기대할 수 있습니다.

총 요청 수
처리량, 요청/초
응답 시간, 밀리초
오류율,%
평균
가장 작은
90% 라인
최고
3356
91.2
4787
155
6645
7304
0.00

측정된 처리량은 초당 91.2 요청으로 실제 결과가 매우 유사하다는 점은 주목할 가치가 있습니다. 

스레드 풀 늘리기 

애플리케이션 속성을 사용하여 스레드 풀의 최대 스레드 수를 400으로 늘려 보겠습니다.

server:
  tomcat:
    threads:
      max: 400
 
 

테스트를 다시 실행해 보겠습니다.

총 요청 수 처리량, 요청/초 응답 시간, 밀리초 오류율,%
평균 가장 작은 90% 라인 최고
6078 176.7 2549 10 4184 4855 0.00

스레드 풀의 스레드 수를 두 배로 늘리면 처리량이 세 배로 늘어날 것으로 예상됩니다.

그러나 시스템 용량 및 리소스 제약 조건에 관계없이 스레드 풀의 스레드 수를 늘리면 성능, 안정성 및 전체 시스템 동작에 부정적인 영향을 미칠 수 있습니다. 시스템의 특정 요구 사항 및 기능에 따라 스레드 풀 크기를 신중하게 조정하고 최적화하는 것이 중요합니다.

2.웹플럭스

각 요청에 전용 스레드를 할당하는 대신 WebFlux는 종종 이벤트 루프 그룹이라고 하는 소수의 스레드가 있는 이벤트 루프 모델을 사용합니다. 이를 통해 제한된 수의 스레드로 많은 수의 동시 요청을 처리할 수 있습니다. 요청은 비동기적으로 처리되며 이벤트 루프는 비차단 I/O 작업을 사용하여 여러 요청을 동시에 효율적으로 처리할 수 있습니다. WebFlux는 다수의 긴 연결이나 스트리밍 데이터를 처리하는 등 높은 확장성이 필요한 시나리오에 매우 적합합니다.
이상적으로 WebFlux 애플리케이션은 완전히 반응형 방식으로 작성되어야 하지만 때로는 이것이 쉽지 않습니다. 하지만 WebClient를 사용하여 느린 서버를 호출할 수 있는 간단한 애플리케이션이 있습니다.

@Bean
public WebClient slowServerClient() {
    return WebClient.builder()
            .baseUrl("http://127.0.0.1:8000")
            .build();
}

Spring WebFlux의 맥락에서 RouterFunction은 요청 매핑 및 처리에 대한 또 다른 접근 방식입니다.

@Bean
public RouterFunction<ServerResponse> routes(WebClient slowServerClient) {
    return route(GET("/"), (ServerRequest req) -> ok()
            .body(slowServerClient
                  .get()
                  .exchangeToFlux(resp -> resp.bodyToFlux(Object.class)),
                  Object.class
            ));
}

그러나 기존 컨트롤러를 사용하는 것은 여전히 ​​가능합니다.

이제 테스트를 실행해 보겠습니다.

총 요청 수 처리량, 요청/초 응답 시간, 밀리초 오류율,%
평균 가장 작은 90% 라인 최고
7443 219.2 2068년 12 3699 4381 0.00

결과는 스레드 풀을 늘리는 경우보다 훨씬 좋습니다. 그러나 스레드 풀과 WebFlux는 모두 장점과 단점이 있으며 선택은 특정 요구 사항, 작업 부하의 성격, 개발 팀의 전문 지식에 따라 달라진다는 점을 기억하는 것이 중요합니다.

3. 코루틴과 WebFlux

Kotlin 코루틴은 요청 처리에 효과적으로 사용될 수 있으며 보다 동시적이고 비차단 방식으로 대안을 제공합니다.
Spring WebFlux는 요청을 처리하기 위해 코루틴을 지원하므로 이러한 컨트롤러를 작성해 보겠습니다.

@GetMapping
suspend fun callSlowServer(): Flow<Any> {
    return slowServerClient.get().awaitExchange().bodyToFlow(String::class)
}

일시 중단 함수는 기본 스레드를 차단하지 않고 장기 실행 또는 차단 작업을 수행할 수 있습니다. Kotlin 코루틴 기본 문서에서는 기본 사항을 잘 설명합니다.

이제 테스트를 다시 실행해 보겠습니다.

총 요청 수 처리량, 요청/초 응답 시간, 밀리초 오류율,%
평균 가장 작은 90% 라인 최고
7481 220.4 2064년 5 3615 4049 0.00

대략적으로 결과는 코루틴이 없는 WebFlux 애플리케이션의 경우와 다르지 않다는 결론을 내릴 수 있습니다.

그러나 코루틴과 함께 동일한 WebFlux가 사용되면 테스트에서 코루틴의 잠재력이 완전히 밝혀지지 않았을 수 있습니다. 다음에는 Ktor를 시도해 볼 가치가 있습니다 .

4. 가상 스레드(Project Loom)

가상 스레드 또는 파이버는 Project Loom에서 도입한 개념입니다.
가상 스레드는 기본 스레드보다 메모리 공간이 훨씬 적으므로 애플리케이션이 시스템 리소스를 소모하지 않고도 더 많은 수의 스레드를 생성하고 관리할 수 있습니다. 스레드 생성과 관련된 오버헤드를 줄여 더 빠르게 생성 및 전환할 수 있습니다.
가상 스레드 실행 전환은 JVM(Java Virtual Machine)에 의해 내부적으로 처리되며 다음 위치에서 수행될 수 있습니다.
자발적 일시 중단: 가상 스레드는 Thread.sleep() 또는 CompletableFuture.await( ). 가상 스레드가 자체적으로 일시 중단되면 실행이 일시적으로 중단되고 JVM은 다른 가상 스레드 실행으로 전환할 수 있습니다.
차단 작업: 가상 스레드가 I/O 대기 또는 잠금 획득과 같은 차단 작업에 직면하면 자동으로 일시 중단될 수 있습니다. 이를 통해 JVM은 기본 원시 스레드를 사용하여 실행할 준비가 된 다른 가상 스레드를 실행함으로써 이를 보다 효율적으로 활용할 수 있습니다.
가상 스레드 및 캐리어 스레드 주제에 관심이 있다면 DZone - Java Virtual Threads - A Brief Introduction에 대한 훌륭한 기사를 읽어보세요.
가상 스레드는 Java 21에서 마침내 출시될 예정이지만 Java 19부터 테스트할 수 있었습니다. JVM 옵션을 명시적으로 지정하기만 하면 됩니다.

기본적으로 우리가 해야 할 일은 Tomcat 스레드 풀을 일부 가상 스레드 기반 실행기로 바꾸는 것입니다.

@Bean
public TomcatProtocolHandlerCustomizer<?> protocolHandler() {
    return protocolHandler ->
            protocolHandler.setExecutor(Executors.newVirtualThreadPerTaskExecutor());
}

그래서 우리는 스레드 풀 실행기 대신 가상 스레드를 사용하기 시작했습니다.

테스트를 실행해 보겠습니다.

총 요청 수 처리량, 요청/초 응답 시간, 밀리초 오류율,%
평균 가장 작은 90% 라인 최고
7427 219.3 2080 12 3693 4074 0.00

결과는 실제로 WebFlux와 동일하지만 반응형 기술을 전혀 사용하지 않습니다. 느린 서버에서 호출하는 경우에도 일반 차단 RestTemplate을 사용하세요. 우리가 한 일은 스레드 풀을 가상 스레드 실행기로 교체한 것뿐입니다.

결론적으로

테스트 결과를 테이블로 수집해 보겠습니다.

요청 핸들러
30초 이내의 총 요청 수
처리량, 요청/초 응답 시간, 밀리초 오류율,%
평균 가장 작은 90% 라인 최고
스레드 풀(200개 스레드) 3356 91.2 4787 155 6645 7304 0.00
스레드 풀 늘리기(400개 스레드) 6078 176.7 2549 10 4184 4855 0.00
웹플럭스 7443 219.2 2068년 12 36999 4381 0.00
WebFlux + 코루틴 7481 220.4 2064년 5 3615 4049 0.00
가상 스레드(Project Loom) 7427 219.3 2080 12 3693 4074 0.00

이 기사에서 수행된 성능 테스트는 피상적이지만 몇 가지 예비 결론을 도출할 수 있습니다.

  • 스레드 풀의 성능 결과가 좋지 않습니다. 스레드 수를 늘리면 성능이 향상될 수 있지만 시스템의 용량 및 리소스 제약을 고려해야 합니다. 그럼에도 불구하고 스레드 풀은 특히 많은 차단 작업을 처리할 때 실행 가능한 옵션입니다.
  • WebFlux는  매우 좋은 결과를 보여줍니다. 그러나 성능을 최대한 활용하는 것은 주목할 가치가 있습니다. 전체 코드는 반응형 스타일로 작성되어야 합니다. 
  • 코루틴을 WebFlux와 결합하면 비슷한 성능 결과를 얻을 수 있습니다. 아마도 코루틴의 기능을 활용하도록 특별히 설계된 프레임워크인 Ktor를 사용하여 시도해 봐야 할 것입니다. 
  • 가상 스레드(Project Loom)를 사용해 도 비슷한 결과가 나왔습니다. 그러나 코드를 수정하거나 반응 기술을 사용하지 않았다는 점은 주목할 가치가 있습니다. 유일한 변경 사항은 스레드 풀을 가상 스레드 실행기로 교체하는 것입니다. 단순함에도 불구하고 스레드 풀을 사용하는 것에 비해 성능 결과가 크게 향상되었습니다. 

따라서 Java 21의 가상 스레드 출시로 인해 기존 서버 및 프레임워크의 요청 처리 방법이 크게 변경될 것이라는 결론을 내릴 수 있습니다.

추천

출처blog.csdn.net/qq_28245905/article/details/132312136