Skip to main content Link Menu Expand (external link) Document Search Copy Copied

created at 2023-03-04

인증서버의 속도를 높이기 위해 기존 단일 스레드로 작업하던 부분을 아래의 configuration 을 통해 10개~100개의 스레드로 동시성을 가져가보았습니다.

미리 결론을 말씀드리면 성능은 개선되지 않았습니다. 이유는 곧 말씀드리겠습니다.

Thread Pool 설정

@Configuration
@EnableAsync
public class AsyncConfig {

    @Bean(name = "taskExecutor")
    public Executor taskExecutor(){

        ThreadPoolTaskExecutor t = new ThreadPoolTaskExecutor();
        t.setCorePoolSize(10); // 최소 스레드 개수
        t.setQueueCapacity(10); // 중간 LinkedQueue 크기
        t.setMaxPoolSize(100); // 최대 사용가능한 스레드 개수
        t.setThreadNamePrefix("auth-thread-");
        
        t.setWaitForTasksToCompleteOnShutdown(true); // MaxPoolSize를 넘겨도 대기 
        t.setAwaitTerminationSeconds(60); // 대기시간
        t.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy()); // 대기 시간을 넘겨 reject되면, 호출한 Thread에서 reject된 task를 대신 실행

        t.initialize();
        return t;
    }
}

@Service
@Asnyc <-- ADDED
public class UserService {
    ...
}

저는 가장 많이 사용되는 ThreadPoolTaskExecutor 를 사용하였습니다. 얘는 아래와 같이 Task 를 스케줄링하게 됩니다.

  1. Task는 CorePoolSize 만큼 새로운 스레드에서 실행됩니다.
    • CorePoolSize : 최소 스레드 개수를 설정하는 부분입니다.
  2. CorePoolSize 수 만큼 새로운 스레드로 Task가 실행되면, 이후 QueueCapacity 크기 만큼 큐에 Task가 저장됩니다.
    • QueueCapacity : 큐의 크기입니다. default로 LinkedQueue가 생성됩니다.
  3. Task가 QueueCapacity 크기를 초과할 경우, Task는 MaxPoolSize 만큼 새로운 스레드를 늘리며 실행됩니다.
    • MaxPoolSize : 최대 스레드 개수를 설정하는 부분입니다.
  4. 만약 MaxPoolSize 을 초과하는 Task 가 실행된다면, RejectExecutionException 에러를 던지며 셧다운 됩니다.

이 떄, 셧다운 되기전 RejectExecutionException 에러를 핸들링할 수 있습니다. 그래서 Task가 빠짐없이 실행되어야 할 경우, 아래와 같이 설정할 수 있습니다.

  • setWaitForTasksToCompleteOnShutdown : MaxPoolSize를 넘겨도 Task를 대기시킵니다.
  • setAwaitTerminationSeconds : 대기시간을 설정할 수 있습니다.
  • setRejectedExecutionHandler : 대기시간을 넘겨 reject 되어도 호출한 Thread에서 task를 실행할 수 있도록 설정합니다.

(CPU/MEM 사용률) 멀티 스레딩 이전 VS 이후

싱글 스레딩

멀티 스레딩 이전 싱글 스레드로 구현된 docker 컨테이너 서비스들의 cpu/memory 사용률을 확인해볼까요?

img img

제 노트북에서 여러 컨테이너를 사용함에 따라 잔여 CPU 리소스는 고작 0.05%. 이미 한계까지 사용하고 있었네요.

10K HTTP request 의 총 RTT 시간 : 30초

멀티 스레딩

img

10K HTTP request 의 총 RTT 시간 : 30초

Single VS Multiple Threads

보시다시피 10K 의 HTTP request에 걸린 총 시간은 30초로 동일합니다. 또한 CPU 사용률 또한 별 차이가 없죠(이미 극한까지 사용하고 있었기 때문에…)

결론은, 멀티 스레딩은 CPU에 충분한 resource가 존재해야됩니다! 만약 리소스가 없다면, 동시성 부분에서야 이점이 있겠지만 task가 빠짐없이 실행되어야 한다면 결론적으로는 성능은 싱글과 동일하거나 낮습니다(Context-Switch 비용 증가).

즉, 아무래도 저는 현재 성능을 개선시키기 위해서 device 자체를 수평/수직적으로 붙여서 확장시켜야 할 것 같습니다.