더 많은 도움을 드리기 위해

열심히 포스팅 중입니다!


지나가다 📢 광고 한 번 눌러주시면

더 좋은 글로 보답하겠습니다. 🥰

기술 면접 준비

Spring MVC vs Spring WebFlux - 기술 면접 준비

평비 - Giveloper 2025. 6. 16. 19:58

 

👋 

안녕하세요~ 평비입니다!

오늘은 지난 포스팅에 이어서 Spring WebFlux와 Spring MVC를 비교한 포스팅을 준비해봤습니다!

 

 

1. 기술 스택 비교

 

간단하게 보자면, 왼쪽은 Spring WebFlux. 오른쪽은 Spring MVC입니다.

 

2가지 모두 공통적으로 Spring Boot 기반인데, 특이한 점으로는 Reactor도 공통입니다. 이 뜻은 Servlet 스택인 Spring MVC에서도 Reactor를 사용할 수 있는 것입니다.

 

✋ 다만, Servlet 스택 자체가 Blocking IO 방식이기 때문에, Reactor를 사용한다고 해도 Non-blocking IO 방식을 제대로 사용하지는 못한다네요.

 

Servlet 스택에서는 익숙한 기술들이 보입니다. Tomcat과 같은 다양한 Servlet 컨테이너와 Servlet API를 이용하고, 보안을 위해 Spring Security, DB 접근을 위해서는 JDBC, JPA, NoSQL이 사용됩니다.

 

반면, Reactive 스택에서는 Netty와 같은 비동기 Non-blocking을 지원하는 서버 엔진이 기본 엔진으로 사용됩니다. 하지만 3.1 버전 이상의 Servlet 컨테이너도 사용 가능하며, 이외에 Jetty, Undertow도 사용가능합니다.

 

이에 따라 일반적인 Servlet API는 보이지 않고, Reactive Streams Adaptor가 사용되네요. 또한 DB 접근으로는 아직까지 관계형 DB가 지원되지는 않고, Mongo, Cassandra, Redis와 같은 NoSQL 진영이 사용되네요.

 


2. 특성 비교

 

공통

  • Spring MVC에서 API의 엔드포인트를 구현하는 방식으로 @Controller 어노테이션이 쓰이죠. Spring WebFlux에서도 @Controller 어노테이션을 사용해서 구현할 수 있습니다.
  • 특징의 차이나 한계가 있긴 하지만, 둘 다 Reactive client를 구축할 수 있습니다.
  • Spring MVC와 WebFlux 모두 Tomcat, Jetty, Undertow 같은 서블릿 컨테이너 사용할 수 있습니다.

 

Spring MVC

  • 명령형 로직을 사용하기 때문에, 비교적 간단하여 이해하기 쉽고 디버깅이 쉽습니다.
  • 블로킹 의존성 - JDBC, JPA와 같은 외부 시스템/라이브러리 등이 블로킹 방식으로 작동합니다.

 

Spring WebFlux

  • @Controller 어노테이션 뿐만 아니라, 함수형 엔드포인트 방식으로도 구현할 수 있습니다.
  • 이벤트 루프라는 동시성 모델을 사용하기 때문에 Spring MVC에 비해 비교적 이해하기 어려운 단점이 있습니다.
  • 기본 서버 엔진은 Netty입니다.

 

3. 용어 설명

🔍 Reactive client

리액티브 시스템으로부터 데이터를 소비하는 애플리케이션 또는 구성 요소. 특징은 다음과 같습니다.

  • 논블로킹 통신 - I/O 작업을 논블로킹으로 수행한다. 응답을 기다리는 대신, 준비될 때 알림을 받는 메커니즘을 설정합니다.
  • 백프레셔 처리 - 생산자/소비자 간 처리 속도 차이가 있을 때, 호출 서버에 처리가 어렵다는 신호를 보낼 수 있습니다.
  • 비동기 작업 - 리액티브 스트림을 지원하는 프레임워크나 라이브러리를 활용하여 비동기 모델 사용합니다.

 

🔍 함수형 엔드포인트 방식

어노테이션 없이 직접 함수들을 조합하여 웹 요청 라우팅과 처리를 정의하는 논블로킹 방식의 웹 개발 스타일이라고 보시면 됩니다. 두 가지 주요 구성 요소로 나뉩니다.

 

1. Router Function (라우터 함수)

들어오는 HTTP 요청(Request)을 적절한 HandlerFunction으로 매핑(라우팅)하는 역할을 합니다.

@Configuration
public class MyRoutingConfiguration {

    @Bean
    public RouterFunction<ServerResponse> myRoutes(MyHandler handler) {
        return RouterFunctions.route()
            .GET("/users", handler::listUsers) // GET /users 요청을 handler::listUsers 메서드로 라우팅
            .GET("/users/{id}", handler::getUserById) // GET /users/{id} 요청을 handler::getUserById 메서드로 라우팅
            .POST("/users", handler::createUser) // POST /users 요청을 handler::createUser 메서드로 라우팅
            .build();
    }
}

 

2. Handler Function (핸들러 함수)

Router Function에 의해 라우팅된 특정 HTTP 요청을 실제로 처리하는 비즈니스 로직을 포함합니다.

@Component
@RequiredArgsConstructor
public class MyHandler {

    private final UserService userService;

    public Mono<ServerResponse> listUsers(ServerRequest request) {
        return ServerResponse.ok()
                .contentType(MediaType.APPLICATION_JSON)
                .body(userService.findAllUsers(), User.class);
    }

    public Mono<ServerResponse> getUserById(ServerRequest request) {
        String id = request.pathVariable("id");
        return userService.findById(id)
                .flatMap(user -> ServerResponse.ok().contentType(MediaType.APPLICATION_JSON).bodyValue(user))
                .switchIfEmpty(ServerResponse.notFound().build());
    }

    public Mono<ServerResponse> createUser(ServerRequest request) {
        Mono<User> userMono = request.bodyToMono(User.class);
        return userMono
                .flatMap(userService::saveUser
                .flatMap(savedUser -> ServerResponse.created(URI.create("/users/" + savedUser.getId())).build());
    }
}

 

 

그럼, Spring WebFlux는 Spring MVC보다 항상 속도가 빠른 것일까?

 

4. 성능 비교

직접 부하/성능 테스트를 한 것은 아니고 타 블로그의 성능 테스트 사례를 참고하였다.

 

1. 테스트케이스 1

  • 초당 400회 총 3만개의 요청
  • HeapSize는 각 4GB로 동일하게 설정
  • 각 스레드는 12개로 설정 (Webflux 같은 경우 core(6) 수 x 2 로 만들어지기 때문에 MVC도 Webflux thread 개수에 맞춤)
  • WebFlux + R2DBC vs WebMVC + JDBC
기준 표본 수 응답 평균 응답 최소 응답 최대 표준편차 오류 % 처리량 수신 KB/s
WebFlux 30761 2380 21 28934 2586.65 0.74% 166.3/s 16.48
WebMVC 30652 7349 3010 117446 14178.05 93.73% 52.7/s 137.25

출처: HYK - Webflux + R2dbc (nonblocking) vs MVC + JDBC (blocking) 성능 테스트

 

 

2. 테스트케이스 2

  • 1ms 간격으로 1000개 요청 총 100번 호출 (총 10만개 요청)
  • 동일한 스펙의 서버
  • 각 서버는 기본 설정
  • WebFlux + R2DBC vs WebMVC + JPA (Max Thread 200)
기준 응답 최소 응답 최대 평균 처리량
WebFlux 1 252 3700 rps
WebMVC 24 2250 770 rps

 

WebFlux + R2DBC 응답 시간 분포 (~253ms)

 

WebFlux + R2DBC 초당 응답수 (평균 3700rps)

 

WebMVC + JPA 응답 시간 분포 (~2250ms)

 

WebMVC + JPA 초당 응답수 (평균 770rps)

 

출처: MANTY SCUBA - WebFlux+R2DBC 와 WebMVC+JPA 성능 대결

 

 

3. 테스트케이스 3

  • 동시 요청 4 ~ 500까지 50씩 요청을 늘려가며 테스트 (DB에서 10개 record SELECT -> JSON)
  • 코어 할당: 각각 4개의 코어 할당
  • connection pool: 100
  • DB는 Postgres 12.2
  • 2초 동안 서비스에 과도한 부하를 주어 '예열'
  • 시나리오는 20번 반복 (연속이 아닌 다른 테스트와 분리하여)
  • 오류가 발생하지 않은 실행 결과만 분석하고 평균 산출

동시성에 따른 기술 조합별 처리량

  • Spring WebFlux + R2DBC 조합은 동시성에 따른 처리량 기준 가장 높은 성능을 보입니다.
  • Spring MVC + JDBC 조합은 동시성이 올라갈 수록 처리량이 낮아집니다.
  • Spring WebFlux + JDBC 조합이 성능이 가장 낮은 것을 볼 수 있습니다.

출처: AMIS Conclusion - Spring: Blocking vs non-blocking: R2DBC vs JDBC and WebFlux vs Web MVC

 

 

 

👏

속도 측면이든, 처리량 측면이든, 동시성 측면이든 모든 부분에서 Spring WebFlux가 Spring MVC보다 성능이 탁월한 것은 맞네요. 다만, 마지막 테스트케이스에서 볼 수 있듯, WebFlux라도 R2DBC와 같은 논블로킹 DB API가 아닌 JDBC와 같은 블로킹 DB API를 사용하면 성능이 보장되지 않는다는 것을 알 수 있습니다.

 

즉, WebFlux를 사용할 때는 요청 ~ 응답 모든 프로세스에 논블로킹 동작이 적용되어야 최대 성능을 발휘할 수 있습니다.

 

자, 그러면 논블로킹 I/O를 활용할 수 있는 서버를 적용할 예정이라면 WebFlux를 활용하면 좋을 것 같은데, Spring MVC로 구축한 기존 서버를 Spring WebFlux로 넘어가는 것은 큰 리스크와 공수가 동반되겠죠... 이때, 대안은 어떤 것들이 있을까요?

 

이 대안에 대해서 다음 포스트에서 다뤄보겠습니다.

 
평비의 이 평범한 글이 여러분에게 비범한 도움이 되었으면 좋겠습니다 👍