아우토크립트 EVI Studio에서는 최근 새로운 프로젝트를 진행하며 기존 개발 방식을 재검토하게 되었습니다. 사용자 피드백과 요구를 반영하기 위해 새로운 개발 프레임워크를 도입하게 되었는데요. 이번 블로그 포스트에서는 이러한 변화의 이유, 장점, 그리고 과정에서 겪었던 도전들을 공유해 주셨습니다!
1. WebFlux 도입 배경
기존에 저희는 Typescript와 NestJS를 사용하여 애플리케이션을 개발해왔으며, 이번에 새로운 프로젝트를 진행하면서부터는 Java와 Spring Boot로의 전환을 결정하게 되었습니다.
여기서 저희는 한 가지 고려해야할 사항이 있었습니다. 바로 기존에 MSA 구조로 되어 있던 Nodejs 서버들을 Spring MVC를 통해 구성할 시 다량의 네트워크 I/O 로 인해 사용자가 많아질 경우 발생할 병목 현상에 대한 해결책을 찾아야 했습니다.
고민 끝에 저희는 논블로킹 I/O 의 필요성을 느끼게 되었고, 이를 가능하게 해줄 Spring WebFlux 를 도입하게 되었습니다.
2. Spring MVC vs Spring WebFlux
간단한 성능 비교를 위해 동일한 기능을 하는 두 서버를 각각 MVC와 Webflux를 사용하여 구성하고 300명의 가상 유저가 요청을 보내는 방식으로 성능 테스트를 진행했습니다.
0.5초 후 응답을 주는 별도의 서버에 요청을 보내고 받는 기능을 구현했기 때문에 단일 요청의 이상적인 소요 시간의 기준은 500ms 보고 성능을 확인해보았습니다.
그 결과 TPS(초당 요청 처리 개수)의 경우 MVC는 최대 120, WebFlux는 최대 600 이상으로 나타나며, WebFlux가 MVC에 비해 높은 성능을 나타내는걸 확인할 수 있었습니다.
MTT(단일 요청 소요 시간)의 경우, MVC는 초기에 약 600ms를 유지했으나 요청이 누적될수록 최대 2.4초까지 증가한 반면, WebFlux는 부하가 증가해도 MTT가 안정적으로 유지되었습니다. 즉 WebFlux는 처리량이 많아져도 지연 시간이 안정적으로 유지된다는 것을 의미하며, 이를 통해 기존 MVC에서 사용자가 늘어남에 따라 발생했던 병목 현상을 해결한 것을 확인할 수 있었습니다.
3. WebFlux 핵심 개념
리액티브 프로그래밍
리액티브 프로그래밍은 리액티브 시스템을 위한 프로그래밍 모델로, 비동기 및 논블로킹 I/O를 통해 클라이언트 요청에 신속히 대응하여 시스템의 응답성을 높입니다. 리액티브 시스템은 짧은 지연시간으로 빠르게 반응하는 것이 핵심이며, 이에 대한 자세한 내용은 [리액티브 선언문]에서 확인하실 수 있습니다.
리액티브 프로그래밍은 비동기 통신을 통해 블로킹 없이 데이터 스트림을 처리하는 방식으로, 서버 자원을 효율적으로 사용하여 높은 동시성을 구현할 수 있습니다. 단, 이를 위해 표준화된 프로토콜이 필요한데, 리액티브 스트림즈가 이를 위한 표준 사양으로 제시되었습니다.
리액티브 스트림즈는 데이터를 논블로킹과 비동기 방식으로 처리하기 위한 표준을 정의하며, Spring WebFlux는 이 표준을 구현한 Reactor 라이브러리의 Mono와 Flux 타입을 활용하여 비동기 작업을 효율적으로 관리할 수 있게 합니다. Reactor에 대한 자세한 내용은 공식 문서에서 확인할 수 있습니다.
비동기와 논블로킹 I/O
위에서 리액티브 프로그래밍은 비동기 및 논블로킹 I/O를 위한 프로그래밍 모델이라고 설명했습니다. 여기서 비동기와 논블로킹은 종종 같이 언급 되지만, 실제로는 다른 개념입니다.
비동기는 작업을 요청한 후, 그 작업이 끝나는 것을 기다리지 않고 다음 작업을 바로 진행할 수 있는 방식입니다. 이는 작업을 위임한 후 즉시 제어권을 반환받아 다른 작업을 수행할 수 있게 하여, 전체적인 시스템 효율성을 높이는 방식입니다.
WebFlux는 전통적인 Spring MVC의 멀티 스레드 모델과 달리, Node.js와 유사한 이벤트 루프 모델을 사용하여 클라이언트의 요청을 비동기적으로 처리합니다. WebFlux는 Node.js의 장점인 이벤트 루프 모델에 Java의 멀티 스레드 기능을 결합하여, 이를 통해 I/O 작업의 비동기 처리뿐만 아니라, CPU 집약적인 작업도 별도의 스레드 풀에서 효율적으로 처리할 수 있어 보다 높은 처리량을 제공합니다.
WebFlux의 비동기 동작에 대한 자세한 설명은 다음 링크를 참고하시면 좋을 것 같습니다.
다음으로 논블로킹 I/O 입니다. 우리의 프로세스가 원격 클라이언트와 데이터를 주고받기 위해서는 OS 커널과의 통신이 필요하며, 이는 주로 커널 내 소켓과 소켓 버퍼 등을 활용하여 이루어집니다.
이렇게 OS 커널내 소켓 버퍼로부터 프로세스가 데이터를 read/write 하는 작업을 네트워크 I/O라 부르며, 이때 커널에 read 또는 write 하기 위한 시스템 호출(system call)은 블로킹 I/O와 논블로킹 I/O 모델로 나뉘게 됩니다.
블로킹 I/O에서는 프로세스가 read 또는 write 작업을 요청하면, 커널이 데이터를 준비해 네트워크 전송을 완료할 때까지 해당 스레드는 대기 상태에 놓이게 됩니다. 즉, 프로세스가 네트워크 I/O 작업을 커널에 요청하고, 커널이 작업을 완료할 때까지 해당 스레드는 다른 작업을 수행하지 못하고 멈춰 있는 방식입니다.
반면, 논블로킹 I/O에서는 I/O 작업을 요청한 후 곧바로 제어권을 반환받아 다른 작업을 수행할 수 있습니다. 이 경우 커널이 데이터를 준비하지 않았더라도 시스템 호출은 즉시 반환되며, 데이터가 준비되지 않았다면 에러 코드가 반환됩니다. 이후 데이터가 준비되면 이벤트 기반의 메커니즘을 사용하거나, 주기적으로 상태를 확인하여 데이터를 처리하는 방식으로 이어지며, 이를 통해 프로세스는 네트워크 I/O 작업에 묶이지 않고 효율적으로 다른 작업을 병행할 수 있게 됩니다.
논블로킹 I/O 대한 자세한 설명은 다음 [링크]를 참고하시면 좋을 것 같습니다.
4. WebFlux 도입 과정에서의 어려움
새로운 프로젝트를 진행하면서 WebFlux를 도입하는 과정에서 저희는 여러가지 시행착오를 겪어야 했습니다. 이는 주로 WebFlux의 높은 러닝 커브와 관련된 문제들이었으며, 그 중의 하나는 너무 많은 Reactor의 연산자(Operator)에서 비롯된 문제였습니다.
Reactor에서는 Mono와 Flux 타입을 사용해 리액티브 스트림을 구현하고 비동기 작업을 쉽게 관리할 수 있도록 하기 위해 여러 연산자(Operator)를 제공하고 있으며, 이는 공식 문서를 통해서도 확인해볼 수 있습니다.
다만, 이 연산자의 개수만 해도 대략 500개가 넘다보니 연산자들을 하나하나 완벽하게 이해하고 적재적소에 사용하는 것이 쉽지 않았습니다. 이 때문에 Reactor에서 제공하는 연산자 중 만능이라고 여겨지는 flatMap과 같은 연산자를 과도하게 사용하여 마치 자바스크립트의 콜백 지옥처럼 flatMap 지옥으로 인한 코드 가독성의 문제가 생기기도 했습니다.
public MonoflatMap_hell() { return reactiveOp1(1) .flatMap(value2 -> { return reactiveOp2(value2) .flatMap(value3 -> { return reactiveOp3(value3) .flatMap(value4 -> { return reactiveOp4(value4) .flatMap(value5 -> { return reactiveOp5(value5) .flatMap(value6 -> { return reactiveOp6(value6); }); }); }); }); }); }
물론 실제 개발을 진행해 나가면서 자주 유용하게 사용할 수 있는 연산자들은 거의 정해져 있었고, 이러한 연산자들이 조금씩 눈에 띄기 시작하면서 해당 연산자들을 우선적으로 학습함으로써 문제를 어느정도는 해결할 수 있었습니다. 하지만 여전히 Reactor의 방대한 연산자들을 완벽하게 이해하고 활용하는 것은 쉽지 않은 과제로 남아있습니다.
5. 마치며
아직 저희의 Spring Webflux 도입이 완벽하지는 않다고 생각합니다. 위에서 언급한 높은 러닝 커브와 관련된 문제와 더불어 앞으로도 해결해야할 과제들이 많고 그 과정이 절대 쉽지 않을 것이라 예상하고 있습니다.
그러나 WebFlux 도입 과정에서의 이러한 어려움은 저 뿐만이 아닌 팀 차원의 학습 기회가 될 것이라고 생각합니다. 그리고 이러한 기회를 통해 코드 효율성과 시스템 성능 모두를 극대화하는 개발 문화가 자리 잡을 수 있도록 지속적인 노력을 기울인다면, WebFlux의 도입은 단순한 기술 변화가 아니라 저희 팀의 개발 패러다임을 변화시키는 중요한 전환점이 될 것이라고 생각합니다.
이상으로 EVI Studio의 WebFlux 도입기를 마치겠습니다!
EVI Studio Dev그룹 EVD팀 백엔드 개발자 김대권
참고: