스프링의 쓰레드 풀 관리

크리링·2024년 9월 21일
0

궁금증

목록 보기
2/2
post-thumbnail

만들어놓은 스레드 풀을 초과하는 요청이 동시에 들어온다면 어떻게 될까?
지난 글에서의 고민에서 시작되어서 멀티 쓰레드 상황에 동시성 문제가 생기면 어떻게 쓰레드 풀을 관리해야되는지 궁금증이 생겨서 찾아본 내용들을 정리해봤다.



스프링의 기본 쓰레드 풀 관리 동작과 설정

기본 동작

  1. 기본 설정된 개수 만큼 쓰레드 생성
  2. 기본 생성 스레드 수를 초과하는 요청이 들어오면 작업이 큐에 쌓인다.
  3. 큐의 용량 수를 초과하면 그때 쓰레드를 추가 생성한다.

기본 설정

  • corePoolSize : 8
    • 쓰레드 풀의 기본 쓰레드 수 (항상 유지)
  • maxPoolSize : Integer.MAX_VALUE
    • 최대 쓰레드 수, corePoolSize를 초과하는 요청이 들어오고 큐가 가득 찰 때까지 쓰레드 수를 늘릴 수 있다.
  • queueCapacity : Integer.MAX_VALUE
    • 쓰레드가 바쁘면 요청을 대기열에 넣을 수 있다. 대기열이 가득 차면 새로운 쓰레드 생성
  • keepAliveSeconds : 60
    • 추가로 생성된 쓰레드가 유휴 상태로 유지되는 시간, 시간이 지나면 쓰레드가 제거된다.



쓰레드 관리

만들어놓은 스레드 풀을 초과하는 요청이 동시에 들어온다면 어떻게 될까?
첫 물음에서 queueCapacity가 Integer.MAX_VALUE 값을 가지고 있기 때문에 기본 설정에서는 웬만하면 추가적인 쓰레드를 생성할 일이 없다.
상황에 따른 쓰레드풀 관리가 중요해 보인다.

@Configuration
public class ThreadPoolConfig {

    @Bean
    public ThreadPoolTaskExecutor taskExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(10);
        executor.setMaxPoolSize(20);
        executor.setQueueCapacity(50);
        executor.setKeepAliveSeconds(60);
        executor.setThreadNamePrefix("MyExecutor-");
        executor.initialize();
        return executor;
    }
}

위와 같은 방식으로 스레드 풀을 커스터마이징 할 수 있다.
참고로 keepAliveTime 설정은 유휴 상태가 지속되면 종료될 수 있게 만든다. 이는 쓰레드 풀이 더 이상 필요하지 않은 쓰레드를 해제하여 자원을 절약하기 위한 메커니즘이다.



개인적으로 두가지가 더 궁금해진다.
1. queueCapacity를 0으로만들고 maxPoolSize를 무한히 유지하면 굳이 큐에 대기할 필요없지 않을까?
2. 요청이 예상보다 너무 많아서 maxPoolSize를 초과하면 어떻게 될까?

1. queueCapacity = 0 & maxPoolSize = 무한대

당연히 시스템 자원적인 측면에서 좋아보이지는 않아보인다.
실제로 쓰레드를 생성할 때마다 메모리CPU 자원이 필요한데 쓰레드의 스택 크기는 보통 KB에서 MB까지 할당되므로 많아질수록 메모리 소모가 커지고, 쓰레드가 많아지면 CPU 스케줄링에 대한 오버헤드가 증가하고 컨텍스트 스위칭이 빈번해지면서 자원 소모
OutOfMemoryError 발생 가능성 농후

쓰레드 풀을 사용하는 이유는 쓰레드 재사용을 통해 자원을 절약하고 성능을 최적화하기 위한 장치 제한된 수의 쓰레드를 관리하면서 Queue를 통해 요청을 적절히 조절하는게 효율적



2. maxPoolSize를 초과하는 동시 요청

  • corePoolSize : 10
  • queueCapacity : 50
  • maxPoolSize : 100

쓰레드 풀 설정에서 만약 500개의 동시 요청이 일어났다고 가정했을때 순서대로 일어나는 일을 요약해보면

  1. corePoolSize 10개의 쓰레드가 작업 진행
  2. queueCapacity 50개 만큼의 요청 대기
  3. maxPoolSize 100을 채우기 위해 90개의 쓰레드 생성
  4. queue에 쌓여있는 작업 50개와 새로운 40개의 작업 새 생성된 쓰레드에 요청
  5. queue가 비어있는 상태이므로 새로운 작업 50개가 큐에 쌓임
  6. 350개의 요청이 처리되지 못하고 거부됨
  • 거부 정책은 커스터마이징 가능
    • AbortPolicy : 기본 정책으로 예외를 발생시킴
    • CallerRunsPolicy : 현재 실행 중인 스레드가 직접 작업을 처리
    • DiscardPolicy : 처리할 수 없는 요청을 무시합니다.
    • DiscardOldestPolicy : 가장 오래된 요청을 대기열에서 제거하고, 새로운 요청 추가

0개의 댓글