diff --git a/apps/user-service/src/main/java/site/icebang/global/config/asnyc/AsyncConfig.java b/apps/user-service/src/main/java/site/icebang/global/config/async/AsyncConfig.java similarity index 57% rename from apps/user-service/src/main/java/site/icebang/global/config/asnyc/AsyncConfig.java rename to apps/user-service/src/main/java/site/icebang/global/config/async/AsyncConfig.java index 03fa1496..ac059905 100644 --- a/apps/user-service/src/main/java/site/icebang/global/config/asnyc/AsyncConfig.java +++ b/apps/user-service/src/main/java/site/icebang/global/config/async/AsyncConfig.java @@ -1,31 +1,42 @@ -package site.icebang.global.config.asnyc; +package site.icebang.global.config.async; import java.lang.reflect.Method; +import java.util.concurrent.Executor; import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.core.task.SimpleAsyncTaskExecutor; +import org.springframework.core.task.TaskDecorator; import org.springframework.core.task.support.ContextPropagatingTaskDecorator; import org.springframework.scheduling.annotation.AsyncConfigurer; import org.springframework.scheduling.annotation.EnableAsync; -import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; +import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @Slf4j @Configuration @EnableAsync +@RequiredArgsConstructor public class AsyncConfig implements AsyncConfigurer { + private final SemaphoreTaskDecorator semaphoreTaskDecorator; + @Bean("traceExecutor") - public ThreadPoolTaskExecutor traceExecutor() { - ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); - executor.setCorePoolSize(10); - executor.setMaxPoolSize(50); - executor.setQueueCapacity(100); - executor.setTaskDecorator(new ContextPropagatingTaskDecorator()); // 필수 + public Executor traceExecutor() { + SimpleAsyncTaskExecutor executor = new SimpleAsyncTaskExecutor(); + executor.setVirtualThreads(true); executor.setThreadNamePrefix("trace-"); - executor.initialize(); + + // MDC 전파 데코레이터 생성 + TaskDecorator contextDecorator = new ContextPropagatingTaskDecorator(); + + // 두 데코레이터의 조합: + // Context 설정(MDC 복사) 후 Semaphore 제어가 적용되도록 구성 + executor.setTaskDecorator( + runnable -> contextDecorator.decorate(semaphoreTaskDecorator.decorate(runnable))); + return executor; } diff --git a/apps/user-service/src/main/java/site/icebang/global/config/async/SemaphoreTaskDecorator.java b/apps/user-service/src/main/java/site/icebang/global/config/async/SemaphoreTaskDecorator.java new file mode 100644 index 00000000..e6364bb3 --- /dev/null +++ b/apps/user-service/src/main/java/site/icebang/global/config/async/SemaphoreTaskDecorator.java @@ -0,0 +1,41 @@ +package site.icebang.global.config.async; + +import java.util.concurrent.Semaphore; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.core.task.TaskDecorator; +import org.springframework.stereotype.Component; + +import jakarta.annotation.PostConstruct; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +@Component +public class SemaphoreTaskDecorator implements TaskDecorator { + + @Value("${spring.datasource.hikari.maximum-pool-size:10}") + private int maximumPoolSize; + + private Semaphore semaphore; + + @PostConstruct + public void init() { + this.semaphore = new Semaphore(maximumPoolSize); + log.info("SemaphoreTaskDecorator 초기화: 동시 실행 제한 수(maximumPoolSize) = {}", maximumPoolSize); + } + + @Override + public Runnable decorate(Runnable runnable) { + return () -> { + try { + semaphore.acquire(); + runnable.run(); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + log.error("비동기 작업 실행 대기 중 인터럽트 발생", e); + } finally { + semaphore.release(); + } + }; + } +} diff --git a/apps/user-service/src/main/resources/application.yml b/apps/user-service/src/main/resources/application.yml index f6302bc7..70a99661 100644 --- a/apps/user-service/src/main/resources/application.yml +++ b/apps/user-service/src/main/resources/application.yml @@ -1,6 +1,9 @@ spring: application: name: mvp + threads: + virtual: + enabled: true profiles: active: develop test: