문제 발견
- 항공/숙박 Agent 동시 호출 불가능
- @Async 사용시 같은 클래스 내에서 메서드 호출 시 @Async 안 됨
- @Async 가 붙은 메서드는 스프링이 프록시 객체로 감싸서 관리
- 호출이 프록시를 거칠 때만 별도의 스레드풀에서 실행되어 비동기 처리
- 동일 클래스 내에서 자신의 메서드를 직접 호출하면 프록시를 우회함!! -> 비동기 기능 동작X
@Async와 WebClient의 비동기 처리 역할
- @Async: Spring이 별도 스레드 풀에서 메서드를 실행하도록 해, 서비스 레벨에서 병렬 처리를 가능하게 한다. 여러 요청이나 작업을 동시에 처리할 때 유용하다.
- WebClient: HTTP 호출 시 네트워크 I/O를 논블로킹(Non-blocking) 방식으로 처리하여 스레드가 그 대기 시간 동안 블로킹되지 않는다. 대규모 네트워크 호출에 효율적이다.
- 두 기술은 역할이 달라 함께 쓰면 CPU와 네트워크 자원을 모두 효율적으로 쓸 수 있다.
비동기와 병렬 처리 시 고려사항
- 트랜잭션 관리: 비동기 작업은 별도 스레드에서 실행되므로 기존 트랜잭션 범위와 맞지 않을 수 있다. 비동기 작업 별도 트랜잭션 설계가 필요하다.
- 상태 동기화: 여러 스레드가 동시에 동일 자원(메모리, DB 등)에 접근하면 경쟁 조건이 발생하니 락이나 안전한 자료구조 사용이 필수.
- 예외 처리: 비동기 실행 예외는 호출자에 바로 전달되지 않아 별도 로깅, 알림, 재시도 등의 처리가 필요하다.
동기 HTTP 호출과 비동기 HTTP 호출 차이
- 전통적 RestTemplate은 동기 방식으로, 호출 시 스레드가 응답 대기 중 블로킹된다.
- 반면 WebClient는 논블로킹 방식으로 네트워크 요청 중에도 스레드를 점유하지 않아 더 효율적이고 많은 동시 요청 처리 가능하다.
비동기 호출만 써도 블로킹 스레드를 쓸 수 있나?
- @Async는 별도 스레드에서 메서드를 실행하지만, 내부에 동기 호출이 있으면 그 스레드는 그 작업 완료까지 블로킹된다.
- 동시 요청이 많아지면 스레드 자원 부족이 생길 수 있어 WebClient같은 논블로킹 I/O 도구를 쓰는 것이 추천된다.
실제 적용 사례
- 서비스 레벨 메서드에 @Async 붙여 병렬로 실행하고,
- 내부 HTTP 호출은 WebClient로 논블로킹 처리하여 네트워크 지연과 CPU 자원 사용을 모두 최적화한다.
추가
MCP에 쓰인 OkHttpClient 도 동기였다. MCP에 전송되는 동안에도 다른 작업들을 할 수 있게 비동기인 WebClient로 변경하였다.