[이렇게 개발했습니다] Simple & Easy Notification Service #3 : MongoDB Client 비교 (with WebFlux)

NAVER CLOUD PLATFORM
NAVER CLOUD PLATFORM
11 min readDec 29, 2021

--

안녕하세요. 네이버 클라우드 플랫폼입니다.

네이버클라우드는 네이버 클라우드 플랫폼의 Application Service인 Simple & Easy Notification Service(이하 SENS)를 준비하면서 네 가지 목표를 설정했습니다.​

◼ 기술적으로 많은 시도를 해보는 것

◼ 장애 없는 서비스를 만드는 것

◼ 효율적인 자원 활용으로 많은 요청이 들어와도 가용할 수 있는 프로젝트를 만드는 것

◼ 고성능의 프로젝트를 만드는 것

이 글에서는 프로젝트를 구성할 때 목표한 것을 달성하기 위해, 어떤 것들을 고려하고 선택했는지에 대해 설명을 해보려고 합니다.

들어가기에 앞서

SENS (Simple & Easy Notification Service) 란?

Simple & Easy Notification Service (SENS) — 서비스 바로가기

SENS는 SMS (문자메시지), 카카오톡 비즈메시지, 모바일 Push와 같은 메시지 전송 기능을 제공하는 서비스입니다. 간편한 메시지 발송을 지원하기 위해 HTTP API를 제공하며, 메시지 요청/결과 조회와 같은 부가 기능들도 제공하고 있습니다.

​👉[Ncloud 사용 가이드] Simple & Easy Notification Service 기능 살펴보기

SENS에서의 고성능을 추구하는 이유

SENS는 메시지 API의 인터페이스 역할을 합니다. 이력성 데이터를 저장하거나, 받은 메시지 요청을 Kafka에 넣는 등의 비교적 가벼운 처리를 합니다.

저희는 우선 아래 항목에 대해 고민했습니다.

​◼ 최대 10만 TPS의 요청

◼ 메시지 가공이나 발송에 대한 효율성 증대

SENS는 메시지 요청을 받는 인터페이스이기 때문에, 순간적인 요청이 많이 들어와도 메시지를 발송할 때까지 지연이나 장애 없이 동작해야 합니다. 또한 메시지를 가공하거나 통신사에 발송 요청하는 것까지 최대한 효율적으로 동작하는 방법에 대해 고민했습니다.

그 결과 blocking 방식보다는 적은 리소스로 동시성을 다루는 non blocking 방식을 고민하게 되었고, 일반적으로 사용하는 blocking 방식의 Spring MVC보다 event driven, non blocking 한 처리가 가능한 WebFlux를 사용하고자 했습니다.

WebFlux란?

WebFlux란 Spring 5에서 새롭게 추가된 모듈이며, 클라이언트와 서버에서 Reactive 한 개발을 할 수 있게 도와줍니다.

출처 : 스프링 공식 문서 (링크)

​Spring MVC는 Servlet 기반으로 만들어졌고, sync + blocking 방식으로 동작합니다. 결국 하나의 처리를 할 때 Response를 기다리며 thread를 지연시키는 부분이 있었습니다.

물론 Spring MVC 모델을 사용해도 Multi Thread를 사용하면 block 되지 않고 사용할 수 있지만, Thread 간의 Context Switch 발생 시 비용이 생겨 비효율적입니다.​

이에 반해 Spring WebFlux는 요청이나 응답을 처리하는 I/O를 async + non blocking으로 방식으로 수행하여 callback 을 통하여 작업이 진행되기 때문에 Blocking이나 Polling 방식을 사용하지 않습니다. 이에 따라 자원을 더 효율적으로 사용할 수 있습니다.

자원을 효율적으로 사용한다는 점을 그림과 함께 조금 더 자세하게 설명드리겠습니다.

이미지 출처 : Concurrency in Spring WebFlux

​Spring MVC 방식일 때, 사용자들로부터 많은 수의 요청을 받게 된다면 하나의 요청마다 하나의 Thread를 사용하게 됩니다. 결국 사용자의 요청이 몰려서 Thread pool이 수용할 수 있는 범위를 넘어버리게 되면, 작업 처리까지 Queue에 요청이 쌓이게 됩니다. 이 경우 요청에 대한 응답을 받기까지 오랜 시간이 걸리게 됩니다.

이미지 출처 : Concurrency in Spring WebFlux

​이에 반해 WebFlux는 Event-Driven으로 동작하기 때문에 다수 요청을 하나의 Thread로도 처리할 수 있습니다. 즉, 하나의 요청 당 한 개의 Thread를 사용하는 Spring MVC와는 다릅니다.

​Thread pool의 수용 범위를 넘어 Queue에 요청이 쌓이는 문제점을 해결할 수 있습니다. WebFlux를 SENS에서 사용한다면 적은 자원으로도 많은 트래픽을 감당할 수 있고, SENS에서 목표하는 최대 10만 TPS의 요청이 들어왔을 때의 경우에도 효율적으로 처리할 수 있을 것입니다.

​📗 참고 자료 : 스프링 공식 문서 바로가기

위와 같은 검토 끝에 고성능의 프로젝트를 만들기 위해 WebFlux를 프레임워크로 사용하게 되었습니다.

이제 프레임워크는 정해졌으니, 메시지 발송 이력 저장소에 대해 고민하게 되었습니다.

SENS에서의 이력 관리

SENS에서는 사용자가 보냈던 메시지 발송 이력을 조회하는 기능을 제공합니다.

SENS에서 메시지를 발송한 후 이력 저장소에 데이터를 저장하기까지의 흐름은 아래 그림과 같습니다.

​메시지 발송이 완료되면 이력 저장소에 메시지에 대한 이력이 저장이 되는데, 이력 데이터는 발신번호, 수신번호, 발송 일시, 발송 상태 등을 포함합니다. SENS의 이력 데이터 조회 기능 구현을 위해선 이력 데이터를 저장하는 저장소가 필요하고, 메시지 발송 이력 데이터는 최소 30일 ~ 최대 1년 치를 저장해야 합니다.

단순히 이력을 조회할 때 사용되는 데이터이기 때문에, 수정과 transaction 처리가 거의 필요하지 않다고 예상하였습니다. 그리고 저장된 데이터는 발송된 데이터를 한 번에 저장하기 때문에, 다른 데이터와의 relation도 없습니다.

또한 저장소에 write 되는 TPS는 최대 1만 TPS를 기대하고 있으며, IDC 이중화 상황도 고려하여 상대적으로 쉽게 확장할 수 있는 저장소를 사용해야 합니다.

SENS에서의 이력 저장소

◼ 앞서 언급한 내용을 종합하면 SENS의 이력 저장소는 아래 조건을 모두 만족해야 합니다.

◼ 최소 30일~1년 치 데이터를 모두 저장할 수 있어야 함

◼ update와 transaction은 거의 없을 것으로 예상

◼ 다른 데이터와의 relation이 없음

◼ 최대 1만 TPS의 wirte를 커버할 수 있는 성능

◼ 확장성(IDC 이중화)

​위의 조건들을 고려하여 이력 저장소로 MongoDB를 검토했습니다.

MongoDB는 관계형 모델(RDB)이 아닌 NoSQL 종류 중 하나입니다. JSON과 닮은 형태(BSON)의 Document로 데이터를 저장하며, 스키마가 규정되어 있지 않아도 데이터를 저장할 수 있습니다.

또한 데이터의 크기가 크고, 데이터마다 스키마가 다른 경우에도 관계형 저장소에 저장하는 것처럼 전처리 과정 없이 데이터를 한 번에 저장할 수 있습니다. 그만큼 MongoDB는 유동적이고 다양한 형태의 데이터를 처리할 수 있습니다. 비록 MongoDB는 Join을 지원하지 않지만, SENS에서는 다른 데이터와의 relation이 없으니 걱정할 부분은 아니었습니다.

또한 MongoDB는 섀딩(sharding)을 통한 수평적 확장이 가능하고 데이터 볼륨이나 처리량이 증가해도 중지 없이 쉽게 확장이 가능하고, 데이터 Shard를 위한 아키텍처 설계가 이미 되어있어 데이터 분산을 쉽게 할 수 있습니다.

​📗 참고 자료 : MongoDB 공식 문서 바로가기

이런 이유로 SENS에서 이력 저장소로 MongoDB를 선택하게 되었고, WebFlux + MongoDB의 형태로 프로젝트를 진행하게 되었습니다.

MongoDB Client

WebFlux + MongoDB의 조합으로 개발을 진행하기에 앞서, MongoDB Client에 대한 고민을 하게 되었습니다.

스프링 부트에서는 MongoDB에 접근할 때 일반적으로 많이 사용하는 blocking MongoDB Client와 non blocking I/O를 구현하는 MongoDB Client를 지원하고 있습니다.

일반 MongoDB Client와 Reactive를 지원하는 non blocking MongoDB Client의 dependency

WebFlux를 사용하지만 반드시 MongoDB Client도 Reactive를 지원하는 Client를 사용해야 되는지 의문이 생겼습니다.

관련 자료를 찾아보니 WebFlux를 사용하는 만큼 성능을 최대로 내기 위해서는 모든 I/O 작업이 non blocking 기반으로 동작해야 한다는 것을 알게 되었습니다. 만약 I/O 작업이 block 되는 곳이 있다면 event loop meltdown 현상으로 다른 작업까지 영향을 받아 느려질 수 있기 때문에, SENS가 목표하는 고성능을 이루기 위해서는 모든 작업이 non blocking 하게 동작 되어야 합니다.

이상은 리서치를 통해 알게 된 사실이었고, Block MongoDB Client, Reactive MongoDB Client 사용 시 실제 성능 차이가 궁금하여 WebFlux를 기반으로 서로 다른 MongoDB Client를 사용해 성능 테스트를 진행했습니다.

성능 비교

테스트 애플리케이션

WebFlux + Reactive MongoDB Client

스프링 부트(2.5.4.RELEASE) + WebFlux + spring-boot-starter-data-mongodb-reactive

◼ WebFlux + Block MongoDb Client

스프링 부트(2.5.4.RELEASE) + WebFlux + spring-boot-starter-data-mongodb

테스트 서버 환경

CentOS 7.4 64Bit, 2vCPU, 4GB Mem, 100GB Disk

성능 테스트 : nGrinder, Agent 1

저장 성능 측정

조회 성능 측정

◼ findById

MongoDB에 저장하는 요청에 대한 성능 테스트 결과, Reactive MongoDB Client를 사용하는 게 일반 MongoDB Client(Block MongoDB)를 사용하는 것보다 2~3배 정도의 성능 차이가 나는 것으로 확인 되었습니다.

조회 성능은 findById를 사용하여 테스트를 진행했고, 저장 성능 테스트와 동일하게 Reactive MongoDB Client를 사용하는 것이 일반 MongoDB Client를 사용하는 것보다 2배 정도 더 좋은 성능을 보였습니다.

이 테스트를 통해서 WebFlux + MongoDb Client , WebFlux + Reactive MongoDb Client에 대한 비교를 할 수 있었고, WebFlux 활용 시에는 DB Client도 Reactive Client를 사용하는 것이 더 좋은 효율을 내는 것을 확인할 수 있었습니다.

다만 저장/조회 성능 테스트에서 목표하는 최대 TPS를 테스트하지 못했고, 권한 체크를 하거나, Kafka에 메시지 생산을 하는 등의 중간의 처리 과정을 생략하고 테스트를 진행한 점은 아쉬웠습니다.

글을 마치며

지금까지 SENS에서 WebFlux를 도입을 위한 과정과, 이력 데이터 저장을 위한 DB 선택, ,WebFlux로 프로젝트 구성을 위한 DB Client 선택 과정에서의 고민을 공유하였습니다.

테스트를 통해, 프로젝트의 프레임워크로 WebFlux를 사용한다면 DB Client도 non blocking으로 구성해야 더 고성능을 낼 수 있다는 테스트 결과를 확인할 수 있었습니다.

감사합니다.

* 본 기술 포스팅은 NAVER Cloud의 PaaS Dev1 한민지 님의 글입니다. ​

--

--

NAVER CLOUD PLATFORM
NAVER CLOUD PLATFORM

We provide cloud-based information technology services for industry leaders from startups to enterprises.