Daily Develop

ThreadPoolTaskExecutor Queue가 full의 처리 정책

에디개발자 2021. 12. 21. 07:00
반응형

나를 닮았다고 한다...

ThreadPoolTaskExecutor의 RejectedExecutionHandler 설정에 대해서 간단히 살펴보겠습니다.

이번글에서는 RejectedExcecutionHandler의 정책을 설정하는 기준과 설정 시 어떤 결과값이 나오는지 살펴보겠습니다.

 

RejectedExcutionHandler의 종류

  • AboryPolicy
  • CallerRunsPolicy
  • DiscardPolicy
  • DiscardOldestPolicy

 

ThreadPoolTaskExecutor 기본 설정

@Configuration
public class TestThreadPool {

    @Bean
    public TaskExecutor test() {
        var executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(50);   // 최소 유지 Pool Size
        executor.setMaxPoolSize(200);    // 최대 Pool Size
        executor.setQueueCapacity(100);  // 최대 Pool Size가 초과 후 사용되는 Queue
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.AbortPolicy());  // Queue를 full로 사용 후 이후 요청처리 방식
        return executor;
    }

}

위처럼 쓰레드풀을 설정할 경우 아래와 같이 작동됩니다.

  1. Request 500개 요청
  2. ThreadPool 300개 먼저 사용
  3. Queue 100개 사용
  4. 나머지 100개의 요청에 대해서 어떻게 처리할 지 RejectedExcutionHandler에서 지정된 정책으로 처리합니다.

 

ThreadPoolExecutor.AbortPolicy

처리되지 못한 100개의 요청에 대해서 org.springframework.core.task.TaskRejectedException 발생되며 요청을 무시합니다.

 

 

ThreadPoolExecutor.CallerRunsPolicy

처리되지 못한 100개의 요청을 ThreadPool을 호출한 Thead에서 처리하게 됩니다.

  • 만약 Tomcat 서버에서 ThreadPool을 호출했다면 Tomcat thread가 요청을 처리합니다.

 

ThreadPoolExecutor.DiscardPolicy

처리되지 못한 100개의 요청을 조용히 무시합니다. 

  • AbortPolicy와 다른점은 Exception을 발생시키지 않고 버립니다.

 

ThreadPoolExecutor.DiscardOldestPolicy

가장 오래된 요청은 삭제하고 다시 execute() 실행

 

 

그렇다면 여기서 궁금한점?

만약 클라이언트에서 호출 후 결과값을 기다린다는 가정이라면 어떨까?

@RestController
@RequiredArgsConstructor
public class TestController {

    private final TaskExecutor executor;

    @GetMapping("/test1")
    public CompletableFuture<ApiResponse<Integer>> teset() {
        return CompletableFuture.supplyAsync(() ->
            getResult(), executor
        ).exceptionally(e -> {
            return ApiResponse.success(1);
        });
    }


    private ApiResponse<Integer> getResult() {
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return ApiResponse.success(1);
    }

}

위 코드를 간략히 설명하면 하나의 Thread에서 처리하는 시간이 길고 클라이언트에서 결과값을 바라는 코드입니다.

 

AbortPolicy와 CallerRunsPolicy를 사용할 경우 어떻게든 결과값을 받게됩니다. 

  • AbortPolicy: Exception
  • CallerRunsPolicy: 응답이 느려질 수 있으나 받을 수 있음

 

그렇다면 DiscardPolicy와 DiscardOldestPolicy는??

결과는 AboryPolicy와 마찬가지로 Exception을 받지만 아래와 Timeout 관련 Exception이 발생합니다. 

org.springframework.web.context.request.async.AsyncRequestTimeoutException: null

 

 

결론

반드시 처리되어야 하는 경우 CallerRunsPolicy를 사용하면 되겠네요. 하지만 Main Thread에 영향을 끼치기 때문에 성능 테스트를 다양하게 하여 Main Thread에도 최소한의 영향을 끼치는 수준으로 설정해야합니다. 

 

클라이언트에서 요청을 원하고 Exception 처리가 필요한 경우는 Discard 보단 AbortPolicy를 사용하는 편이 좋다고 생각합니다. Timeout은 클라이언트에서 요청을 기다리다가 Exception이 발생하기 때문에 속도측면에서 불리하게 작용될 것입니다. 

반응형