[이렇게 개발했습니다] Simple & Easy Notification Service #1 : 대규모 메시지 요청 상태를 관리하기 위한 데이터 저장소 사용기
안녕하세요, 네이버 클라우드 플랫폼입니다.
오늘은 Simple & Easy Notification Service(이하 SENS)의 SMS 메시지 발송 모듈 개발 관련 경험을 공유하겠습니다.
이 포스트에서는 SENS에서 메시지 요청 상태를 관리하기 위한 저장소(이하 상태 DB)를 사용하게 된 이유와 선택 과정에 대한 경험을 공유해 보려고 합니다.
최대 허용 가능한 메시지 요청량은 100K TPS로 가정했습니다.
들어가기에 앞서
SENS (Simple & Easy Notification Service) 란?
Simple & Easy Notification Service (SENS) — 서비스 바로가기
SENS는 SMS (문자메시지), 카카오톡 비즈메시지, 모바일 Push와 같은 메시지 전송 기능을 제공하는 서비스입니다. 간편한 메시지 발송을 지원하기 위해 HTTP API를 제공하며, 메시지 요청/결과 조회와 같은 부가 기능들도 제공하고 있습니다.
SENS의 목표는 고객으로부터 받은 메시지 전송 요청을 빠르고 안전하게 처리하는 것이며, 정확하고 안정적인 메시지 발송을 위해 노력하고 있습니다.
SENS에서 ‘상태 DB’란?
SENS는 고객이 요청한 메시지들에 대한 이력을 일정 기간 보관해야 합니다.
SENS 서비스 특성상 대규모 요청에 대한 고려는 필수적이기 때문에, 부하 상황에서도 고객의 요청을 빠르게 처리하기 위한 방법 중 하나로 요청 상태만을 관리하는 저장소를 별도로 사용하고 있습니다.
주요 목적은 요청받은 데이터를 빠르게 처리하기 위함이며, 이를 ‘상태 DB’라고 부르겠습니다.
Q. SENS에서 상태 DB가 필요한 이유는?
<그림 1>에서 알 수 있듯이, 사용자는 메시지 발송 요청에 대한 결과를 응답받은 2번 시점부터 결과 조회를 할 수 있어야 합니다. (그림 1> 6번 과정) 적은 수의 메시지라면 빠른 처리와 응답이 가능하겠지만, 메시지 규모가 클 경우 여러 가지 고민이 필요합니다.
- 메시지 발송을 최종 처리하는 통신사의 데이터 처리량은 상대적으로 높지 않고 가변적입니다.
- SENS는 통신사의 데이터 처리량과 무관하게 안정적으로 메시지 발송 요청을 수용해야 합니다.
위와 같은 이유로 메시지 요청을 통신사로 전달하는 과정은 비동기로 처리하고 있습니다.
그럼, 대규모 요청 상황을 가정해 보겠습니다.
초당 10만 건 정도의 대규모의 요청이 들어오고, 통신사가 수용 가능한 최대 메시지 발송량은 초당 1,000건 정도인 극단적 상황을 설정해 보겠습니다.
1️⃣ SENS가 받은 요청에 대한 응답이 사용자에게 나가고 (그림 2> 2번 과정)
2️⃣ 이후 SENS에서 받은 요청을 통신사로 전달하는데 일정 시간 지연이 발생할 것이고 (그림 2> 3번 과정)
3️⃣ 사용자는 이미 요청에 대한 응답을 받았기 때문에 조회를 시도할 수 있습니다. (그림 2> 5번 과정)
SENS의 입장에서는 해당 요청 정보를 알 수 없는 상황에 놓이게 됩니다.
Q. 통신사 요청에 응답 전, 메시지 발송 요청 정보를 DB에 저장해두면 되지 않을까?
A. 맞습니다. 위와 같은 이유로 네이버 클라우드 플랫폼 SENS는 ‘상태 DB’를 사용하고 있습니다.
다만 선택하는 DB, 내부 프로세스에 따라서 고객이 불편을 느낄 수 있는 포인트들이 존재합니다.
예를 들어, 메시지 발송 요청 상태는 사용자에게 응답하기 전 저장이 필요하므로 (그림3> 2번 과정), 빠른 I/O 속도를 필수로 보장해야 합니다. 즉, 고객이 사용하는 DB의 특징에 따라 해당 메시지가 순차적으로 저장될 때까지 응답을 대기할 수도 있습니다.
👉 이러한 이유로 SENS의 메시지 상태 관리 프로세스에 적합한 DB가 필요하다고 결론 내렸습니다.
Q. 그래서 어떤 DB를 써야 할까?
데이터베이스 선택 시 가장 먼저 고민되는 주제, 관계형 데이터데이스를 사용할지, No SQL을 사용할지에 대한 고민입니다. 간단하게 각각의 장단점을 비교해 보겠습니다.
* RDBMS와 NoSQL 모두 보편적 특징을 작성하였으며, 특정 데이터베이스에는 해당하지 않는 내용이 있을 수 있습니다.
1) RDBMS
✅ 장점
- 정해진 스키마에 따라 데이터를 저장하므로 명확한 데이터 구조를 보장할 수 있다. (테이블 간 관계도 정의 가능)
- SQL이라는 구조화된 질의를 통해 데이터를 편리하게 다룰 수 있다.
✅ 단점
- 테이블 간 관계를 맺고 있어 시스템이 커질 경우 JOIN 문이 많은 복잡한 쿼리가 만들어질 수 있다.
- 스키마로 인해 데이터가 유연하지 못하다.
- 나중에 스키마가 변경될 경우 번거롭다. (반드시 스키마에 맞춰야 한다)
2) NoSQL
✅ 장점
- RDBMS 대비 빠른 I/O 성능
- 스키마가 없기 때문에 유연하며 자유로운 데이터 구조를 가질 수 있다. 언제든 저장된 데이터를 조정할 수 있다.
- 데이터 분산이 용이하며 성능 향상을 위한 Scale-out이 편리하다.
✅ 단점
- 스키마가 존재하지 않기에 명확한 데이터 구조를 보장하지 않으며 데이터 구조 결정이 어려울 수 있다.
- Key 값에 대한 입출력만 지원한다.(Get/Put, RDBMS에 비해 다양한 쿼리 기능 부족)
정리
👉결론부터 말씀드리면 저희는 NoSQL 사용을 결정했습니다.
앞서 말씀드렸듯이, SENS 입장에서 빠른 I/O는 중요한 포인트이기 때문입니다.
- 구조적으로 메시지의 형태는 언제든지 바뀔 수 있어 정형화된 데이터를 지원하는 RDBMS보다는 유연한 NoSQL이 적합하다고 생각했습니다.
- 비록 RDBMS에 비해 다양한 쿼리 기능이 부족한 점은 NoSQL의 일반적인 단점이라 설명드렸지만, SENS에는 크게 단점으로 작용하지 않는 것으로 판단했습니다.
- 또, 요청량이 많아질 경우 Scale out이 비교적 쉽다는 것 또한 장점으로 생각되었기 때문에 NoSQL을 사용하기로 결론 내렸습니다.
SENS에서 필요한 기능 지원 여부
SENS가 필요로 하는 기능인지 여부와 무관하게, 지원하는 기능은 많으면 많을수록 좋습니다.
다만, 오늘의 목표는 ‘SENS에 딱 맞는 옷’을 찾기 위함이므로 SENS에 필요한 기능이 무엇인지, 필요하지 않은 기능은 무엇인지 확인하고 지원 여부를 확인해 보았습니다.
< SENS에서 기대하는 상태 DB의 성격은 다음과 같습니다.>
1. 자체적으로 부여한 Key 또는 ID 기반의 빠른 I/O 작업이 가능해야 한다.
2. 자체 목표인 10만 TPS 수준의 요청량을 수용할 수 있어야 한다.
3. 요청량이 많아지는 경우에 대비해 Scale Out이 자유로워야 한다.
4. 데이터베이스의 역할이라기보단 데이터 캐시 용도에 가깝다. 장시간 보관이 필요한 데이터는 아니다.
5. 상태에 대한 조회 기능이 메인이기 때문에, 다양한 쿼리 및 복잡한 Join 연산을 지원할 필요는 없다.
6. 이미 요청받은 데이터는 수정될 가능성이 적다. 데이터 수정 기능의 경우 크게 필요로 하지 않는다.
MongoDB
MongoDB는 다양한 쿼리 기능과 인덱스를 제공하고 있습니다. 다만 상태 DB의 가벼운 사용목적에 비해 MQL의 생소함과 러닝 커브로 인해 복잡성이 커지는 느낌을 받았고, 캐시 목적보단 영구 저장소 목적에 더 부합한다는 생각이 들었습니다. MongoDB가 지원하는 다양한 쿼리 또한 SENS에게는 필요 이상의 오버-스펙으로 느껴졌습니다.
Redis
Redis는 In-Memory DB로 SENS에서 요구하는 성능에 부합하는 퍼포먼스를 보여줄 수 있습니다. 또, Key-Value 기반의 간단하지만 다양한 데이터 타입을 지원하는 점 또한 장점으로 생각되었고, 상태 DB에서 필요로 하는 기능 또한 모두 만족하였습니다.
👉 각자 장단점이 있지만, SENS에서의 사용 목적과 상태 DB 처리 프로세스에 Redis가 더 적합하다고 판단하여 고민 끝에 Redis를 도입하기로 결정했습니다.
테스트
그럼, 제일 중요한 부분인 SENS가 필요로 하는 수준을 만족하는지 테스트를 진행해 보도록 하겠습니다.
진행하기에 앞서 테스트를 진행할 서버 Spec은 아래와 같습니다.
2vCPU, 4GB Mem, 100GB Disk
테스트만을 위해 만든 환경이라 사양은 높지 않지만, Redis의 성능을 측정하기에는 충분하다고 생각했고, Redis는 단일 노드로 구축했습니다.
테스트는 Redis-benchmark (참고)를 통해 진행했고,
아래 명령어를 사용했습니다.
redis-benchmark -p 6379 -d 10000 -r 10000 -n 100000 -q -t get,set,lpush,lpop,sadd,spop
다음은 테스트 결과를 나타낸 표입니다.
다른 설정을 그대로 두고 여러 번 테스트해 봤을 때, 편차는 있지만 대부분 근접하는 수치로 나오는 걸 확인할 수 있었고, maxmemory 설정을 바꾸어 테스트해 봤을 때도 maxmemory에 도달한다고 성능에 큰 영향을 주지는 않는 것으로 확인되었습니다.
SENS에서 주로 사용할 command인 Get, Set, Hash Get, Hash Set으로 봤을 때도 O(N) 명령이 아니라서 성능에 영향을 줄만한 요소는 아닙니다.
기타 Application의 로직이나 서버 Spec에 따라 차이는 있을 수 있겠지만, Redis의 성능만 놓고 봤을 때 상태 DB로써 사용하기에는 충분하다고 결론 내렸습니다.
글을 마치며
지금까지 Simple & Easy Notification (SENS)에서 메시지 요청 상태를 관리하기 위한 저장소를 사용하게 된 이유와 선택 과정에 대한 경험을 말씀드렸습니다.
서비스 개발 과정을 글로 남기는 과정이 조금은 생소하고 서툰 느낌이지만, 조금이나마 도움이 되셨길 바라며 글을 마칩니다. 감사합니다.
- 본 기술 포스팅은 NAVER Cloud의 PaaS Dev1 김선호 님의 글입니다.