IT기술/webflux (reactor)

[WebFlux] Scheduler의 역할과 활용 방법

후스파 2025. 4. 29. 22:21
반응형

spring webflux reactor

Spring WebFlux는 Reactor 프로젝트를 기반으로 비동기·논블로킹 애플리케이션을 구축합니다. 이 환경에서 Scheduler는 스레드 관리를 담당하며, 병렬 처리와 리소스 최적화의 핵심 요소입니다.

 


 

1. Scheduler 기본 개념

 

1.1 물리 스레드 vs 논리 스레드

구분 설명
물리 스레드 CPU 코어 수에 해당, 하드웨어적으로 생성 (예: 8코어 CPU → 8물리 스레드)
논리 스레드 소프트웨어적으로 생성·관리, 동시성 구현을 위해 사용

 

1.2 Scheduler의 역할

  • 스레드 풀 관리: 작업을 적절한 스레드에 할당
  • 실행 컨텍스트 제어: subscribeOn, publishOn과 조합해 실행 흐름 최적화
  • 리소스 제한: 스레드 개수, 대기 큐 크기 등을 제어해 시스템 과부하 방지

 


 

2. 주요 Scheduler 종류

 

2.1 기본 제공 Scheduler

Scheduler 특징 사용 시나리오
immediate() 현재 스레드에서 즉시 실행 테스트, 간단한 동기 작업
single() 단일 스레드 재사용 짧은 지연 작업
boundedElastic() Blocking I/O 최적화 (기본: CPU 코어×10, 최대 100,000 작업 큐) DB 호출, 파일 I/O
parallel() Non-Blocking CPU 작업 최적화 (CPU 코어 수만큼 스레드 생성) 계산 집약적 작업
newXXX() 커스텀 스레드 풀 생성 (이름, 데몬 스레드 옵션 등) 특수 요구사항이 있는 경우

 

2.2 Scheduler 선택 가이드

  • Blocking 작업: boundedElastic() (예: JDBC, 레거시 시스템 통합)
  • Non-Blocking 작업: parallel() (예: Reactive MongoDB, WebClient)
  • 단순 작업: single() 또는 immediate()

 


 

3. 핵심 Operator와의 조합

 

3.1 subscribeOn()

  • 특징: 구독 시점의 스레드를 지정 (전체 체인 영향)
  • 사용법: 체인 어디에 위치해도 동일하게 동작
    Mono.fromCallable(() -> blockingApiCall())
      .subscribeOn(Schedulers.boundedElastic()) // Blocking 작업에 필수
      .subscribe();

 

3.2 publishOn()

  • 특징: 하위 체인의 스레드만 변경 (위치에 따라 영향 범위 달라짐)
    Flux.range(1, 10)
      .publishOn(Schedulers.parallel()) // 이후 연산은 parallel 스레드
      .map(i -> i * 2)  // parallel 스레드에서 실행
      .publishOn(Schedulers.single())   // 이후 연산은 single 스레드
      .subscribe();     // single 스레드에서 실행

 

3.3 parallel() + runOn()

  • 병렬 처리 전용 조합
    Flux.range(1, 100)
      .parallel(4)                // 4개 물리 스레드 분할
      .runOn(Schedulers.parallel()) // 각 분할 작업을 parallel 스레드에서 실행
      .map(i -> i * 2)
      .sequential()
      .subscribe();

 


 

4. 실전 활용 예제

 

4.1 Blocking API 호출

public Mono fetchUser(String id) {
    return Mono.fromCallable(() -> legacyBlockingApi.getUser(id))
               .subscribeOn(Schedulers.boundedElastic());
}

 

4.2 Non-Blocking 병렬 처리

Flux.range(1, 1000)
    .parallel()
    .runOn(Schedulers.parallel())
    .map(i -> intensiveCalculation(i))
    .sequential()
    .collectList()
    .subscribe();

 

4.3 Hybrid 처리 (Blocking + Non-Blocking)

webClient.get()
         .uri("/api/data")
         .retrieve()
         .bodyToMono(Data.class)
         .publishOn(Schedulers.parallel())     // Non-Blocking 처리
         .flatMap(data -> processData(data))
         .publishOn(Schedulers.boundedElastic()) // Blocking 저장 작업
         .flatMap(result -> saveToLegacyDB(result))
         .subscribe();

 


 

5. 주의사항 및 모범 사례

  1. Blocking 작업 반드시 분리: boundedElastic() 없이 Blocking 호출 시 성능 저하
  2. Operator 위치 신중히 결정: publishOn은 하위 체인만 영향
  3. 스레드 풀 남용 금지: newXXX() 남발 시 메모리 누수 가능성
  4. 리소스 정리: Scheduler 인스턴스는 사용 후 dispose() 호출
  5. 테스트 시 가상 시간 활용: StepVerifier.withVirtualTime()

 


 

결론

WebFlux에서 Scheduler를 효과적으로 활용하려면:

  1. 작업 유형(Blocking/Non-Blocking) 식별
  2. 적합한 Scheduler 선택 (boundedElastic vs parallel)
  3. subscribeOnpublishOn의 실행 범위 이해
  4. 병렬 처리 시 parallel() + runOn() 조합 사용

이를 통해 시스템 리소스를 효율적으로 관리하면서 고성능 비동기 애플리케이션을 구축할 수 있습니다.

 

 

[WebFlux] Scheduler

Spring WebFlux는 비동기 및 논블로킹 애플리케이션을 구축하기 위한 프레임워크로, Reactor 패러다임...

blog.naver.com

 

 

[WebFlux] 리액티브 스트림즈 Sinks: 핵심 개념과 활용 가이드

Reactor의 Sinks는 리액티브 스트림즈에서 프로그래밍 방식으로 Signal을 전송하는 표준 메커니즘입니다. 기존 Processor의 한계를 극복하고, 멀티스레드 환경에서 안정적인 데이터 방출을 보장합니다.

hoosfa.tistory.com

 

반응형