외부 API 사용 시 선택할 수 있는 전략 ‐ 카카오 API 사례 기반 - DDD-Community/DDD-12-MOYORAK-API GitHub Wiki

외부 API 호출 전략 단계별 선택

외부 API를 호출할 때, 서비스 상황과 성능 요구에 따라 다양한 호출 전략을 선택할 수 있습니다.

해당 글은 처리량을 테스트하기 위함이기 때문에 코드는 첨부하지 않겠습니다.

단계 전략 특징
1단계 RestClient (동기) 간단한 호출, 블록킹 I/O, 처리량 낮음
2단계 RestClient + 비동기 (ex. CompletableFuture) 병렬 처리 가능, 하지만 RestClient 내부는 블록킹이므로 한계 존재
3단계 WebClient (Reactive) 비동기 논블로킹 I/O, 고성능 가능, 적절한 리액티브 설계 필요

동기적인 프로그래밍을 시작으로 사용량에 따라 점차 비동기 논블로킹 I/O로 전환하면 현재 서버의 리소스를 최대한 활용할 수 있습니다.

처음부터 WebClient를 사용하는 것도 좋은 방법이지만, 오버 엔지니어링이 될 수 있는 것 같습니다.

간단한 비동기 동작 과정

스프링에서 비동기 프로그래밍을 하는 경우에 Blocking I/O와 Non Blocking I/O가 있습니다.

RestClient + 비동기

RestClient + 비동기

정말 심플하게 이해를 돕기 위해 그렸습니다...ㅎㅎ

  • 메인 Thread인 Tomcat Thread가 Blocking 되는 것을 막기 위해 내부의 Thread pool를 설정하여 해로운 스레드로 작업을 위임합니다.
  • 그렇기때문에 Tomcat Thread는 다른 Request를 받아서 처리할 수 있게 됩니다.
  • 대신 내부 스레드는 Blocking되어 있습니다.

WebClient (Reactive)

WebClient

출처: LG유플러스기술 블로그

  • WebClient는 I/O 작업을 커널에게 위임합니다.
  • 커널은 파일 디스크립터로 소켓 상태를 관리합니다.
  • 소켓을 읽을 수 있는 상태가 되면 이벤트를 발생시켜 이벤트 루프가 처리하도록 합니다. 때문에 Thread Blocking없이 동작이 가능합니다.

처리량 최적화 테스트 (카카오 API 사례)

이번 테스트에서는 카카오 로컬 API 를 비동기 논블로킹 I/O인 WebClient로 호출하는 경우와 RestClient(동기)로 호출하는 경우 2단계만 테스트를 진행하였습니다.

  • 테스트 도구: JMeter
  • 테스트 대상: https://dapi.kakao.com/v2/local/search/keyword.json
  • 테스트 환경: Tomcat Thread를 1개로 고정
  • WebClient + Retry 적용: 카카오 api 쿼터제로 인해 예외가 발생하여 Retry를 적용하였습니다.
전략 / 조건 처리량 (Throughput) 에러율
RestClient + 기본 처리 21 req/sec 0%
WebClient + Retry 264 req/sec 30.37% (429 Too Many Requests 발생)

RestClient 결과 RestClient

WebClient 결과 WebClient

결과 해석

  • WebClient으로 처리한 경우 Throughput이 10배정도 차이나는 것을 볼 수 있습니다.
  • 심지어 위 결과는 카카오 API의 초당 쿼터 제한이 존재하여 일정 이상부터 429(Too Many Requests) 발생 → Retry/Backoff 영향으로 Throughput이 감소한 상태였습니다.
  • 정상 상황(쿼터 제한이 없다면)에서는 WebClient가 지금보다 훨씬 더 높은 처리량을 낼 수 있다고 생각합니다.
    • 이번 결과는 API 제공자의 쿼터 정책 영향이 컸던 것

결론

  • WebClient 자체는 서버 리소스를 효율적으로 사용하는 구조이므로 스레드 풀 고갈 없이 많은 요청 처리가 가능합니다.
  • WebClient 기반 구조는 충분히 높은 처리량을 낼 수 있지만 API 제공자의 쿼터 제한이 있는 경우에는 별도의 Retry/Backoff 설계가 중요합니다.

그 외 전략

  • Java Virtual Thread: 커널 수준의 Thread가 아니기 때문에 매우 가볍다는 특징이 있습니다.
  • Kotlin: Kotlin의 코루틴은 비동기 처리를 위한 경량화된 실행 단위이며, Thread 단위가 아니라 여러 코루틴이 하나의 Thread 위에서 협력적으로 실행됩니다. 이를 통해 Thread blocking 없이 효율적인 리소스 사용이 가능합니다.
⚠️ **GitHub.com Fallback** ⚠️