WebFlux에서 BlockHound로 블로킹 잡기: 실무 적용 가이드
WebFlux를 쓰는데도 응답이 갑자기 느려질 때가 있다.
대부분 원인은 비슷하다. 논블로킹 경로 안에 블로킹 코드가 섞여 들어간 경우다.
BlockHound는 이 지점을 꽤 직설적으로 잡아준다.
- 지금 이 스레드에서 이 호출은 블로킹이다
- 그래서 여기서 실패시킨다
이 글은 BlockHound를 왜 쓰는지, 어떻게 붙이는지, 실패가 나면 어떻게 고치는지를
실전 흐름으로 정리한 가이드다.
1) 왜 BlockHound가 필요한가
WebFlux 장점은 이벤트 루프 스레드를 오래 붙잡지 않는 데 있다.
그런데 Thread.sleep, 블로킹 JDBC, 파일 I/O가 끼어들면 장점이 바로 사라진다.
겉으로 CPU가 여유 있어 보여도 p95/p99가 튀는 케이스가 여기서 자주 나온다.
2) BlockHound가 실제로 하는 일
BlockHound는 런타임에서 블로킹 메서드 호출을 감지한다.
그리고 그 호출이 논블로킹 스레드에서 일어나면 BlockingOperationError를 터뜨린다.
포인트는 성능 최적화 도구라기보다 위반 탐지 도구라는 점이다.
3) 적용: 테스트에서 먼저 켜기
Gradle 의존성
dependencies {
testImplementation("io.projectreactor.tools:blockhound")
}
JUnit에서 설치
import reactor.blockhound.BlockHound;
class ReactiveTestSupport {
static {
BlockHound.install();
}
}
실무에서는 보통 테스트 공통 베이스에서 한 번만 설치한다.
4) 실패를 재현해보면 바로 이해된다
아래 코드는 의도적으로 블로킹을 넣은 예시다.
import org.junit.jupiter.api.Test;
import reactor.core.publisher.Mono;
import reactor.core.scheduler.Schedulers;
import reactor.test.StepVerifier;
class BlockHoundDemoTest extends ReactiveTestSupport {
@Test
void detectsBlockingCallOnNonBlockingThread() {
Mono<String> pipeline = Mono.fromRunnable(() -> {
try {
Thread.sleep(30); // blocking
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
})
.publishOn(Schedulers.parallel())
.thenReturn("ok");
StepVerifier.create(pipeline)
.expectErrorMatches(t -> t.getClass().getName().contains("BlockingOperationError"))
.verify();
}
}
이 실패는 오히려 반갑다.
운영에서 늦게 터질 문제를 CI에서 먼저 막아주기 때문이다.
5) 실패가 나면 어떻게 고칠까
실무에서는 보통 아래 셋 중 하나다.
A. 논블로킹 대체재로 교체 (가장 좋음)
RestTemplate->WebClient- JDBC -> R2DBC (가능한 경우)
B. 블로킹 구간 격리
Mono<User> loadUser(String id) {
return Mono.fromCallable(() -> blockingRepository.findById(id))
.subscribeOn(Schedulers.boundedElastic());
}
마이그레이션 단계에서는 이 방식이 현실적으로 가장 많이 쓰인다.
C. 불가피한 레거시만 allow-list
BlockHound.install(builder ->
builder.allowBlockingCallsInside("com.example.LegacyBridge", "readOnce")
);
중요한 점: 이건 문제 해결이 아니라 예외 관리다.
이유, 담당자, 제거 시점을 같이 기록해두는 게 좋다.
6) 롤아웃은 이렇게 가면 안전하다
추천 순서:
- 로컬 테스트에 먼저 적용
- CI 테스트에서 강제
- allow-list 최소화 + 정리 일정 관리
- 지표(p95/p99)로 실제 효과 확인
이 순서로 가면 팀 충격을 줄이면서 품질 가드를 만들 수 있다.
7) 도입 시 자주 나오는 질문
Q1. BlockHound를 프로덕션에 항상 켜야 하나요?
필수는 아니다. 많은 팀이 테스트/스테이징에서 강하게 사용한다.
목표가 “위반 조기 발견”이기 때문이다.
Q2. 모든 블로킹을 100% 잡아주나요?
아니다. 다만 실무에서 문제를 만드는 주요 패턴을 빠르게 드러내는 데 꽤 유용하다.
Q3. WebFlux면 무조건 BlockHound를 써야 하나요?
필수는 아니지만, 팀 규모가 커지거나 코드가 많아질수록 효과가 커진다.
특히 리뷰에서 놓치기 쉬운 블로킹 호출을 자동으로 잡아준다.
정리하면 BlockHound는 성능 튜닝 도구보다,
반응형 규율을 지키게 해주는 안전장치에 가깝다.
- 이벤트 루프를 막는 코드 조기 발견
- 실패를 테스트 단계로 당김
- 운영 지연 사고 가능성 낮춤
WebFlux를 이미 쓰고 있다면, BlockHound는 투자 대비 효과가 큰 품질 가드다.