스프링부트

spring boot 가상 스레드 적용해보기

알쓸개잡 2023. 12. 3.

JDK 21부터 가상 스레드(virtual thread)가 정식 릴리즈 되었다. 또한 spring boot 3.2부터는 가상 스레드가 지원되어 spring boot 애플리케이션에서 가상 스레드를 시험해 볼 수 있게 되었다. 이번 포스팅에서는 spring boot 웹 애플리케이션에서 가상 스레드를 적용한 샘플코드를 작성해 보고 jmeter를 통해서 가상 스레드와 플랫폼 스레드의 처리량의 차이를 비교해 보고자 한다.

 

가상 스레드에 대한 내용은 다음 포스팅에서 정리하였다.

2023.11.24 - [자바] - java 21 처리량 향상을 위한 대안 - virtual thread 알아보자

2023.11.26 - [자바] - java21 - scoped value에 대해서 알아보자

 

 

사전 조건

virtual thread를 적용하기 위해서는 JDK 21을 설치해야 한다.

intellij IDE 혹은 SKDMAN을 통해서 손쉽게 JDK 21 버전을 설치할 수 있다.

프로젝트에는 JDK 21을 SDK 버전으로 설정해야 한다.

SDKMAN에 대해서는 다음 포스팅을 참고하기 바란다.

2023.09.02 - [분류 전체보기] - SDKMAN - 개발 도구 손쉽게 관리하기

 

샘플코드

virtual thread 적용을 위한 configuration

내장 톰캣에서 사용하는 스레드를 virtual thread로 적용하기 위해서는 다음과 같이 configuration을 생성한다.

package com.example.spring.virtualthread.config;

import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.task.TaskExecutionAutoConfiguration;
import org.springframework.boot.web.embedded.tomcat.TomcatProtocolHandlerCustomizer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.task.AsyncTaskExecutor;
import org.springframework.core.task.support.TaskExecutorAdapter;

import java.util.concurrent.Executors;

@Configuration
@ConditionalOnProperty(
	value = "application.use-virtual",
	havingValue = "true")
public class ThreadConfig {

	@Bean(TaskExecutionAutoConfiguration.APPLICATION_TASK_EXECUTOR_BEAN_NAME)
	public AsyncTaskExecutor asyncTaskExecutor() {
		return new TaskExecutorAdapter(Executors.newVirtualThreadPerTaskExecutor());
	}

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

@ConditionalOnProperty 어노테이션을 통해서 application.use-virtual=true 인 경우에 가상 스레드가 활성화된다.
application.use-virtual=false인 경우에는 전통적인 플랫폼 스레드가 사용된다.

application:
  # false인 경우 플랫폼 스레드 사용
  use-virtual: true
위와 같이 @Configuration 클래스 작성 필요 없이 다음과 같이 설정 만으로도 virtual thread를 활성화 할 수 있다.
spring.threads.virtual.enabled=true (가상 스레드 활성화)
spring.threads.virtual.enabled=false (가상 스레드 비활성화)

 

 

Service

단순히 blocking I/O를 수행하는 역할을 하는 코드로 5초간 sleep을 주었다.

package com.example.spring.virtualthread.service;

import org.springframework.stereotype.Service;

@Service
public class BlockingIOService {

	public void blocking() throws InterruptedException {
		Thread.sleep(5000);
	}
}

 

 

controller

GET /thread 호출을 통해 가상 스레드 여부와 스레드 정보를 응답하는 단순한 API를 하나 만들었다.

package com.example.spring.virtualthread.controller;

import com.example.spring.virtualthread.service.BlockingIOService;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;


@RestController
@RequestMapping("/thread")
@RequiredArgsConstructor
public class ThreadController {

	private final BlockingIOService blockingIOService;

	@GetMapping
	public ResponseEntity<ThreadInfo> getThreadInfo() throws InterruptedException {
		blockingIOService.blocking();
		return ResponseEntity.ok(
			new ThreadInfo(
				Thread.currentThread().isVirtual(),
				Thread.currentThread().toString())
			);
	}
	public record ThreadInfo(boolean isVirtual, String threadName) {}
}

 

 

API 호출 테스트

application.use-virtual=true 인 경우에 /thread 호출은 다음과 같이 응답하여 가상 스레드가 사용됨을 확인할 수 있다.

{
    "isVirtual": true,
    "threadName": "VirtualThread[#57]/runnable@ForkJoinPool-1-worker-2"
}

 

application.use-virtual=false인 경우에 /thread 호출은 다음과 같이 응답하여 플랫폼 스레드가 사용됨을 확인할 수 있다.

{
    "isVirtual": false,
    "threadName": "Thread[#45,http-nio-8080-exec-2,5,main]"
}

 

처리량 테스트

jmeter를 통해서 가상 스레드와 플랫폼 스레드의 처리량을 비교해 보자.

맥 m1, m2 환경에서 jmeter를 설치하는 방법은 아래 포스팅에 정리하였다.

2023.12.02 - [ETC] - 맥 m1, m2에 jmeter 사용하기

 

jmeter 스레드 그룹 설정

jmeter 스레드 그룹 설정
jmeter 스레드 그룹 설정

1000 개의 스레드를 생성하여 각 스레드가 100초간 반복 호출하도록 설정하였다.

 

http 요청 설정

jmeter http 요청 설정
jmeter http 요청 설정

GET http://localhost:8080/thread API를 호출하도록 설정한다.

가상 스레드 적용 처리량

애플리케이션의 application.use-virtual=true로 설정 후 애플리케이션을 기동 후 jmeter 테스트를 수행한 결과는 다음과 같다.

가상스레드 처리량
가상 스레드 처리량
가상 스레드 응답 시간 그래프
가상 스레드 응답 시간 그래프

가상 스레드의 경우에는 초당 185.6건의 처리량을 보였다.

가상 스레드의 경우 모든 응답 시간은 균일하게 5초가 소요되었다.

제약 없이 스레드를 생성하여 처리할 수 있기 때문에 blocking I/O 작업 동안 다른 스레드의 대기가 발생하지 않기 때문이다.

플랫폼 스레드 적용 처리량

플랫폼 스레드의 경우에는 별도의 thread pool 개수 설정을 하지 않았으므로 디폴트 값이 200개로 수행된다.

애플리케이션의 application.use-virtual=false로 설정 후 애플리케이션을 기동 후 jmeter 테스트를 수행한 결과는 다음과 같다.

플랫폼 스레드 처리량
플랫폼 스레드 처리량
플랫폼 스레드 응답 시간 그래프
플랫폼 스레드 응답 시간 그래프

 

플랫폼 스레드의 경우에는 초당 39.3건의 처리량을 보였다.

200개의 스레드로 제한되어 있기 때문에 blocking I/O 작업 동안 다른 스레드에서의 대기가 발생하기 때문이다.

baeldung 사이트의 글과 비교했을 때 가상 스레드와 플랫폼 스레드의 응답 시간 그래프 모양은 동일하다.

 

간단한 샘플 코드를 통해서 가상 스레드와 플랫폼 스레드 간의 처리량의 차이를 확인해 보았다. 빈번하게 blocking I/O가 발생하는 애플리케이션에 가상 스레드를 적용하면 확실하게 처리량의 향상을 얻을 수 있을 것으로 기대한다.

 


참고 링크

https://www.baeldung.com/spring-6-virtual-threads

https://spring.io/blog/2022/10/11/embracing-virtual-threads

댓글

💲 추천 글