본문 바로가기

제안&정리

[Java] Thread Pool의 적정 Queue 사이즈

잘못된 Queue 사이즈로 인한 문제 상황

최근 회사에서 새로운 기능을 배포했는데 특정 인스턴스들에서 간헐적으로 RejectedExecutionException이 발생했습니다.

 

예외 상세 메세지는 아래와 같았는데요.

org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor$1@d02af99[Running, pool size = 1000, active threads = 8, queued tasks = 100, completed tasks = 6805414]

 

보시면 pool size에 비해 active thread가 여유가 있는데도 예외가 발생했습니다.

분석 시 문제는 Thread Pool에 설정된 coreSize, queueSize와 관련이 있었는데요.

 

아래와 같은 시나리오로 조건이 충족된 인스턴스들에서 문제가 발생했던 것이었습니다.

(Thread Pool의 maxSize=1000, coreSize=400, queueSize=100 일때)

  1. 한 인스턴스에 많은 쓰레드를 요하는 요청들이 동시 발생
  2. Thread Pool 의 쓰레드 수가 400(coreSize)을 넘어감 - 이 조건 만족 시 무조건 queue에 넣었다가 실행
  3. 100개(queueSize)보다 많은 수의 쓰레드를 동시에 생성하는 요청이 문제 인스턴스로 유입
  4. Thread Pool에 queue가 100개(queueSize) 넘어가면서 예외 발생

queue로 한꺼번에 많은 요청을 넣었던 코드는 아래와 같습니다.

CompletableFuture<Foo> fooFutures = largeList.stream()
        .map(service.asyncAction)
        .toList();

 

적정 Thread Pool Queue Size

이번 장애를 겪으면서 Queue Size는 maxSize과 비슷하거나 커야 한다고 판단했습니다.

그 이유는 queueSize가 maxSize보다 적다면 갑자기 maxSize만큼의 async 실행이 발생할 때 queueSize가 병목지점이 되어 maxSize를 다 활용할 수 없기 때문입니다.

 

maxSize 큰데 얼마나 커야 할지는 서버 자원, 서비스의 성격, 평균 처리 속도에 따라 달라집니다.

백오피스 성격이라 서버 자원도 넉넉하고 오래 걸려도 실패하지 않는게 중요하다면 maxSize의 수십배까지도 queueSize를 설정할 수 있습니다.

반대로 사용자에게 빨리 실패를 알려줄 필요가 있는 서비스라면 딱 maxSize 정도로 설정하는게 좋습니다. 빠른 실패 후 재시도 한다면 정상인 다른 서버로 연결 후 성공할 수도 있으니까요.