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 |
|---|