MSA 심장부를 설계하다: Theme3. MVC, WebFlux 경계

dev_will_d·2024년 4월 3일
5
post-thumbnail

Spring Framework 기반으로 Web Application Server를 개발한다고 하면 크게 두가지 방법이 있다.
1. spring-web-mvc: Sync, Blocking 기반
2. spring-webflux: Async, NonBlocking 기반

두 방식은 서로 다른 모델을 기반으로 Web Application Server를 개발 할 수 있도록 한다. 서로 다른 두 모델의 차이점을 이해하고 MSA 프로젝트에서 어떻게 경계를 나눠 사용하는지 알아보자.

Spring Web MVC 동작 방식

  • 기본 설명
    Spring Web MVC는 기본적으로 서블릿 컨테이너인 Tomcat을 기반으로 Sync, Blocking 방식으로 작업을 수행한다. Spring Web MVC에서는 효율적인 Thread 관리를 위해 Thread Pool을 운용하며, 하나의 요청에 대해 Thread Pool에서 관리중인 하나의 Thread를 할당하고 Thread Pool의 최소 Thread 갯수를 넘어 그 이상의 요청이 들어온다면 Thread를 생성하여 각각의 요청에 대해 Thread를 할당한다. (단, Tomcat은 Thread Pool에서 기본적으로 관리할 최소 Thread의 갯수를 정하고 동시에 다량의 요청이 들어왔을때 추가로 생성하여 대응할 최대 Thread의 갯수를 설정을 통해 관리한다. 즉, 이 최대 Thread의 갯수가 초과 되면 더이상 Thread를 생성하지 않고 나머지 요청을 대기상태로 진입시킨다. 또한 최대 Thread의 갯수를 초과할 만큼의 다수의 요청이 끝나고 일정 시간이 지나면 Thread를 회수하여 Thread Pool에 최소 Thread의 갯수를 유지하여 자원을 효율적으로 관리한다.) Spring Web MVC에서 단일 요청을 담당하는 Thread는 특정 요청의 작업이 모두 완료될때 까지 기본적으로 순차적으로 작업을 진행하고 작업을 진행하는 동안 이외의 작업을 수행할 수 없다. 즉, 요청을 처리하는 Thread는 Blocking 상태가 된다.
  • 요약
    - Spring Web MVC는 효율적인 Thread 관리를 위한 Thread Pool 운용
    • Spring Web MVC는 설정을 통해 Thread Pool에 기본적으로 관리할 최소 Thread 갯수 지정
    • Spring Web MVC는 설정을 통해 Thread Pool에서 관리할 최대 Thread 갯수 지정
    • 최소 Thread의 갯수를 넘는 요청이 발생하면 추가로 Thread를 생성하여 대응하고 최대 Thread를 초과하는 다량의 요청이 들어오면 더이상 Thread를 생성하지 않고 나머지 요청은 대기 상태로 진입
    • 최대 Thread 갯수를 초과하는 다량의 요청이 모두 종료 되고 일정시간이 지나면 Tomcat은 효율적인 자원관리를 위해 최소 Thread 갯수만 Thread Pool에 유지
    • 하나의 요청에 대해 하나의 Thread를 할당하며 특정 요청에 할당된 Thread는 기본적으로 작업을 순차적으로 진행하며 작업이 모두 완료 될때 까지 Blocking 상태를 유지

Spring WebFlux 동작 방식

  • 기본 설명
    Spring Webflux는 기본적으로 Netty와 Reactor(Reactive Streams 구현체)를 기반으로 Async, NonBlocking 방식으로 작업을 수행한다. WebFlux는 요청이 들어 오면 Boss Event Loop Group(소수의 Thread로 구성이 되어 있다. 이해를 위해 하나라고 생각해도 좋을거 같다.)에서 이 요청을 수락하고 Chanel을 통해 Worker Event Loop Group(기본적으로 컴퓨터 코어수를 기반으로 Thread가 생성이 된다.)의 특정 Woker Thread에 이 요청을 할당한다. 특정 요청을 할당 받는 Worker Thread는 NonBlocking I/O 모델을 기반으로 Reactive Stream인 Mono, Flux의 구독을 진행하고 구독이 된 Reactive Stream(Mono, Flux)은 다양한 Scheduler를 통해 Async, NonBlocking 모델을 기반으로 실질적인 작업을 처리한다. WebFlux는 MVC와 다르게 각각의 요청에 대해서 Thread를 할당하지 않고 Event Loop를 기반으로 소수의 Thread로 다수의 요청들에 대해 비동기적으로 작업을 처리한다. 그 결과 WebFlux는 적은 자원(소소의 Thread)을 활용하여 고성능, 고효율의 성능을 발휘할수 있다.
  • 요약
    - WebFlux는 효율적인 자원 활용을 위해 Event Loop 기반으로 동작
    • 요청을 수락하는 Boss Event Loop Group은 소수의 Thread로 구성
    • NonBlocking I/O 모델을 기반으로 Worker Thread는 Reactive Stream(Mono, Flux)를 구독
    • Worker Thread는 기본적으로 CPU 코어수를 기반으로 생성
    • 다수의 요청에 대해 각 Thread가 할당되는 방식이 아닌 Event Loop를 기반으로 저자원 고성능, 고효율 발휘

Spring Web MVC vs Spring WebFlux

Spring Web MVC

  • 명령형 프로그래밍
    - 행위를 중심으로 프로그램 개발을 진행하는 방식이다.
    • 기본적으로 동기적으로 프로그래밍 개발한다.
  • 메모리 효율성
    - 하나의 Thread에는 독자적인 메모리 영역이 할당되는데 Thread Pool을 운용하는 Tomcat은 기본적으로 다수의 Thread를 생성해 놓으며, 필요에 의해 다수의 Thread를 추가로 생성한다. 즉, 생성된 Thread의 갯수 만큼 독자적인 메모리 영역이 할당될것이고 이는 메모리 효율성을 저하 시킬 여지가 있다.
  • CPU Bounds: 시스템의 성능이 CPU의 처리 능력에 의해 제한되는 경우
    - Spring Web MVC는 하나의 요청에 대해 하나의 Thread를 할당한다. 즉, 시스템의 CPU 코어수를 초과하는 다수의 Thread가 생설될 수 있음을 암시한다. 이러한 상황은 CPU에 큰 부하를 주는 다량의 Context Switching을 발생 시켜 CPU Bounds를 발생 시킬 여지가 있다.
  • I/O Bounds: 시스템의 성능이 I/O 작업에 의해 제한되는 경우
    - Spring Web MVC는 Blocking I/O 모델을 기반으로 작업을 수행한다. 즉, 하나의 요청에 대해 할당된 Thread는 현재 수행 중인 작업 이외의 작업을 수행 할 수 없다. 이렇게 특정 작업에 묶여 있는 다수의 Thread가 존재하는 것은 대기상태인 Thread가 다수 존재할 수 있음을 암시하고 이것은 I/O Bounds를 발생 시킬 여지가 있다.

Spring WebFlux

  • 반응형 프로그래밍
    - 데이터를 중심으로 프로그램 개발을 진행하는 방식이다.
    • 기본적으로 Stream을 통해 비동기적으로 개발을 진행한다.
    • Reactive Stream의 형태는 선언형 프로그램의 형태를 띈다.
  • 메모리 효율성
    소수의 Thread를 운용하는 철학을 가진 WebFlux는 상대적으로 많은 수의 Thread를 생성하지 않는다. 이는 상대적으로 Thread 자체적인 메모리 할당이 많지 않다는 뜻이므로 효율적으로 메모리를 사용한다고 할 수 있겠다.
  • CPU Bounds: 시스템의 성능이 CPU의 처리 능력에 의해 제한되는 경우
    소수의 Thread를 운용하는 철학을 기반으로 다수의 Thread를 생성하지 않아 다수의 Context Switching이 발생할 여지가 많이 줄어 CPU Bounds가 상대적으로 덜하다고 할 수 있다.
  • I/O Bounds: 시스템의 성능이 I/O 작업에 의해 제한되는 경우
    하나의 요청에 대해서 NonBlocing I/O 모델을 기반으로 작업을 수행한다. 하나의 요청에 대해 작업을 처리하는 Thread는 상황에 맞게 효율적으로 다수의 작업을 진행할 수 있음을 암시한다. 즉, 병목현상이 발생하지 않는다는 말이다. 이는 I/O Bounds가 상대적으로 덜하다고 할 수 있다.
    주의점
    위의 설명과 차이점을 비교해보면 WebFlux를 사용하는 것이 무조건 더 나은 성능을 발휘한다고 생각할 수 있겠다. 그렇다면 진짜 무조건 WebFlux가 MVC보다 무조건 더 나은 성능을 발휘할까? 결론은 그렇지 않다. 적은 수의 Thread를 운용하는 WebFlux의 가장 큰 약점은 병목현상이다. 개발자의 실수 또는 환경으로 인해 병목현상이 발생하면 특정 Thread는 그 작업에 묶이게 되고 적은 수의 Thread를 운용하는 WebFlux의 성능은 현저히 떨어진다. 실제 병목 현상이 발생하면 Spring WebFlux가 Spring Web MVC 보다 더 낮은 성능 퍼포먼스를 보인다. 그러니 WebFlux를 사용할때는 항상 병목현상을 주의하고 병목현상에 대한 테스트를 반드시 진행해야 한다.

MSA 프로젝트 MVC Module, WebFlux Module 경계 설정

우리는 위의 설명을 통해 Spring Web MVC와 Spring WebFlux의 차이를 이해하고 서로다른 두 모델을 이해했다. 그렇다면 실제로 코드를 작성하는 개발자 입장에서는 어떠한 차이점이 있을까? 결론은 Spring WebFlux는 Reactive Programming을 기반으로 Reactive Stream(Mono, Flux)을 구현해야 한다. 그 결과 아래의 4가지 기술을 구현하는 방식이 달라진다. 이를 근거로 하여 mvc module과 webflux module을 분리했다.

  • Exception Handler
  • Resolver
  • Filter
  • Client

질문 👨🏻‍💻) 리액티브 스트림이 뭔가요
간단히 설명해서 리액티브 스트림은 데이터를 중심으로 행위의 흐름을 나타내는 집합이라고 할 수 있다. 이러한 여러 스트림의 상화작용을 통해 우리는 논리를 완성하고 기능을 구현한다. 그리고 이것을 복합적으로 Reactive Progamming이라고 할 수 있겠다.

마무리

필자는 원래 iOS, Android를 개발한 Mobile 개발자였다. 지금도 회사에서 백엔드를 메인으로 개발을 진행하고 있지만 iOS, Android 개발도 필요하면 진행하고 있다. (ex) WebView Mapping, fcm 기능, DynamicLink 기능) iOS, Android 개발을 할때 필자는 RxSwift, RxKotlin을 사용한다. 이 Rx 시리즈들은 Reactor과 같이 Reactive Streams의 구현체다. 필자는 모바일 개발을 할때 Reactive Progreamming을 기반으로 많은 프로그래밍을 했고 많은 학습을 진행했는데 WebFlux를 다시 봤을때 너무 반가웠다. 다시 고향으로 돌아온 느낌이였다.

그런데 갑자기 본질적인 질문이 머릿속에 떠올랏다. 왜 Spring 사단에서는 Reactive Programming이 대중화 되어 있지 않을까? (실제 Mobile 사단에서는 Reactive Programming은 기본이다.) 러닝 커브 때문인가? 아니면 Reactive Programming을 사용하는 것은 오버스팩이고 사용할 필요가 없어서 사용하지 않는것인가? 모바일도 그렇고 백엔드도 그렇고 다른 Framework이나 기술들을 살펴보면 확실히 비동기 프로그래밍의 중요성, 이를 기반으로 한 Publish, Subject 개념은 대두되고 있다. (이는 복잡한 세상의 비지니스를 해결하기 위한 노력과 하드웨어의 발전사(더 많은 CPU 코어수)와 관련이 있을것이다.) 즉, 이 두 개념을 기반으로 한 Reactive Progamming은 시대의 발전과 함께 가고 있다. 그렇다면 왜 적극적으로 이 시대의 흐름을 반영하지 않는것인가.

백엔드의 시스템은 방대하다. 하나의 시스템이 몇년 길게는 10년을 넘길 수 있다. 이렇게 긴 수명으로 유지된 시스템의 본질과 근간을 바꾸는 것은 쉽지 않을것이다. 효율성이 떨어진다고 해도 올바르게 동작하는 시스템을 개혁해서 위험 부담을 지는것은 옳지 않은 판단이다. 그래서 변하기 쉽지 않구나, 트랜드를 바로 반영하기 쉽지 않구나 생각했다. 또한 이러한 흐름을 기반으로 개발한 다수의 개발자들은 이 기조를 기반으로한 기술에 특화되어 있을것이다. 시스템을 개발하는 대상의 Pool또한 여기에 맞춰질 수 밖에 없다. 그러니 시대의 흐름이 아무리 변하고 더 좋은 기술이 나와도 신중해질수 밖에 없다.

하지만 필자는 미래에 모두 Reactive Programming으로 개발할 것이라고 생각한다. 개발자에게 여러 중요한 덕목이 있지만 그중 가장 중요한 덕목은 항상 자원을 낭비하지 말고 효율적으로 사용하자는 것이다. WebFlux도 그렇고 모든 Reactive Programming이 자원을 효율적으로 사용하자는 철학을 근거로 한다. 또한 기술의 발전이 이를 중심으로 발전하고 있다. 지금 현재는 복잡하고 어려워 오버스팩이라는 소리를 듣지만, 모두가 익숙해지는 날이 오고 위의 아름 다운 철학과 시대의 흐름이 잘 녹여진다면 기본이 되고 표준이 될것이라고 생각한다.

긴글 읽어주셔서 감사합니디.☺️

profile
질문의 질이 답의 질을 결정한다.

0개의 댓글