어제 오늘 내일

[Reactive] "데이터가 강물처럼 흐른다?" 리액티브 스트림즈(Reactive Streams)와 Backpressure 완벽 이해 본문

IT/SpringBoot

[Reactive] "데이터가 강물처럼 흐른다?" 리액티브 스트림즈(Reactive Streams)와 Backpressure 완벽 이해

hi.anna 2026. 3. 31. 00:13

 

자바 개발자에게 데이터란 무엇일까요? 보통 List, Map 같은 컬렉션에 담긴 "완성된 결과물"을 의미했습니다.
DB에서 100만 건을 조회하면, 100만 건이 다 메모리에 올라올 때까지 기다렸다가 한 번에 List로 받아서 처리했죠. (Memory: "살려줘...")

하지만 리액티브 프로그래밍(Reactive Programming)에서는 다릅니다.
데이터는 고여있는 호수가 아니라, 끊임없이 흐르는 강물(Stream)과 같습니다.

오늘은 WebFlux의 뼈대가 되는 표준 사양, 리액티브 스트림즈(Reactive Streams)와 그 핵심 기능인 백프레셔(Backpressure)를 알아보겠습니다.


1. Iterable(과거) vs Reactive Streams(미래)

가장 큰 차이는 "누가 주도권을 쥐고 있는가?"입니다.

  • Iterable (for-each): 소비자가 주도합니다.
  • "냉장고(List) 문 열어. 사과 하나 꺼내(next()). 또 꺼내. 더 없어? 끝."
  • 데이터가 이미 준비되어 있어야 합니다.
  • Reactive Streams (Publisher-Subscriber): 생산자가 주도하되, 소비자가 조절합니다.
  • "나 구독할게(subscribe). 데이터 생기면 나한테 던져줘(onNext)."
  • 데이터가 미래의 어느 시점에 도착합니다. (비동기)

2. 리액티브 스트림즈의 4대장 (인터페이스)

이 4가지 인터페이스가 서로 대화를 주고받으며 데이터를 처리합니다.

  1. Publisher (생산자): 데이터를 만들어내는 녀석입니다. (예: DB, 외부 API)
  • subscribe(Subscriber s): "구독자, 들어와!"
  1. Subscriber (소비자): 데이터를 받아서 처리하는 녀석입니다. (예: 우리 서버, 클라이언트)
  • onSubscribe(Subscription s): "구독 시작됐다!"
  • onNext(T t): "데이터 하나 도착!"
  • onError(Throwable t): "에러 발생!"
  • onComplete(): "데이터 다 보냈어. 끝!"
  1. Subscription (구독권): 생산자와 소비자 사이의 연결 고리이자 리모컨입니다.
  • request(long n): "n개만 더 줘." (★ 핵심)
  • cancel(): "구독 취소할래."
  1. Processor (중개자): 생산자와 소비자 사이에서 데이터를 가공하는 녀석입니다. (Publisher이자 동시에 Subscriber)

3. 백프레셔(Backpressure): "체할 것 같으니까 천천히 줘!"

리액티브 스트림즈가 탄생한 진짜 이유는 바로 Backpressure(배압) 때문입니다.

😱 문제 상황: Push 모델의 비극

생산자(Publisher)는 초당 10,000개의 데이터를 뿜어냅니다.
그런데 소비자(Subscriber)는 1초에 100개밖에 처리를 못 합니다.
-> 소비자의 메모리 큐(Queue)에 데이터가 계속 쌓이다가... OutOfMemoryError 펑! 서버가 죽습니다.

😎 해결책: Pull 모델의 도입 (Backpressure)

소비자가 생산자에게 이렇게 말합니다.
"나 지금 바쁘니까 딱 10개만 줘(request(10)). 다 처리하면 더 달라고 할게."

이것이 바로 백프레셔입니다. 소비자가 감당할 수 있는 만큼만 데이터를 요청해서 시스템의 안정성을 보장하는 기술이죠.


4. 코드 흐름으로 이해하기

말로만 하면 어려우니, 실제 동작 흐름을 의사 코드(Pseudo-code)로 볼까요?

// 1. 소비자가 구독을 요청합니다.
publisher.subscribe(subscriber);

// 2. 생산자가 구독권(Subscription)을 줍니다.
// [Subscriber 내부]
public void onSubscribe(Subscription s) {
    this.subscription = s;
    // ★ 핵심: "일단 1개만 줘봐." (Backpressure)
    s.request(1); 
}

// 3. 생산자가 데이터를 1개 보냅니다.
// [Subscriber 내부]
public void onNext(Data data) {
    System.out.println("데이터 처리: " + data);

    // 처리 끝났으니 1개 더 줘!
    this.subscription.request(1);
}

이렇게 하면 생산자가 아무리 빨라도, 소비자가 request()를 하지 않으면 데이터를 보내지 않기 때문에 서버가 터질 일이 없습니다. 시스템의 회복 탄력성(Resilience)이 엄청나게 좋아집니다.


5. WebFlux와의 관계

Spring WebFlux는 이 Reactive Streams 표준을 구현한 Project Reactor라는 라이브러리를 기반으로 만들어졌습니다.

  • WebFlux에서는 저 복잡한 Subscription, request() 코드를 직접 짤 필요가 없습니다.
  • MonoFlux라는 아주 편리한 도구가 다 알아서 해주거든요.

우리는 그저 "데이터가 흐른다", "소비자가 속도를 조절한다"는 개념만 머릿속에 넣으면 됩니다.


마치며

오늘의 결론입니다.

  1. Reactive Streams는 데이터를 한 번에 받지 않고 흐름(Stream)으로 처리하는 표준이다.
  2. Publisher(생산자)가 데이터를 주고, Subscriber(소비자)가 받는다.
  3. Backpressure는 소비자가 "나 처리할 수 있는 만큼만 줘!"라고 요청해서 시스템 과부하를 막는 핵심 기술이다.

개념은 잡혔는데, "그래서 자바 코드로 어떻게 짜는데?"라는 의문이 드시죠? List 대신 뭘 써야 할까요?

다음 포스팅에서는 WebFlux의 양대 산맥, 01개의 데이터를 다루는 Mono와 0N개의 데이터를 다루는 Flux에 대해 알아보며 본격적인 코딩을 시작해 보겠습니다.

도움이 되셨다면 좋아요와 댓글 부탁드립니다! 😊

반응형
Comments