-
[알림 서비스] 알림 서비스 구조 개선 - 대량의 알림 데이터 처리를 위한 장기적 관점의 확장 가능한 구조 설계STOVE DEVCAMP 3기/알림 서비스 2023. 3. 10. 03:12
📌 개선 대상
현재 구조(알림 요청, 알림 전송) 동작 방식
- 전송할 알림 메시지를 데이터베이스에 저장하고, 스케줄러를 사용해 주기적으로 전송할 메시지를 데이터베이스에서 조회해 FCM에 알림을 전송함
📌 기존 처리 구조의 보완할 점 파악
1) 알림 요청 부분
- Spring Boot에서는 Thread Pool을 사용해 다중 요청을 처리함
- 주의 : 다중 요청을 받을 수 있다는 의미이고, 이것이 처리량이 향상된다는 의미가 아님
- 서버 Scale-Out 시 처리량 향상 가능함
- 따라서, 알림 요청 부분은 장기적인 관점에서 대량의 알림 데이터 처리가 가능한 구조임
2) 알림 전송 부분
- 일정 간격으로 하나의 스케줄러가 실행되고, 한 번의 실행에서 알림을 조회하고 발송하는 작업을 순차적으로 실행하는 구조임
- 현재 구조에서는 스케줄러가 알림을 조회할 때 데이터베이스에서 조회하기 때문에 서버 Scale-Out 시 여러 스케줄러가 동일한 영역의 데이터를 조회하게 될 수 있으므로, 데이터 경합과 알림 중복 전송이 발생함
- 이는 장기적인 관점에서 대량의 알림 데이터 처리가 가능한 구조가 아님 (서버 Scale-Out 시에도 데이터 처리량이 향상되는 구조가 아님)
📌 개선 구조
- 현재의 푸시 알림은 순서를 가지지 않는다는 점을 고려하면, 알림 조회 및 발송이 각각 병렬 실행되도록 개선 가능함
- 인메모리 메시지 브로커를 통해 알림 조회 병렬 실행 구조 수립
- 조회된 메시지 개수에 따라 알림 발송 스레드를 분리해 자동으로 알림 발송 병렬 실행 구조 수립
1) 알림 조회 병렬 실행 구조 상세
- 알림 조회 스레드가 병렬 실행될 경우, 현재 구조에서는 데이터베이스에서 동일한 영역의 데이터를 조회하기 때문에 데이터 경합 발생 및 알림 중복 전송 발생함
- 이는 병렬 실행되었지만, 데이터 처리량이 향상되는 구조가 아님
- 외부 메시지 브로커의 동작 방식을 참고해 현재 주어진 자원 내에서 알림 조회를 병렬 실행하여 데이터 처리량을 향상시킬 수 있는 구조에 대해 고민해봄
- 동일한 컨슈머 그룹 내의 알림 조회 컨슈머들이 서로 다른 데이터를 처리할 수 있도록 데이터를 분할해주는 인메모리 메시지 브로커를 구현함
- 인메모리 메시지 브로커
- 알림 생성 API 호출 시 알림 요청 처리 로직에서 1) 알림 메시지 저장, 2) 인메모리 메시지 브로커에 메시지 전달하면, 인메모리 메시지 브로커는 해당 Topic의 인메모리 큐에 메시지 저장
- 컨슈머 개수 <= 큐의 개수
- 알림 생성 API 호출 시 알림 요청 처리 로직에서 1) 알림 메시지 저장, 2) 인메모리 메시지 브로커에 메시지 전달하면, 인메모리 메시지 브로커는 해당 Topic의 인메모리 큐에 메시지 저장
- 컨슈머
- n초마다 자신에게 할당된 큐에서 메시지 꺼낸 후, 알림 발송
참고
Kafka 리밸런싱
1-1. 처리량 향상 검증
Q. 위의 구조로 개선했을 때, 어떻게 데이터 처리량이 향상되는가?
서버 Scale-Out 관점
- 서버 Scale-Out 시 알림 요청 트래픽이 분산되어 각 서버의 인메모리 큐에 알림 데이터가 분산되어 저장됨
- 즉, 알림 전송이 병렬 실행되므로 데이터 처리량이 향상되는 구조임
하나의 서버 관점
- 멀티 코어일 때 여러 개의 컨슈머가 병렬 실행되므로 데이터 처리량이 향상되는 구조임
- 주의
- 기존에 할당받은 서버 스펙은 CPU 1개인 싱글 코어임 -> 병렬 실행을 위한 기본 조건이 갖춰지지 않은 것임
- 따라서 기존의 서버에서는 여러 개의 컨슈머가 병렬 실행될 수 없음
2) 알림 발송 병렬 실행 구조 상세
멀티 워커 구조
- 1개의 스케줄러가 n개의 알림을 읽어와 m개의 스레드로 FCM 발송을 처리함
- 즉, 데이터 조회 스레드 1개 실행 + 데이터 처리 담당 워커 스레드 n개 실행하는 구조
- 구현 시 참고
구현 요구사항 및 조건
- 구현 요구사항
조회된 데이터의 개수에 따라 자동으로 워커 스레드의 실행 개수가 결정될 수 있어야 함-> CPU 코어 개수에 따라 워커 스레드 실행 개수가 결정되어야 함- 설정 값은 모두 변수로 관리하여 쉽게 수정 가능해야 함
- 조건
- 하나의 워커 스레드에서 처리할 데이터의 개수는 얼마인가? -> FCM에 따라 결정되는 것이 맞음
- FCM의 sendAll()은 최대 500개 가능
- 하나의 스레드에서 500개 처리
- 몇 개의 데이터를 조회할 것인가? -> 워커 스레드의 병렬 실행 가능 개수에 따라 결정되어야 함
500의 배수
- 하나의 워커 스레드에서 처리할 데이터의 개수는 얼마인가? -> FCM에 따라 결정되는 것이 맞음
동작 방식
- 메시지 조회 스레드와 메시지 발송 스레드를 분리함
- 조회된 메시지 개수에 따라 메시지 발송 스레드 여러 개가 병렬 실행됨
설정 값 관리 및 ThreadPool 사이즈 설정
참고
- 알림 발송은 외부 시스템과 통신하므로, IO Bound Task임
- 따라서, ThreadPool 크기는 CPU 코어의 개수와 대기 시간을 고려해야 함
이전 내용
- 설정 값들은 application.yml에 저장하여 쉽게 수정이 가능하도록 함
- fcm.message.limit : 메시지 조회 개수
- fcm.message.per.thread : 하나의 워커 스레드에서 처리하는 메시지 개수
- FcmWorker 멀티스레드 실행을 위한 ThreadPool 설정
- 동시 사용 스레드 예상 최대 개수
- 새로운 알림 전송 스케줄러, 알림 재전송 스케줄러가 동시에 실행되는 상황
- corePoolSize (동시에 실행시킬 스레드의 개수)
- 값 : messageLimit / messagePerThread
- maxPoolSize (스레드 풀의 최대 사이즈)
- 값 : corePoolSize * 2
- queueCapacity (스레드 풀 큐의 사이즈)
- corePoolSize 개수를 초과하는 task가 들어왔을 때 큐에 task가 쌓이게 됨
- 최대로 maxPoolSize 개수 만큼 쌓일 수 있음
- 값 : corePoolSize
- 동시 사용 스레드 예상 최대 개수
2-1. 처리량 향상 검증
서버 Scale-Out 관점
- 서버 Scale-Out 시 여러 개의 서버에서 알림 발송이 이루어짐
- 즉, 알림 발송이 병렬 실행되므로 데이터 처리량이 향상되는 구조임
하나의 서버 관점
- 멀티 코어일 때 여러 개의 FcmWorker가 병렬 실행되므로 데이터 처리량이 향상되는 구조임
- 주의
- 기존에 할당받은 서버 스펙은 CPU 1개인 싱글 코어임 -> 병렬 실행을 위한 기본 조건이 갖춰지지 않은 것임
- 따라서 기존의 서버에서는 여러 개의 FcmWorker가 병렬 실행될 수 없음
'STOVE DEVCAMP 3기 > 알림 서비스' 카테고리의 다른 글
[개념] Kafka 리밸런싱 (0) 2023.03.10 [알림 서비스] 알림 서비스 구조 개선 방향 수립 (0) 2023.03.10 [알림 서비스] 알림 서비스 구조 개선 - 알림 전송 외부 라이브러리 의존성 분리 리팩토링 (0) 2023.03.10 [알림 서비스] 알림 서비스 구조 개선 - 신뢰성 있는 알림 서비스 제공 (0) 2023.03.10 [알림 서비스] 알림 서비스 데이터베이스 설계 (0) 2023.03.10