
AWS SES에서 메일 전송 속도를 비동기로 최적화했다. 93%정도의 시간을 줄일 수 있었다. 이에 대해 가볍게 정리해본다.
AOP(Aspect-Oriented Programming, 관점 지향 프로그래밍)는 공통 관심사(부가 기능)를 비즈니스 로직에서 분리하여 모듈화하는 프로그래밍 기법이다.
Spring에서는 AOP를 통해 핵심 로직 + 공통 로직을 깔끔하게 분리하여 재사용성과 유지보수성을 높일 수 있다.
Spring AOP는 프록시 패턴을 기반으로 동작한다.
Spring은 프록시 객체(대리 객체) 를 만들어 클라이언트가 실제 객체를 호출하기 전에 프록시가 호출을 가로챈다.
프록시가 Advice(@Async, @Transactional 등) 를 실행한 뒤 실제 객체의 메서드를 호출한다.
클라이언트 → 프록시 → 실제 객체
같은 클래스 내부에서 this.sendEmailAsync()로 메서드를 호출하면 프록시 객체를 거치지 않고 실제 객체를 직접 호출하게 된다.
결과적으로 프록시가 비동기 처리를 담당하지 못해서 동기로 실행된다.
방법 1) 별도의 서비스 클래스로 @Async 분리
@Service
public class EmailAsyncService {
@Async
public void sendEmailAsync(String to, String content) {
// 비동기 메일 전송 로직
}
}
@Service
@RequiredArgsConstructor
public class MailService {
private final EmailAsyncService emailAsyncService;
public void sendMail(String to, String content) {
// 비동기로 실행됨
emailAsyncService.sendEmailAsync(to, content);
}
}
방법 2) ApplicationContext를 통해 직접 프록시를 호출
@Autowired
private ApplicationContext applicationContext;
public void sendMail(String to, String content) {
applicationContext.getBean(MailService.class).sendEmailAsync(to, content);
}
다만 이 방식은 권장되지 않음 (코드 복잡도 증가)
근데 같은 클래스 내에서 this.sendEmailAsync()로 호출하면
프록시를 안 거치고 그냥 "자기 자신 호출"로 인식 → 비동기 안 됨 = 동기로 실행됨.
AWS SES 메일 전송 로직을 @Async로 비동기화 하여 API 응답 속도를 2.6초에서 234ms로 최적화
비동기 로직은 별도의 AsyncService로 분리하여 프록시를 정상적으로 거치도록 설계
AsyncUncaughtExceptionHandler를 등록하여 비동기 예외도 별도로 로깅
Spring에서 AOP 기반의 어노테이션(@Async, @Transactional 등)은 반드시 프록시 객체를 통해 호출되어야 적용된다.
따라서 동일 클래스 내에서 this로 호출하면 동기로 실행되는 현상을 주의해야 한다.
이를 방지하기 위해 비동기 메서드는 별도의 서비스 클래스로 분리하여 사용하는 것이 일반적인 해결책이다.