@Async를 붙였는데 스레드가 하나씩만 처리되는 이유
문제 발생
무거운 작업을 비동기로 처리하려고 @Async를 붙였는데, 트래픽이 몰리자 서버가 스레드를 무제한으로 만들어내며 리소스가 고갈되는 문제가 발생했습니다.
@Async
public void sendNotification(User user) {
// 오래 걸리는 작업
}원인 분석
@EnableAsync만 켜고 별도로 Executor 빈을 설정하지 않으면, Spring은 기본값으로 SimpleAsyncTaskExecutor를 씁니다. 이름 그대로 "단순한" 구현인데, 스레드 풀을 재사용하지 않고 호출될 때마다 매번 새 스레드를 생성합니다. 동시 호출이 적을 때는 문제가 없어 보이지만, 트래픽이 몰리면 스레드 수가 제한 없이 계속 늘어나 메모리와 컨텍스트 스위칭 비용이 폭증하고, 결국 OutOfMemoryError나 시스템 전체 성능 저하로 이어질 수 있습니다.
해결 방안
- 전용
ThreadPoolTaskExecutor를 직접 정의하고 크기 제한을 명시적으로 둡니다.
@Configuration
@EnableAsync
public class AsyncConfig {
@Bean(name = "notificationExecutor")
public Executor notificationExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(5);
executor.setMaxPoolSize(20);
executor.setQueueCapacity(100); // 큐가 다 차면 이후 정책(거부 등) 필요
executor.setThreadNamePrefix("notif-");
executor.initialize();
return executor;
}
}@Async에 사용할 Executor 이름을 명시합니다 — 지정하지 않으면 다시 기본값으로 돌아갑니다.
@Async("notificationExecutor")
public void sendNotification(User user) { ... }- 큐가 가득 찼을 때의 정책(거절, 호출 스레드에서 직접 실행 등)도 함께 고려해야 합니다 — 무작정 무한 대기하게 두면 다른 형태의 자원 고갈로 이어질 수 있습니다.
@Async메서드 안에서 발생한 예외는 호출한 쪽으로 전파되지 않습니다 —void반환 메서드라면AsyncUncaughtExceptionHandler를 별도로 등록해야 예외를 로깅이라도 할 수 있습니다. 기본 설정으로는 예외가 조용히 사라져 디버깅이 어려워집니다.@Async메서드를 같은 클래스 안에서 직접 호출(self-invocation)하면 프록시를 거치지 않아 비동기 실행 자체가 안 되는 함정도 있습니다 — 이건@Transactional의 self-invocation 문제와 같은 원인(Spring AOP 프록시 메커니즘)입니다.
댓글0
댓글을 남기려면 로그인이 필요해요. 로그인
아직 댓글이 없어요. 첫 의견을 편하게 남겨 보세요.