Spring

Spring WebClient block() vs subscribe(): 계층별 사용법 정리

j1adev 2025. 6. 18. 16:01

Spring WebFlux나 WebClient를 처음 사용할 때 가장 많이 고민하게 되는 포인트 중 하나는 바로 block()과 subscribe() 중 무엇을 써야 하는가이다. 특히 테스트 코드, 서비스 코드, 컨트롤러, API 응답부 등 **계층마다 요구되는 특성이 다르다.

이번 포스트에서는 WebClient를 사용하는 각 계층에서의 전략적 선택 기준을 정리해본다.


기본 개념: block() vs subscribe()

메서드 설명 특징

block() Mono/Flux의 결과를 동기적으로 대기하여 반환 블로킹 (스레드 점유), 흐름 제어 쉬움
subscribe() Mono/Flux를 비동기적으로 실행하고 콜백으로 결과를 처리 논블로킹, 백그라운드 실행, 흐름 관리 어려움

 

계층별 전략 정리

계층 사용 추천 이유

✅ 단위 테스트 block() 테스트는 완료 여부가 중요함. subscribe()는 타이밍 이슈 발생 가능
✅ 통합 테스트 block() 외부 연동 흐름이 확실히 끝났는지 확인해야 함
✅ 서비스 계층 상황에 따라 선택**(핵심 로직이면 block() 허용)** 요약 처리, 외부 응답 필수 로직 등은 block()으로 동기화 필요할 수 있음
❌ 컨트롤러 (@RestController) subscribe() (또는 Mono 반환) 클라이언트 요청을 처리하는 HTTP 스레드를 절대 블로킹하면 안 됨. 요청 처리 스레드 낭비로 서버 부하 유발
❌ API 응답부 (HandlerFunction 등) subscribe() 요청 처리 스레드 낭비로 서버 부하 유발

 

왜 컨트롤러와 응답부에서는 block()을 쓰면 안 될까?

핵심 이유: 애플리케이션이 병목에 걸리고 결국 다운될 수 있기 때문

Spring WebFlux는 다음 철학을 바탕으로 만들어졌다:

“하나의 스레드로도 수천 개의 요청을 처리할 수 있도록 하자”

→ 이를 위해 모든 I/O, HTTP, DB 호출 등을 **논블로킹(reactive)**으로 설계

 

 

❗ block()을 쓰는 순간 무슨 일이 벌어지냐면?

  • 해당 요청을 처리하던 스레드가 결과를 기다리며 멈춤
  • 다른 요청이 들어와도 스레드는 아직 이전 요청 때문에 사용 중
  • 병렬 요청이 많아지면?→ 새 요청은 처리되지 않음
  • → 서버가 멈춘 것처럼 느려짐 → 다운
  • 사용 가능한 스레드가 모두 블로킹됨

이걸 "스레드 고갈(thread exhaustion)" 또는 "리액터 데드락"이라 부른다.

 

subscribe()는 언제 쓰나?

  • 내부적으로 로그 전송, 알림 메시지, 모니터링 이벤트 전송
  • 결과를 굳이 기다리지 않아도 되는 비핵심 로직
  • 예: 슬랙 알림
slackWebClient.post()
    .uri("/chat.postMessage")
    .body(...)
    .retrieve()
    .bodyToMono(String.class)
    .subscribe(); // 결과는 중요하지 않음


 

보너스: block()은 “컨텍스트 안에서만” 써라

  • @Scheduled, Batch, Retry Processor, Message Listener → block() 허용 가능
  • HTTP 요청/응답 컨텍스트 (Controller) → 절대 block() 금지

 

마무리

WebClient는 강력한 도구지만, 사용 위치에 따라 전략적으로 다뤄야 한다.

비동기를 고집하다 테스트가 깨지고, 동기를 남발하다 서버가 느려지는 일은 흔하다.

따라서 계층별로 다음을 기억하자:

🔹 테스트는 block(), 실서비스는 subscribe(), 컨트롤러는 절대 block() 금지!

'Spring' 카테고리의 다른 글

Spring DataSource, config server 에러 : Access denied for user  (0) 2025.10.25