codingstairs
노트에듀라이프연락
⌕검색⌘K
koen

Navigation

  • Intro
  • Blog
  • Life

연락하기

로그인 없이도 보낼 수 있어요. 답변이 필요하면 이메일을 함께 적어 주세요.

  • 익명 폼으로 의견 남기기 →
  • ✉ warragon112@gmail.com
  • 카카오톡 오픈채팅 ↗

© 2026 codingstairs

  • 노트
  • 에듀
  • 검색
  • 라이프
  • 연락
  • 약관
  • RSS
  • GitHub
노트›backend

Spring MVC 와 WebFlux

2026-04-28 게시· 2026-05-18 갱신·0회 조회

Spring MVC 와 WebFlux

Spring 에는 두 가지 웹 스택이 공존합니다. 오래된 Spring MVC (Servlet 기반, 동기 블로킹) 와 비교적 새로운 Spring WebFlux (Reactor 기반, 비동기 논블로킹).

1. 두 스택에 대한 이야기

항목 Spring MVC Spring WebFlux
등장 Spring 1.0 (2004) 부터 Spring 5.0 (2017) 부터
기반 Servlet API (Tomcat · Jetty · Undertow) Reactor (Netty 기본, Servlet 컨테이너도 가능)
모델 요청당 스레드 점유 이벤트 루프 + 비동기 콜백
핵심 타입 ResponseEntity<T>, 동기 반환 Mono<T> · Flux<T>

Reactor 는 Pivotal/VMware 가 주도한 JVM 의 reactive streams 구현입니다. Reactive Streams 사양 (2015) 의 구현체 중 하나입니다. Mono 는 0–1 개, Flux 는 0–N 개의 비동기 시퀀스를 나타냅니다.

R2DBC (Reactive Relational Database Connectivity) 는 2018 년경 시작되어 2019 년 0.8 으로 비교적 안정화된 사양으로 알려져 있습니다. JDBC 가 본질적으로 블로킹 API 인 점을 우회하기 위한 별도 사양입니다.

2. 동기 블로킹 (MVC)

요청 한 건이 스레드 한 개를 점유합니다. DB · 외부 API · 파일 IO 동안 그 스레드는 대기합니다. Tomcat 의 기본 워커 풀 (보통 200) 만큼만 동시에 처리할 수 있고, 그 너머의 요청은 큐에서 기다립니다. 코드는 직선적이고 디버깅·예외 추적이 쉽습니다.

3. 비동기 논블로킹 (WebFlux)

이벤트 루프 스레드는 요청을 받고 콜백을 등록한 뒤 다음 요청으로 넘어갑니다. IO 가 완료되면 다시 깨어나 처리합니다. 적은 스레드로 많은 동시 연결을 다룰 수 있습니다. 대신 콜백/연산자 체인이 길어지면 흐름 추적이 어려워집니다.

// MVC
@GetMapping("/u/{id}")
public User get(@PathVariable Long id) {
    return userRepo.findById(id).orElseThrow();
}

// WebFlux
@GetMapping("/u/{id}")
public Mono<User> get(@PathVariable Long id) {
    return userRepo.findById(id);
}

4. Reactor 핵심

  • Mono.just(x) · Mono.empty() · Mono.error(e) 같은 생성자.
  • map · flatMap · filter · zip · merge 같은 연산자.
  • 구독 (subscribe) 시점에야 실제 실행됩니다 (콜드 시퀀스).
  • 백프레셔 (backpressure) 지원 — 다운스트림이 처리 가능한 만큼만 요청합니다.

5. R2DBC

JDBC 를 reactive 환경에서 그대로 쓰면 결국 어딘가에서 스레드를 막게 됩니다. R2DBC 는 드라이버 수준에서 논블로킹을 보장합니다. PostgreSQL · MySQL · SQL Server · H2 등의 R2DBC 드라이버가 존재합니다. 다만 JDBC 만큼 도구 생태계가 두텁지는 않고 일부 기능 (저장 프로시저·복잡한 트랜잭션 패턴) 은 제한적입니다.

6. Java 21 가상 스레드

Java 21 Virtual Threads (Project Loom, JEP 444, 2023-09 GA) 는 JVM 이 운영체제 스레드 대신 가상 스레드를 제공합니다. Servlet 기반 MVC 에 가상 스레드를 켜면 코드는 그대로 (동기 블로킹) 두면서 동시성 한계를 크게 풀 수 있다는 점이 자주 언급됩니다.

WebFlux 의 동기적 매력 중 일부를 MVC 가 흡수하는 흐름이라 평가됩니다. WebFlux 가 항상 정답이 아닐 수 있다는 의미입니다.

7. Kotlin Coroutines · 다른 프레임워크

Spring 은 WebFlux 위에서 코루틴 (suspend fun) 을 1 급으로 지원합니다. Reactor 연산자 대신 순차 코드처럼 작성하면서 비동기 이득은 유지합니다.

JVM 위의 다른 프레임워크 — Quarkus · Micronaut. 컴파일 타임 DI · GraalVM 친화 같은 지점이 다릅니다.

8. .block() 점진 제거

WebFlux 코드에서 .block() 은 현재 스레드를 막습니다. 이벤트 루프 스레드에서 호출하면 전체 처리량이 무너질 수 있습니다. 흔한 점진 제거 순서:

① 컨트롤러 반환 타입을 Mono/Flux 로 변경
② 서비스 계층의 T 반환을 Mono<T> 로 끌어올림
③ 리포지토리(R2DBC)까지 닿으면 중간 .block() 모두 제거
④ 외부 HTTP 호출은 WebClient (RestTemplate 의 비동기 대체)

9. 두 스택의 혼용

같은 모듈에서 MVC 와 WebFlux 컨트롤러를 동시에 쓰는 것은 권장되지 않습니다. 모듈을 분리하거나, 한쪽 스택 위에서 다른 쪽 라이브러리만 (예: MVC 위에서 WebClient) 도입하는 식이 안전합니다.

10. 자주 걸리는 자리

이벤트 루프 위에서의 블로킹 호출 — JDBC · 파일 IO · Thread.sleep · 무거운 CPU 연산. Schedulers.boundedElastic() 같은 별도 스케줄러로 옮겨야 합니다.

Mono.subscribe() 만 호출하고 결과 무시 — WebFlux 컨트롤러는 반환된 Mono/Flux 를 프레임워크가 구독합니다. 직접 subscribe() 를 호출하면 내부적으로 fire-and-forget 이 되어 트랜잭션·에러 처리가 누락될 수 있습니다.

트랜잭션 경계 — R2DBC 트랜잭션은 TransactionalOperator 또는 @Transactional (반응형 매니저 설정 필요) 로 다뤄야 하고 JDBC 와 같은 의미가 아닙니다.

디버깅 — 스택 트레이스가 연산자 체인 위에서 끊깁니다. Hooks.onOperatorDebug() 또는 BlockHound · checkpoint 연산자가 도움이 됩니다.

하고픈 말

WebFlux 가 항상 더 빠르거나 더 모던한 것은 아닙니다. 진짜 이득은 IO 대기 시간이 처리 시간보다 훨씬 큰 자리에서 보입니다. Java 21 가상 스레드 도입 후에는 MVC 의 단순함이 다시 매력적인 자리가 늘 것입니다.

Next

  • fastapi-philosophy

Spring MVC 공식 · Spring WebFlux 공식 · Project Reactor · Reactive Streams 사양 · R2DBC 공식 · JEP 444 Virtual Threads · Spring + Virtual Threads 을 참고합니다.

backend 카테고리의 다른 글

카테고리 전체 보기 →
  • 공공 OpenAPI 는 자체 BFF 로 한 번 감싼다
  • 이메일 발송과 OTP — SMTP
  • 감사로그 — logAdminAction 패턴
  • WebSocket · SSE — 실시간 통신
  • REST API 입문
  • OpenAPI 사양