Reactive란 무엇인가?
Spring WebFlux를 이해하기 위해서는 먼저 Reactive(리액티브)라는 개념에 대한 이해가 필요합니다. 먼저, reactive라는 단어의 사전적 정의를 살펴보겠습니다.
reacting to events or situations rather than
acting first to change or prevent something
이를 직역하면, Reactive는 먼저 행동하여 상황을 바꾸거나 예방하려는 것이 아니라, 발생한 사건이나 상황에 반응하는 방식을 의미합니다.
조금 더 쉽게 풀어 설명하면, Reactive는 어떤 요청이나 이벤트가 발생했을 때 그에 맞춰 유연하게 반응하는 구조라고 볼 수 있습니다. 즉, 시스템이 스스로 모든 흐름을 주도하기보다는, 외부에서 발생하는 신호나 상태 변화에 따라 동작을 수행하는 방식입니다.
기존의 전통적인 방식에서는 클라이언트의 요청이 들어오면 서버가 해당 요청을 처리하는 동안 스레드를 점유하고 기다리는 구조였다면, Reactive 방식은 요청을 처리하는 동안에도 블로킹되지 않고 다른 작업을 수행할 수 있도록 설계됩니다. 이로 인해 리소스를 보다 효율적으로 사용할 수 있으며, 대량의 동시 요청에도 안정적인 처리가 가능해집니다.
Reactive에 대해 깊이 있게 이해하기 위해서는 다음 3가지 선행 개념을 이해하고 있어야 합니다.
Reactive Streams
Reactive Streams의 역사
Reactive 생태계는 여러 기술과 커뮤니티의 협업을 통해 단계적으로 발전해왔습니다. 주요 흐름을 연도별로 정리하면 다음과 같습니다.
| 연도 / 시기 | 주요 내용 |
| 2011년 6월 | MS 닷넷 프레임워크를 위한 Reactive Extensions 배포 |
| 2013년 2월 | Netflix 기술 블로그에서 RxJava 공개 (0.5 버전) |
| 2013년 9월 | Reactive Manifesto v1 공표 |
| 2013년 11월 | Pivotal Project Reactor 1.0.0 배포 |
| 2014년 9월 | Reactive Manifesto v2 공표 |
| 2014년 11월 | Netflix RxJava 1.0 배포 |
| 2015년 4월 | Reactive Streams 1.0.0 Java 9와 함께 배포 |
| 2015년 4월 | Akka Stream 1.0 배포 |
| 2017년 8월 | Reactive Streams 1.0.1 배포 |
| 2021년 7월 | Red Hat의 Mutiny 1.0 배포 |
위 타임라인을 통해 Reactive 개념은 단순한 이론을 넘어, 다양한 언어와 플랫폼에서 실제 표준과 프레임워크로 자리잡아 왔음을 확인할 수 있습니다. 특히 Reactive Streams의 표준화는 Spring WebFlux와 같은 Reactive 기반 프레임워크가 등장하는 결정적인 계기가 되었습니다.
Reactive manifesto v1
Reactive Manifesto는 Reactive 시스템이 가져야 할 핵심 가치와 설계 원칙을 정리한 선언문입니다. 단순히 비동기나 논블로킹 기술을 의미하는 것이 아니라, 현대 분산 시스템과 클라우드 환경에서 안정적이고 확장 가능한 시스템을 만들기 위한 철학적 기준이라 할 수 있습니다. Reactive Manifesto는 다음 네 가지 핵심 특성을 기반으로 합니다.
1. Responsive (응답성)
시스템은 항상 빠르게 응답해야 합니다. 응답성이 보장될 때 사용자 경험은 향상되며, 문제가 발생했을 때도 이를 신속히 감지하고 대응할 수 있습니다.
2. Resilient (탄력성)
시스템의 일부가 실패하더라도 전체가 중단되지 않아야 합니다. 장애가 발생해도 복구 가능하도록 설계되어야 하며, 격리와 복원 메커니즘이 함께 고려됩니다.
3. Elastic (유연성)
부하 변화에 따라 시스템은 자동으로 확장되거나 축소될 수 있어야 합니다. 이는 클라우드 환경에서 중요한 요소이며 리소스 효율성과 직결됩니다.
4. Message Driven (메시지 기반)
시스템의 구성 요소는 비동기 메시지 전달을 통해 통신해야 합니다. 이를 통해 느슨한 결합과 높은 독립성을 유지할 수 있습니다.
이를 종합하면 Reactive 시스템은 비동기 논블로킹 기반의 메시지 큐 또는 메시지 전달 메커니즘을 통해 구성 요소 간 통신을 수행해야 한다는 의미로 해석할 수 있습니다. 즉, 각 컴포넌트는 직접적인 호출이 아닌 이벤트 또는 메시지를 매개로 느슨하게 연결되며, 이를 통해 시스템은 높은 확장성과 회복력을 동시에 확보하게 됩니다.
Reactive manifesto v2
2014년 9월 발표된 Reactive Manifesto v2는 기존 v1의 개념을 유지하면서도 보다 명확한 개념 정리를 통해 용어와 표현을 일부 개선한 버전입니다. 특히 v2에서는 Reactive 시스템의 특성을 보다 직관적으로 표현하기 위해 몇 가지 명칭이 변경되었습니다.
- Elastic → Scalable
- Responsive → Interactive
- Message Driven → Event Driven
v1에서 사용되던 Elastic은 시스템이 부하에 따라 유연하게 확장된다는 의미였으나, v2에서는 이를 보다 직관적인 표현인 Scalable(확장 가능성)로 변경하였습니다.
또한 v2에서는 단순히 빠르게 응답하는 수준을 넘어, 사용자와 실시간 상호작용이 가능한 시스템을 강조하기 위해 Interactive(상호작용성) 개념이 부각되었습니다.
마지막으로 v1에서는 Message Driven이라는 표현을 사용했지만, v2에서는 이를 Event Driven(이벤트 기반)으로 확장하여 표현했습니다. Message Driven은 시스템 구성 요소 간에 "메시지를 전달한다"는 통신 방식 자체에 초점을 둡니다.
반면 Event Driven은 "메시지가 전달되는 이유"와 "상태 변화의 흐름"까지 포함하는 개념입니다. 단순히 메시지를 보내는 것이 아니라, 특정 이벤트(상태 변화, 조건 충족, 사용자 액션 등)가 발생했을 때 시스템이 어떻게 반응하고 연결되는가에 더 큰 의미를 둡니다.
Reactive Manifesto v2는 기존 v1의 개념을 유지하면서도 보다 현실적인 시스템 환경을 반영하여 내용을 보완한 버전입니다. v2에서는 Reactive 시스템의 목표가 단순한 처리 속도가 아닌, 예측 가능하고 신뢰할 수 있는 시스템 동작이라는 점이 명확히 강조되었습니다.
Reactive Streams
앞서 살펴본 Reactive 철학이 실제 구현 단계로 이어질 때 등장한 표준이 바로 Reactive Streams입니다. Reactive Streams는 비동기 스트림 처리와 배압(Back-pressure)을 명확하게 정의한 사양이며, Publisher와 Subscriber 사이의 상호작용 방식을 규격화합니다.
Reactive Streams는 Java 9 이후 java.util.concurrent.Flow 로 포함되었으며, Spring WebFlux와 Project Reactor, RxJava 등의 기반이 됩니다.

Reactive Streams에서는 기존의 요청-응답 구조가 아닌, 다음과 같은 흐름으로 동작합니다.
- Callee는 Caller에게 즉각적인 응답 값을 반환하지 않습니다.
대신, 처리 결과를 담을 수 있는 Publisher를 제공합니다. - Callee는 각각의 데이터를 Publisher를 통해 전달합니다.
데이터는 한 번에 반환되는 것이 아니라, 스트림 형태로 순차적으로 발행됩니다. - Caller는 Publisher를 직접 구독(subscribe)하거나, 다른 Caller에게 전달할 수 있습니다.
이를 통해 스트림 처리는 유연하게 연결됩니다. - Caller는 Subscriber를 등록하여 Back-pressure를 통해 처리 속도를 제어합니다.
Subscriber는 자신이 처리 가능한 만큼의 데이터만 요청하며, 이를 통해 과부하를 방지합니다.
CompletableFuture와 Publisher의 차이
CompletableFuture와 Publisher는 모두 비동기 처리를 지원하지만, 처리 방식과 설계 목적에서 명확한 차이를 가집니다.
| 구분 | CompletableFuture | Publisher |
| 데이터 단위 | 단일 결과 | 다수 데이터 스트림 |
| 실행 시점 | 생성 즉시 실행 | subscribe 시 실행 |
| 지연 로딩 | 불가능 | 가능 |
| Back-pressure | 미지원 | 지원 |
| 제어 주체 | 생산자 중심 | 소비자 중심 |
| 주요 목적 | 단일 비동기 작업 | 지속적인 스트림 처리 |
Back-pressure와 메시지 큐의 역할
Reactive Streams에서 중요한 개념 중 하나가 바로 Back-pressure입니다.
Publisher는 내부적으로 메시지 큐(버퍼)를 생성하여 데이터를 관리하며, Subscriber의 요청 수요에 맞춰 데이터를 전달합니다.
- 과도한 데이터 전송으로 인한 메모리 초과 방지
- 처리 속도에 맞춘 안정적인 데이터 흐름 제어
- 시스템 부하 균등 분산
즉, Publisher는 단순히 데이터를 발행하는 주체가 아니라, 부하 관리와 흐름 제어를 담당하는 핵심 제어자 역할을 수행합니다.
Reactive Streams의 구조
Reactive Streams는 3가지 기본 인터페이스로 구성됩니다.
| 구성 요소 | 역할 |
| Flow.Publisher | 데이터(이벤트)를 발행하는 주체 |
| Flow.Subscriber | 데이터(이벤트)를 소비하는 주체 |
| Flow.Subscription | 데이터 요청 및 흐름 제어(요청량, 취소 등) 담당 |

Publisher

- subscribe() 함수를 제공하여 다수의 Subscriber 등록을 지원합니다.
- Subscriber가 등록되면 Subscription 객체를 함께 전달합니다.
- 실제 데이터 발행은 Subscriber의 요청(request)에 따라 이루어집니다.
Subscriber

- subscribe 시점에 Publisher로부터 Subscription을 전달받습니다.
- 다음과 같은 콜백 메서드를 통해 데이터를 수신합니다.
- onNext() : 데이터 수신 (여러 번 호출 가능)
- onError() : 오류 발생 시 호출 (딱 한 번)
- onComplete() : 정상 종료 시 호출 (딱 한 번)
Subscription

- request(long n) : 처리 가능한 데이터 개수를 요청하여 Back-pressure를 제어합니다.
- cancel() : 데이터 발행을 중단하고 구독을 해지합니다.
Hot Publisher vs Cold Publisher
Reactive Streams에서 Publisher는 동작 방식에 따라 Hot Publisher와 Cold Publisher로 구분됩니다. 이 구분은 스트림이 언제 시작되며, 데이터가 어떻게 전달되는지를 이해하는 중요한 기준이 됩니다.

Hot Publisher는 구독자(Subscriber)의 존재 여부와 관계없이 데이터를 지속적으로 생성하고 스트림에 전달하는 구조입니다.
- Subscriber가 없어도 데이터를 생성하고 stream에 push합니다.
- 하나의 데이터 소스가 여러 Subscriber에게 동일한 데이터를 전달합니다.
- 데이터는 실시간으로 생성되며, 늦게 구독한 Subscriber는 이전 데이터를 받을 수 없습니다.
대표적인 예시로는 SNS 실시간 타임라인과 공유 리소스 변화 등이 있습니다.

Cold Publisher는 Subscriber가 구독을 시작하는 순간부터 데이터를 생성하고 전달하는 구조입니다.
- subscribe가 호출되는 시점부터 스트림이 시작됩니다.
- 각 Subscriber마다 독립적인 데이터 스트림이 제공됩니다.
- Subscriber마다 동일한 로직이 개별적으로 실행됩니다.
대표적인 예시로는 파일 읽기와 웹 API 요청 등이 있습니다.
Reactive Streams 구현 라이브러리

Reactive Streams는 표준 사양이지만, 실제 개발에서는 이를 구현한 라이브러리를 통해 사용하게 됩니다. 여기서는 Spring WebFlux와 함께 자주 사용되는 대표적인 3가지 구현체에 대해 살펴보겠습니다.
Project Reactor
Project Reactor는 Spring WebFlux의 공식 Reactive 라이브러리이며, 가장 널리 사용되는 구현체입니다.

Reactor의 핵심은 Flux와 Mono라는 두 가지 타입입니다.
Flux

Flux<T>는 0개 이상(N개)의 데이터를 비동기적으로 발행하는 범용 Publisher입니다.
데이터 스트림은 정상 완료(onComplete) 또는 오류(onError)로 종료될 수 있으며, 이 모든 신호는 Subscriber의 onNext, onComplete, onError 메서드 호출로 전달됩니다.
중요한 특징은 모든 이벤트(종료 이벤트 포함)는 선택적이라는 것입니다.
| onNext | onComplete | onError | 의미 |
| ❌ | ✅ | ❌ | 유한하지만 비어 있는 시퀀스 |
| ✅ | ❌ | ❌ | 무한 스트림 |
| ❌ | ❌ | ❌ | 무한하지만 비어 있는 시퀀스 (Flux.never) |
| ✅ | ✅ | ❌ | 정상적인 유한 스트림 |
| ❌ | ❌ | ✅ | 즉시 실패한 스트림 |
데이터가 실제로 발행(onNext)되면 스트림으로 이해할 수 있고,
발행 없이 종료만 존재하거나 아무 신호도 없다면 개념적인 시퀀스에 가깝다고 볼 수 있습니다.
Mono

Mono<T>는 최대 1개의 데이터만 발행하는 특수한 Publisher입니다.
정상적인 경우 onNext를 한 번 호출한 뒤 onComplete로 종료되며, 실패한 경우에는 onError만 발생합니다. onNext와 onError가 함께 발생하는 것은 명확히 금지되어 있습니다.
Mono는 단일 결과를 표현하는 데 적합하며, API 호출 결과나 단일 데이터 조회와 같은 상황에서 자주 사용됩니다. 대부분의 Mono 구현체는 값을 발행(onNext)한 후 즉시 onComplete를 호출하여 스트림을 종료합니다.
일부 특수한 예외도 존재합니다. Mono.never()는 어떤 신호도 발생시키지 않는 Mono로, 일반적인 비즈니스 로직에서는 거의 사용되지 않지만 테스트 상황에서 활용될 수 있습니다.
Flux에서도 단일 값을 전달할 수 있지만, Mono가 별도로 존재하는 이유는 최대 1개의 값만 방출한다는 사실을 타입 수준에서 명확히 표현할 수 있기 때문입니다. 또한 Mono<Void>를 사용하면 데이터 전달이 아닌, 작업의 완료 자체를 중심으로 한 비동기 흐름을 표현할 수 있습니다.
RxJava
Netflix에서 시작된 RxJava는 Project Reactor와 마찬가지로 Reactive Streams 개념을 기반으로 하지만, 보다 세분화된 스트림 타입을 통해 데이터의 성격을 명확히 구분합니다. RxJava의 핵심은 "얼마나 많은 데이터를 전달하는가"와 "Back-pressure가 필요한가"에 따라 타입을 나눈다는 점입니다.

Observable

가장 기본이 되는 타입은 Observable입니다. Observable은 여러 개의 데이터를 순차적으로 발행할 수 있는 스트림 타입이지만, Back-pressure를 지원하지 않습니다. 따라서 대량의 데이터가 빠르게 유입될 경우 소비자가 이를 제어할 수 없다는 한계가 존재합니다.
Flowable

Observable를 보완하기 위해 등장한 타입이 Flowable입니다. Flowable은 Observable과 동일하게 다수의 데이터를 발행하지만, Back-pressure를 지원하여 Subscriber가 처리 가능한 만큼만 요청할 수 있습니다. 따라서 고부하 환경이나 대용량 처리 상황에서는 Flowable이 보다 적절한 선택이 됩니다.
Single

단일 결과를 표현하기 위한 타입도 별도로 존재합니다. Single은 반드시 하나의 결과가 존재함을 전제로 하며, 성공 시 하나의 값이 방출되고 실패 시 에러로 종료됩니다. 이는 Mono와 매우 유사한 개념입니다.
Maybe

값이 존재할 수도 있고 존재하지 않을 수도 있는 경우에는 Maybe가 사용됩니다. Maybe는 0 또는 1개의 값을 표현하며, "결과가 없을 수도 있음"까지 의미에 포함합니다.
Completable

마지막으로 Completable은 결과 값 없이 완료 여부만을 표현하는 타입입니다. 이는 Mono와 같이 완료 자체가 의미 있는 비동기 작업에 적합합니다.
Mutiny
Red Hat이 개발한 Mutiny는 함수형 체이닝 구조에 집중했던 기존 Reactive 라이브러리들과 달리, 개발자가 코드만 읽어도 처리 흐름을 이해할 수 있도록 DSL 스타일을 적극적으로 도입하였습니다.

Mutiny는 Uni와 Multi 두 가지 핵심 타입을 제공합니다.
Uni
Uni<T>는 최대 1개의 결과를 비동기적으로 전달하는 타입입니다.
성공 시 하나의 값을 발행하거나, 값 없이 완료되거나, 또는 에러로 종료될 수 있습니다. 이러한 구조는 Project Reactor의 Mono와 유사하지만, Mutiny는 실패 처리와 완료 개념을 더욱 명확하게 표현하도록 설계되었습니다.
Multi
반면 Multi<T>는 여러 개의 데이터를 시간의 흐름에 따라 연속적으로 발행하는 스트림 타입입니다. 이는 Project Reactor의 Flux에 해당하며, 이벤트 스트림이나 지속적인 데이터 흐름을 표현할 때 사용됩니다.
'Spring > Webflux' 카테고리의 다른 글
| Reactor operators (0) | 2025.12.08 |
|---|---|
| Netty - ByteBuf (2) | 2025.12.03 |
| Netty Server (0) | 2025.12.03 |
| Netty - EventLoop와 Channel (0) | 2025.12.02 |
| Spring portfolio (0) | 2025.11.27 |