ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [알림 서비스] 알림 서비스 구조 개선 - 대량의 알림 데이터 처리를 위한 장기적 관점의 확장 가능한 구조 설계
    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의 인메모리 큐에 메시지 저장
        • 컨슈머 개수 <= 큐의 개수
    • 컨슈머
      • n초마다 자신에게 할당된 큐에서 메시지 꺼낸 후, 알림 발송

     

    참고

    Kafka 리밸런싱

     

    1-1. 처리량 향상 검증

    Q. 위의 구조로 개선했을 때, 어떻게 데이터 처리량이 향상되는가?

    서버 Scale-Out 관점

    • 서버 Scale-Out 시 알림 요청 트래픽이 분산되어 각 서버의 인메모리 큐에 알림 데이터가 분산되어 저장
    • 즉, 알림 전송이 병렬 실행되므로 데이터 처리량이 향상되는 구조임

    하나의 서버 관점

    • 멀티 코어일 때 여러 개의 컨슈머가 병렬 실행되므로 데이터 처리량이 향상되는 구조임
    • 주의
      • 기존에 할당받은 서버 스펙은 CPU 1개인 싱글 코어임 -> 병렬 실행을 위한 기본 조건이 갖춰지지 않은 것임
      • 따라서 기존의 서버에서는 여러 개의 컨슈머가 병렬 실행될 수 없음

     

    2) 알림 발송 병렬 실행 구조 상세

    멀티 워커 구조

    구현 요구사항 및 조건

    • 구현 요구사항
      • 조회된 데이터의 개수에 따라 자동으로 워커 스레드의 실행 개수가 결정될 수 있어야 함 -> CPU 코어 개수에 따라 워커 스레드 실행 개수가 결정되어야 함
      • 설정 값은 모두 변수로 관리하여 쉽게 수정 가능해야 함
    • 조건
      • 하나의 워커 스레드에서 처리할 데이터의 개수는 얼마인가? -> FCM에 따라 결정되는 것이 맞음
        • FCM의 sendAll()은 최대 500개 가능
        • 하나의 스레드에서 500개 처리
      • 몇 개의 데이터를 조회할 것인가? -> 워커 스레드의 병렬 실행 가능 개수에 따라 결정되어야 함
        • 500의 배수

    동작 방식

    • 메시지 조회 스레드와 메시지 발송 스레드를 분리함
    • 조회된 메시지 개수에 따라 메시지 발송 스레드 여러 개가 병렬 실행됨

     

    설정 값 관리 및 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가 병렬 실행될 수 없음

    댓글

Designed by Tistory.