@Async로 UX 개선하기

2026. 9. 9. 22:47스프링

문제 상황

이 글에서 데이터를 저장한 뒤, Gemini API로 분석하는 기능을 이벤트 기반으로 처리했습니다.

이제 문제는 분석에 최소 1분의 시간이 걸린다는 것입니다. 사용자는 데이터 등록 버튼을 누르고 화면이 멈춘 채로 1분 이상의 시간을 기다려야 합니다.

 

로직상 에러는 없지만 사용자 입장에서 지금 로딩화면이 정상적으로 등록 중이라 멈춘 건지 아니면 무언가 에러가 발생해서 멈춘건지 알 수 없기 때문에 UX관점에서 보면 정말 좋지 않다고 할 수 있습니다.

 

생각해 보면 사용자가 이 시간을 기다릴 필요는 없습니다.

데이터 등록 자체는 순식간에 끝나는 동작이고, 뒤이은 분석은 사용자의 입력이 끝났으니 별개의 작업으로 돌릴 수 있습니다.

그래서 데이터 저장 작업이 끝나면 바로 사용자에게 응답하고 데이터 분석 작업은 다른 스레드로 넘기는 비동기 방식으로 로직을 바꿔야 했는데, 그 역할을 하는 것이 @Async입니다.

 

 

 

@Async란

@Async는 Spring이 제공하는 어노테이션으로 이 어노테이션을 붙인 메서드를 호출의 스레드와 다른 스레드에서 실행하게 만듭니다.

호출한 쪽은 이 메서드가 끝나는 걸 기다리지 않고 바로 다음 줄로 넘어갑니다.

 

사용은 2단계로 이뤄집니다.

1. @EnableAsync 켜기

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

 

2. 메서드에 @Async 붙이기

@Async
@Transactional(propagation = Propagation.REQUIRES_NEW)
@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
public void onLinkCreated(LinkCreatedEvent event) {
    try {
        analysisService.analysisWithAI(event.id());
    } catch (Exception e) {
        log.warn("링크 {} 분석 실패", event.id(), e);
    }
}

 

사용은 위처럼 간단하게 하면 됩니다.

리턴타입은 void, Future<T>, CompletableFuture<T> 중에서 선택할 수 있습니다.

 

변경 후 1분 이상 걸리던 사용자 응답시간은 1초도 안 걸리는 시간으로 줄었습니다.

다만, 분석작업 자체가 빨라진 건 아니기 때문에 추가로 사용자가 해당 데이터를 조회할 때 "아직 데이터 분석 중입니다."라는 메시지를 띄워주도록 UI를 변경해야 UX가 개선되었다고 말할 수 있을 겁니다.

 

 

 

@Async 주의점

기본 Executor의 함정

executor를 지정하지 않으면 Spring은 기본적으로 SimpleAsyncTaskExecutor를 붙입니다.

이 executor는 매 호출마다 새로운 스레드를 만들고 상한도 없습니다.

스레드 생성에도 비용이 들고 따라서 트래픽이 몰릴 때 스레드가 계속 생성돼서 서버가 죽어버릴 수도 있습니다.

실무에서 사용 시에는 ThreadPoolTaskExecutor를 통해 최대 스레드 수, 기본 스레드 수 등을 정의해서 사용하는 게 좋습니다.

 

트랜잭션 전파 안됨

@Async는 새 스레드에서 실행되기 때문에 호출자의 트랜잭션이 전파되지 않고, 영속성 컨텍스트도 공유되지 않습니다.

만약 @Async 메서드 안에서 DB작업이 필요하다면 그 메서드에 별도로 @Transactional을 붙여야 합니다.

 

self-invocation 문제

@Async는 Spring AOP 기반이기 때문에 프록시로 동작합니다.

따라서 같은 클래스 내부에서 메서드를 직접 호출하면 프록시를 거치지 않아서 @Async뿐 아니라 @Transactional을 포함한

Spring AOP 전반의 이슈이기 때문에 별도의 글에서 다루겠습니다.