[Spring] @Async 비동기 처리

icebox127·2026년 4월 25일

졸업 프로젝트에서 깃허브 분석본을 생성하는 api가 있다.
이 때, 다 생성된 후 응답을 주면 응답 시간이 30초 이상으로 너무 오래 걸린다.
이런 경우엔 비동기 처리를 해주는게 좋다.

비동기 처리란?

특정 작업이 끝날때까지 기다리지 않고, 바로 다음 일을 진행하는 방식이다.

동기 처리

이전 작업을 끝낸 후 다음 작업을 진행

  • 요청: 함수 호출
  • 대기: 작업이 끝날 때까지 대기
  • 응답: 작업이 끝난 후 결과 반환
    이후 다음 단계를 진행한다.

장점

  • 가독성과 유지보수성이 좋음

단점

  • 응답성 저하: 처리 시간이 오래 걸릴 경우, 멈춘것처럼 보일 수 있음
  • 처리량 제약: 요청을 처리하는 스레드가 그 시간만큼 묶여있어서 처리량에 한계가 생김

비동기 처리

특정 작업이 끝날때까지 기다리지 않고, 바로 다음 일을 진행

  • 작업 위탁: 비동기 함수를 호출함
  • 즉시 반환: 호출한 스레드는 결과를 기다리지 않고 즉시 반환되어 다른 일을 계속 함
  • 완료 통지: 위탁한 작업이 끝나면 끝났다는 신호와 함께 결과 전달

장점

  • 높은 응답성: 기다리는 동안에도 다른 일을 할 수 있어 화면이나 서버가 멈춘듯 보이지 않음
  • 지연 숨기기: 동기 처리로 1,200ms(각각 500, 300, 400) 걸리던 작업 시간이 이론 상 500ms까지 줄어들 수 있다.
  • 자원 효율: 기다리는 동안 스레드를 붙잡아두지 않아서 효율적임

단점

  • 이해 및 추적 어려움
  • 오류 처리 복잡성 증가
  • 테스트 및 디버깅 난이도 상승

Spring 비동기 처리

1. Application에 @EnableAsync 추가

@SpringBootApplication
@EnableJpaAuditing
@EnableAsync
public class GitTurlApplication {

	public static void main(String[] args) {
		SpringApplication.run(GitTurlApplication.class, args);
	}

}

이 설정을 해둬야 @Async 메서드를 감지해서 비동기 처리가 가능하다.

2. AsyncConfig 생성

@Configuration
@EnableAsync
public class AsyncConfig {

    @Bean(name = "taskExecutor")
    public Executor taskExecutor() { // 스레드 풀 직접 정의하는 메서드
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(5); // 항상 유지되는 최소 스레드 수
        executor.setMaxPoolSize(10); // 최대 스레드 수 제한
        executor.setQueueCapacity(50); // 비동기 작업 요청 대기 큐 크기
        executor.setThreadNamePrefix("async-"); // 스레드 이름 prefix 지정
        executor.initialize(); // 스레드 풀 초기화 메서드
        return executor;
    }
}

@Async에서 사용할 스레드 풀(TaskExecutor)을 직접 정의한다.

3. @Async로 비동기 처리

Spring은 프록시가 @Async를 호출하는데, 같은 파일 내 메서드끼리는 프록시를 안 거치므로 @Async 메서드는 사용될 서비스 메서드가 있는 파일과 분리해야한다.
따라서 ReportAsyncService를 추가로 만들었다.

@Service
@Slf4j
@RequiredArgsConstructor
public class ReportAsyncService {

    private final GitLogParser gitLogParser;
    private final GitAnalysisService gitAnalysisService;
    private final BuildPrompt buildPrompt;
    private final GptService gptService;
    private final ReportRepository reportRepository;
    private final ObjectMapper objectMapper;
    private final GitCloneService gitCloneService;

    @Async
    @Transactional
    public void generateReport(Long reportId, Member currentMember, ReportReqDto.Repo dto) {
        log.info("비동기 실행됨: {}", Thread.currentThread().getName());
        String email = currentMember.getEmail();
        String gitUrl = GitRepoParser.getRepoLink(dto.getFullName());
        String repoPath = gitCloneService.cloneRepository(gitUrl);

        Report report = reportRepository.findById(reportId)
                .orElseThrow();

        report.updateGenerationStatus(GenerationStatus.PROCESSING);
        reportRepository.save(report);

        try {
            List<GitCommit> commits = gitLogParser.getCommits(repoPath);
            List<GitCommit> userCommits = commits.stream()
                    .filter(c -> c.getAuthorEmail().equals(email) || c.getAuthorEmail().contains(currentMember.getGithubId()))
                    .toList();

            GitAnalysisResult result = gitAnalysisService.analyze(GitRepoParser.getRepoFullName(gitUrl), repoPath, commits, userCommits);

            String prompt = buildPrompt.buildReportPrompt(result);
            ReportWrapper content = gptService.analyzeGit(prompt);
            if (content == null) {
                throw new ReportException(ReportErrorCode.GPT_RESPONSE_NOT_FOUND);
            }
            String contentJson;
            try {
                contentJson = objectMapper.writeValueAsString(content);
                report.updateContent(contentJson);
                report.updateGenerationStatus(GenerationStatus.FAIL);
            } catch (JsonProcessingException e) {
                throw new RuntimeException("JSON 변환 실패", e);
            }
        } catch (Exception e) {
            report.updateGenerationStatus(GenerationStatus.FAIL);
        }

    }
}

이렇게 비동기로 처리를 바꾸니 30초 이상에서 5초대로 빠른 응답을 할 수 있었다.

참고

동기와 비동기
스프링 비동기 처리

profile
감자의 공부기록🥔

0개의 댓글