Engineering/Spring

Spring @Async 사용 예제

부스 boos 2025. 6. 9. 15:45
728x90

 다음과 같은 코드가 있다. DB 에서 처리할 작업 목록을 가지고 와서 하나씩 처리하는 코드인데, 한번에 하나씩 즉, 동기식으로 동작한다.

@Sl4j
@Component
public class ScheduledTask {
    private final DoService doService;

    private final DoMapper doMapper;

    @Scheduled(fixedDelayString = "300000")
    public void analyze() {
        Job job;
        while((job = doMapper.findJobsWithNotRunning()) != null) {
            int rs = doMapper.updateJobStatusIfNotRunning(job.getSeq());
            if (rs < 1) {
                log.info("Job is not update. jobSeq:{}", job.getSeq());
                return;
            }

            processJob(job);
        }
    }

    public void processJob(Job job) {
        try {
            log.info("Job processing... jobSeq:{}", job.getSeq());
            doService.workJob(job.getSeq());
        } catch (Exception e) {
            doMapper.updateJobStatus(job.Seq(), FAIL);
            log.error("Failed to process Job. jobSeq: {}", job.getSeq(), e);
        }
    }
}

 

 5분마다 doMapper.findJobsWithNotRunning() 로 처리할 job 를 가지고 job 의 현재 상태값을 바꾸고 처리하는 메소드(processJob()) 을 호출한다.

 legacy 코드로 job 을 처리하는 시간이 짧을때는 아무 문제가 없었지만, 하나의 job 처리 시간이 갑자기 길어지는 일이 발생하면서 처리할 job 들이 늘어나게 되고 작업 시간이 뒤로 밀리는 현상이 나오게 되었다.

 

 구조를 여러 방식으로 바꿔야 하겠지만, 일단 간단하게 processJob() 을 비동기식으로 바꾸고 나서 실행을 해보았다.

@Sl4j
@Component
public class ScheduledTask {
    private final DoService doService;

    private final DoMapper doMapper;

    @Scheduled(fixedDelayString = "300000")
    public void analyze() {
        Job job;
        while((job = doMapper.findJobsWithNotRunning()) != null) {
            int rs = doMapper.updateJobStatusIfNotRunning(job.getSeq());
            if (rs < 1) {
                log.info("Job is not update. jobSeq:{}", job.getSeq());
                return;
            }

            processJobAsync(job);
        }
    }

    @Async
    public void processJobAsync(Job job) {
        try {
            log.info("Job processing... jobSeq:{}", job.getSeq());
            doService.workJob(job.getSeq());
        } catch (Exception e) {
            doMapper.updateJobStatus(job.Seq(), FAIL);
            log.error("Failed to process Job. jobSeq: {}", job.getSeq(), e);
        }
    }
}

 

 processJob() 을 @Async 어노테이션을 추가하고 메소드 명도 processJobAsync() 로 바꾸고 이제 여러 job 들이 여러개 동시에 처리될거라고 생각했다.

 그러나 예상과 달리 findJobsWithNotRunning() 에서 3개의 job 들(100,101,102) 이 있음에도 "Job processing... jobSeq: 100" 이렇게 하나의 job 만 처리하고 있다. 동기식으로 동작을 하는 것이다.

 

 원인을 찾아보니 Spring 에서 @Async 가 붙은 메소드는 proxy 로 호출이 되는데, 같은 클래스 내(ScheduleTask)에서 호출하는 거는 proxy 가 적용이 안된다는 것이다. (https://docs.spring.io/spring-framework/docs/5.0.2.RELEASE/kdoc-api/spring-framework/org.springframework.scheduling.annotation/-enable-async/)

 

 해결 방법은 processJobAsync() 를 다른 서비스 클래스로 옮겨서 실행하거나( doService.processJobAsync() ) 자기 자신을 주입받아서 호출( @Autowired private ScheduledTask self;  ... self.processJobAsync(); )하면 비동기로 동작이 가능하다고 한다.

 

 관련해서 Spring 에서 관리하는 bean 과 Proxy 관계를 조사해보았다. (https://firstboos.tistory.com/531)