Scheduler와 다른 비동기 작업들의 스레드풀 분리

최인준·2024년 3월 19일
0
post-thumbnail

서론

이번 프로젝트에서 Scheduler 작업이 필요한 요구사항이 있었다.

그래서 Scheduler모듈을 추가하여 구현을 진행하였다.

별도의 설정 없이 Scheduler를 사용한다면 Springboot가

기본으로 할당해주는 스레드 한개에서만 스케줄링 작업이 수행된다.

즉, 비동기로 처리하는 것이 디폴트가 아닌 것이다.

그래서 @Async 어노테이션을 스케줄링 작업에 적용하여 비동기로 처리되도록 하였다.

하지만 고민해보니 문제가 될 수 있는 부분이 있다 생각했고 몇몇 추가 작업을 진행하였다.

이번 포스팅에서는 어떤 문제가 있고 이를 어떻게 처리했는 지 소개 할 예정이다.

기존의 비동기 처리 방식과 문제점

스케줄링 작업을 구현하기 이전에 비동기 작업으로 구현해야 하는 것이 있어 비동기 관련 스레드풀을 정의해놓은상태였다. 설정은 다음과 같다.

@Configuration
@EnableAsync
public class AsyncConfig {

    private final static int CORE_POOL_SIZE = 3;
    private final static int MAX_POOL_SIZE = 10;
    private final static int QUEUE_CAPACITY = 30;

    @Bean(name = "asyncTask")
    public Executor threadPoolTaskExecutor() {
        ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();
        taskExecutor.setCorePoolSize(CORE_POOL_SIZE); // 기본 스레드 수
        taskExecutor.setMaxPoolSize(MAX_POOL_SIZE); // 최대 스레드 수
        taskExecutor.setQueueCapacity(QUEUE_CAPACITY); // Queue 사이즈
        taskExecutor.setThreadNamePrefix("async-thread-");
        return taskExecutor;
    }
}

이와 같이 기존에 만들어놓은 스레드풀 설정이 있기에 비동기 스케줄링 작업을 구현할 때 이 스레드풀을 활용하면

된다고 생각했다. 그래서 많은 생각을 하지 않고 다음과 같이 스케줄러를 구현했다.

@Async(value = "asyncTask")
@Scheduled(cron = "0 0 0 * * *")
public void updateSignal(){
		log.info("current thread : {}", Thread.currentThread().getName());
    //logic 
}

그리고 어플리케이션을 실행하면 로그가 다음과 같이 잘 나온다.

정상적으로 잘 작동하여 끝났다라고 생각하려 했지만..! 문득 생각이 든게 있었다.😳

core 모듈에서도 비동기 작업이 꽤 일어나는데 scheduler모듈에서도 같은 스레드풀을 사용하는게 괜찮을까?

물론 괜찮다. 지나쳐도 되는 생각이고 문제점이 아니라고 생각할 수도 있다.

하지만 진행중인 프로젝트의 경우, 스케줄러가 매일 자정에 한번만 실행되고 있고 추후에 리팩토링을 하면서 스케줄링 작업이 추가 될 여지가 있다.

모듈마다 비동기 작업이 있는 상황인건데 하나의 스레드풀을 공유하기엔 여러 모듈을 한번에 고려하여 스레드풀을 설정하는 것이 애매하고 프로젝트 규모가 늘어날 수록 점점 더 복잡해질 것 같다고 생각했다.

또한, 스케줄러 비동기 작업을 처리하다가 스레드풀에 문제가 생기게 되면 다른 비동기 작업들까지 영향을 받게 된다. 그래서 스레드풀을 분리하는 결정을 내렸다.

위에서 언급한 문제점을 기반으로 스레드풀 분리로 얻을 수 있는 장점을 다음과 같이 정리해 보았다.

  1. 작업 특성: 비동기 작업은 특성이 다 다를 수 있다. 별도의 스레드 풀을 가짐으로써 처리할 작업의 특성에 맞게 각 풀의 구성을 맞춤화할 수 있다.
  2. 리소스 관리: 작업을 별도의 스레드 풀로 분리하면 시스템 리소스를 더 잘 관리할 수 있다. 예를 들어, 타이트하게 성능 요구 사항을 충족해야 하는 중요한 작업에는 더 많은 스레드를 할당하고, 중요하지 않은 작업에는 더 적은 스레드를 할당하여 리소스 충돌을 방지하고 전반적인 시스템 안정성을 보장할 수 있다.
  3. 격리 및 오류 격리: 작업을 여러 스레드 풀로 분리하면 일정 수준의 격리가 제공된다. 한 작업에서 문제가 발생하더라도 다른 유형의 작업 실행에 영향을 주지 않아 결함 격리와 시스템의 전반적인 복원력이 향상된다.

중요한 요점은 모두 소개했다!

비동기 스케줄러 작업을 위한 스레드풀은 다음과 같이 설정하였다.🙃

Scheduler용 Thread Pool 설정

@Configuration
@EnableScheduling
public class SchedulerConfig {

    private final static int POOL_SIZE = 2;

    @Bean("schedulerTask")
    public TaskScheduler taskScheduler() {
        ThreadPoolTaskScheduler executor = new ThreadPoolTaskScheduler();
        executor.setPoolSize(POOL_SIZE);
        executor.setThreadNamePrefix("scheduler-thread-");
        executor.initialize();
        return executor;
    }
}

지금은 스케줄링 작업이 하나이고 주기가 긴 편이라 Pool 사이즈를 적게 설정하였다!

이에 맞춰 스케줄링 작업의 @Async 어노테이션도 다음과 같이 바꿔주었다.

@Async(value = "asyncTask")
@Scheduled(cron = "* * * * * *")
public void updateSignal(){
    log.info("current thread : {}", Thread.currentThread().getName());
    //logic
}

이제 다시 로그를 찍어보면!

스케줄러 스레드풀에서 설정한 prefix가 제대로 나오면서 잘 된 것을 확인할 수 있다!

결론

정리하자면 변경 전과 변경 후를 그림으로 보면 다음과 같다!

변경 전


변경 후


이번에 비동기 작업을 처리하면서 모듈별로 스레드풀을 분리하는 과정을 거쳐보았다.

뭐든지 무분별한 분리는 좋지 않지만 상황에 따라 적절한 분리는 필요하다고 생각한다.

이번 프로젝트의 경우, 스레드풀을 분리하지 않더라도 문제가 없을 것이다.

트래픽을 많이 받고있는 것도 아니고 비동기 작업이 엄청 많은 것은 아니라 복잡하지 않다.

하지만 프로젝트의 요구사항이 늘어나 작업이 늘어나게 된다면 비동기 작업이 더 추가될 수도 있고,

그에 맞춰 하나의 스레드풀을 계속 수정하기에는 제대로 된 스레드 설정을 하지 못하게 될 것이다.

그래서 결론은 지금 당장 필요한 작업은 아니지만 앞으로를 생각하여 작업을 한 것이고 좋은 경험을 했다고 생각한다!

0개의 댓글