본문으로 바로가기
반응형

최근 대규모 트래픽 처리, 외부 API 연동, MSA 환경 등에서 효율적인 서버 개발이 점점 중요해지면서, WebFlux와 같은 논블로킹 리액티브 웹 프레임워크를 이해하고 활용하는 것이 필요해졌습니다.

 

그래서 이 글에서는 Spring MVC와 WebFlux의 차이, 언제 무엇을 선택해야 하는지, 장단점을 작성해보려고 합니다.


먼저 차이를 알아보기 전에 동기/비동기 블로킹/논블로킹에 대해 간단한 개념 정리하고 가겠습니다.

 

Sync Blocking (동기 + 블로킹)

- "커피 주문하고 나올 때까지 카운터 앞에서 멍하니 기다리기"

- 요청을 보내고 결과가 올 때까지 제어권을 뺏긴 채 아무것도 하지 않고 기다림.

RestTemplate restTemplate = new RestTemplate();
String result = restTemplate.getForObject("https://example.com/get", String.class);
System.out.println(result);


Async Blocking (비동기 + 블로킹)

- "진동벨은 받았지만(비동기), 진동벨만 쳐다보며 아무것도 안하기"

- 비동기 작업을 요청했지만, 결국 그 결과가 나올 때까지 제어권을 얻지 못하고 대기하는 상황.

ExecutorService executor = Executors.newFixedThreadPool(10);
Future<String> future = executor.submit(() ->
    restTemplate.getForObject("https://example.com/get", String.class)
);
System.out.println(future.get());


Sync Non-Blocking (동기 + 논블로킹) 

- "커피 시키고 딴짓하다가, '다 됐어요?' 하고 물어보기"

- 요청 후 즉시 제어권을 돌려받아 다른 일을 할 수 있다.(Non-Blocking) 하지만 작업이 완료되었는지 확인하기 위해 계속해서 상태를 체크(Polling)해야 한다.

ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor();
Future<String> future = executor.submit(() ->
    restTemplate.getForObject("https://example.com/get", String.class)
);
System.out.println(future.get());


Async Non-Blocking (비동기 + 논블로킹)

- "커피 시키고 자리에 앉아 일하다가, 진동벨 울리면 가지로 가기"

- 요청 후 즉시 제어권을 받아 다른 일을 처리한다.(Non-Blocking) 작업이 끝나면 '완료되었다'는 신호(Callback/Event)를 받아 결과를 처리한다.(Async)

WebClient webClient = WebClient.create();
Mono<String> result = webClient.get()
    .uri("https://example.com/get")
    .retrieve()
    .bodyToMono(String.class);

result.subscribe(System.out::println);

 


1. Spring MVC란?

Spring MVC는 전통적인 Servlet 기반 웹 프레임워크.
Tomcat 같은 WAS 위에서 동작하며, 동기·블로킹 모델을 사용.

특징
Servlet API 기반
- 요청 1개 = 쓰레드 1개(Thread-per-request)
- FreeMarker, Thymeleaf, JSP 같은 템플릿 엔진과도 친함.
- 대부분의 라이브러리(JPA, MyBatis 등)와 폭넓은 호환성.

동작 흐름
- 요청 → 컨트롤러 → 서비스 → DB 호출 → 응답
- 모든 단계가 기본적으로 동기/블로킹.
- 즉, DB를 200ms 동안 조회하면 해당 요청 스레드는 200ms 동안 멈춤.

장점
- 익숙하고 배우기 쉬움
- 디버깅 쉬움
- 기존 레거시와 호환성 높음
- 웹 서비스 대부분에서 충분한 성능

단점
- 블로킹 I/O가 많은 경우 성능이 부족할 수 있음
- 많은 요청이 동시에 들어오면 스레드 부족 문제 발생
- 대표적으로 CPU나 I/O가 많은 대규모 트래픽 상황에서는 병목이 생긴다.

2. Spring WebFlux란?

Spring WebFlux는 논블로킹·리액티브 프로그래밍 기반의 웹 프레임워크.
핵심 기반은 Reactor (Mono, Flux)이며, Netty 같은 논블로킹 서버 엔진을 사용할 수 있음.

특징
- Non-blocking & Asynchronous
- Reactive Streams 기반
- Mono(단일), Flux(다중)를 통해 데이터 전달
- 소수의 스레드로도 높은 처리량(throughput)을 낼 수 있음

동작 구조
- 이벤트 루프 기반으로 동작하며, I/O 작업이 생기면 기다리지 않고 다른 요청을 처리한다.

장점
- 높은 동시성 처리에 유리
- 외부 API 호출이 많은 MSA 환경에서 강력함
- WebSocket, SSE 같은 스트리밍 환경과 궁합이 좋음

단점
- 리액티브 프로그래밍에 대한 학습 곡선
- 디버깅과 트레이싱이 MVC보다 어려움
- 모든 라이브러리가 리액티브를 지원하는 것이 아님

 

아래 코드는 순차 처리와 병렬 처리에 대한 비교이고 동시에 여러 요청을 보낼 때 차이가 명확해짐.

MVC는 스레드 풀이 고갈될 수 있지만, WebFlux는 이벤트 루프로 효율적으로 처리함.

 

=== 비교 완료: MVC 2089ms vs WebFlux 791ms ===

    @GetMapping("/sequential")
    public Mono<Map<String, Object>> compareSequentialProcessing(@RequestParam List<Long> ids) {

        log.info("=== 순차 처리 vs 병렬 처리 비교 시작 ===");

        // MVC 방식 (블로킹, 순차)
        long mvcStart = System.currentTimeMillis();
        List<Post> mvcPosts = mvcService.getMultiplePostsSequential(ids);
        long mvcDuration = System.currentTimeMillis() - mvcStart;

        log.info("MVC 처리 완료: {}ms", mvcDuration);

        // WebFlux 방식 (논블로킹, 병렬)
        return Mono.fromCallable(() -> System.currentTimeMillis())
                .flatMap(webFluxStart -> webFluxService.getMultiplePostsParallel(ids)
                        .map(webFluxPosts -> {
                            long webFluxDuration = System.currentTimeMillis() - webFluxStart;
                            log.info("WebFlux 처리 완료: {}ms", webFluxDuration);

                            Map<String, Object> result = new HashMap<>();
                            result.put("mvc", Map.of(
                                    "duration_ms", mvcDuration,
                                    "count", mvcPosts.size(),
                                    "type", "순차 처리 (블로킹)"
                            ));
                            result.put("webflux", Map.of(
                                    "duration_ms", webFluxDuration,
                                    "count", webFluxPosts.size(),
                                    "type", "병렬 처리 (논블로킹)"
                            ));
                            result.put("performance_gain", String.format(
                                    "WebFlux가 약 %.1f배 빠름",
                                    (double) mvcDuration / webFluxDuration
                            ));

                            log.info("=== 비교 완료: MVC {}ms vs WebFlux {}ms ===",
                                    mvcDuration, webFluxDuration);

                            return result;
                        })
                );
    }

 

    public List<Post> getMultiplePostsSequential(List<Long> ids) {
        String threadName = Thread.currentThread().getName();
        log.info("[MVC] 여러 게시물 순차 조회 시작: {} - Thread: {}", ids, threadName);

        long startTime = System.currentTimeMillis();

        List<Post> posts = ids.stream()
                .map(this::getPost)
                .collect(Collectors.toList());

        long duration = System.currentTimeMillis() - startTime;
        log.info("[MVC] 여러 게시물 순차 조회 완료 - 총 소요시간: {}ms, Thread: {}",
                duration, threadName);

        return posts;
    }

 

    public Mono<List<Post>> getMultiplePostsParallel(List<Long> ids) {
        String threadName = Thread.currentThread().getName();
        log.info("[WebFlux] 여러 게시물 병렬 조회 시작: {} - Thread: {}", ids, threadName);

        long startTime = System.currentTimeMillis();

        return Flux.fromIterable(ids)
                .flatMap(this::getPost)  // 모든 요청을 병렬로 실행
                .collectList()
                .doOnSuccess(posts -> {
                    long duration = System.currentTimeMillis() - startTime;
                    String currentThread = Thread.currentThread().getName();
                    log.info("[WebFlux] 여러 게시물 병렬 조회 완료 - 총 소요시간: {}ms, Thread: {}",
                            duration, currentThread);
                });
    }

 

MVC는 단순/안정적인 블로킹 웹 프레임워크이고 WebFlux는 고성능/논블로킹 리액티브 웹 프레임워크 이므로 둘 중 무엇이 더 좋다는 개념은 없고, 프로젝트 특성에 따라 선택하는것이 정답인거 같다.

반응형

'n년차 개발자' 카테고리의 다른 글

개인화 추천 API 성능 개선  (0) 2025.11.27
서버 자원 수집 및 시각화  (0) 2025.04.28
Spring Boot Logstash 연동 설정 및 작동 방식  (1) 2025.04.16
모니터링 시스템 구축  (0) 2025.04.07
AWS SDK 버전 충돌  (0) 2025.04.05