
리액티브 프로그래밍에서 흔히 마주치는 개념 중 하나가 바로 Cold와 Hot이다. 이 용어는 데이터 스트림의 생성 방식과 구독 시점에 따른 처리 차이를 설명하는데 사용된다. 단어 자체는 어렵지 않지만, 실제 동작 방식은 충분히 실험해보지 않으면 오해하기 쉽다. 이 글에서는 Cold와 Hot의 의미를 익숙한 IT 용어와 함께 살펴보고, Reactor에서 어떤 방식으로 적용되는지를 실습 코드와 함께 자세히 설명한다.
IT 세계에서 'Hot'이라는 표현은 이미 실행 중이거나, 재시작 없이 즉시 작동 가능한 상태를 의미한다.
→ 핵심 요약: 시스템을 다시 시작하지 않고, 현재 상태 그대로 이어서 작동한다는 개념
반면 'Cold'는 매번 처음부터 다시 시작하는 흐름이다.
→ 핵심 요약: 매번 새로 시작하며, 같은 작업을 반복하게 됨
Reactor에서 Flux, Mono는 기본적으로 Cold Sequence이다. 구독이 일어날 때마다 Publisher는 데이터 생성을 처음부터 시작한다.
Flux<String> coldFlux = Flux.fromIterable(Arrays.asList("A", "B", "C"))
.map(String::toLowerCase);
coldFlux.subscribe(it -> log.info("# subscribe1 : {}", it));
Thread.sleep(2000L);
coldFlux.subscribe(it -> log.info("# subscribe2 : {}", it));
출력 결과:
# subscribe1 : a
# subscribe1 : b
# subscribe1 : c
# subscribe2 : a
# subscribe2 : b
# subscribe2 : c
→ 두 번째 구독에서도 데이터가 처음부터 다시 emit됨을 확인할 수 있다.
Hot Sequence는 이미 실행 중인 Publisher를 여러 Subscriber가 공유하는 방식이다. 한 번 emit된 데이터는 다시 emit되지 않으며, 구독자는 실시간으로 흘러가는 데이터에 중간에 합류한다.
Flux<String> hotFlux = Flux.fromArray(new String[]{"A", "B", "C"})
.delayElements(Duration.ofSeconds(1))
.share(); // Hot 변환
hotFlux.subscribe(it -> log.info("# subscribe1 : {}", it));
Thread.sleep(2000L);
hotFlux.subscribe(it -> log.info("# subscribe2 : {}", it));
출력 예시:
# subscribe1 : A
# subscribe1 : B
# subscribe1 : C
# subscribe2 : C
→ subscribe2는 A, B가 emit된 이후에 구독했기 때문에 C만 받음
→ .share() 연산자를 통해 Flux가 Hot으로 전환되어 스트림이 공유됨
이러한 Hot 특성은 다음과 같은 경우에 적합하다:
Mono<String> mono = WebClient.create()
.get()
.uri(url)
.retrieve()
.bodyToMono(String.class)
.map(response -> {
DocumentContext jsonContext = JsonPath.parse(response);
return jsonContext.read("$.datetime");
});
mono.subscribe(response -> log.info("#1 datetime1 : {}", response));
Thread.sleep(2000);
mono.subscribe(response -> log.info("#2 datetime2 : {}", response));
→ mono는 Cold Sequence이기 때문에 구독할 때마다 HTTP 요청이 발생한다.
→ 동일한 요청을 반복하게 되며, 응답 시점에 따라 결과가 달라질 수도 있다.
Mono<String> mono = WebClient.create()
.get()
.uri(url)
.retrieve()
.bodyToMono(String.class)
.map(response -> {
DocumentContext jsonContext = JsonPath.parse(response);
return jsonContext.read("$.datetime");
})
.cache(); // Hot으로 전환
mono.subscribe(response -> log.info("#1 datetime1 : {}", response));
Thread.sleep(2000);
mono.subscribe(response -> log.info("#2 datetime2 : {}", response));
→ 이 경우 WebClient는 한 번만 요청하고, 응답을 캐시하여 이후 구독자에게 재사용한다.
→ HTTP 요청의 중복을 방지하고, 동일한 결과를 공유할 수 있어 토큰 인증 등에서 매우 유용
| 구분 | Cold Sequence | Hot Sequence |
|---|---|---|
| 기본 동작 | 구독마다 새로 시작 | 스트림 공유, 중간 합류 |
| HTTP 요청 | 구독마다 재요청 | 최초 요청 결과 재사용 가능 |
| 연산자 예시 | 기본 상태, Mono, Flux | .share(), .cache() |
| 적합한 사용 | 정확한 재처리, 각자 독립적 요청 | 실시간 알림, 중복 방지 캐시, 스트리밍 |
Cold와 Hot의 차이는 단순한 성능 문제가 아니라, 스트림의 의미를 어떻게 해석할 것인가에 대한 설계 철학과도 맞닿아 있다. 실시간인지, 이력 기반인지, 결과를 공유할 필요가 있는지 등을 고려하여 상황에 맞는 전략을 선택하는 것이 중요하다.