ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [알림 서비스] 알림 서비스 구조 개선 방향 수립
    STOVE DEVCAMP 3기/알림 서비스 2023. 3. 10. 03:15

    💡 구조 개선 방안 상세와 이에 대한 멘토님의 피드백을 정리한 문서입니다.

     

     

    <구조 개선 방안 상세>

     

    📌 개선 대상

    • 알림 생성 기능 처리 구조

     

    📌 최종 목적

    • 알림 서버가 재시작되거나 장애가 발생해도 생성된 알림이 유실되지 않고 Firebase에 전송되도록 함
    • 알림 생성 API 지연응답으로 유저/트윗 서비스에 장애를 전파하지 않도록 함
    • Firebase에 장애가 발생하거나 Firebase 전송 오류가 발생해도 생성된 알림이 유실되지 않고 Firebase에 전송되도록 함

     

    📌 현재 처리 구조의 문제점 및 보완할 점 파악

    • 알림 서버가 재시작되거나 장애가 발생하여 서버가 다운되면, Firebase에 아직 전달되지 않은 알림 메시지는 유실됨
    • Firebase에 장애가 발생하거나 Firebase 전송 오류가 발생하면, 알림 메시지가 유실됨
    • 알림 생성 API 지연응답이 발생할 경우, 해당 API를 호출한 유저/트윗 서비스에 장애가 전파됨

     

    📌 4차 현황보고 전까지 개선할 구조 상세

    최종적으로 달성하고자 하는 구조 (to-be)

    • 외부 메시지 큐를 사용해 FCM에 알림 전송을 비동기로 처리하고, FCM에 전송할 알림 데이터의 유실을 방지함

     

    조건

    • 현재 주어진 자원 내에서 수행해야 함 → 외부 메시지 큐 사용 X

     

    구성

    • Spring Application Event 기반 비동기 이벤트 처리 방식 사용

     

    <처리 순서>

    1) '알림 생성' 이벤트가 발생하면, '이벤트 기록 Listener'는 이벤트를 이벤트 저장소(=MongoDB)에 저장한다.

    2) '알림 생성' 이벤트가 발생하면, 'FCM 알림 전송 Listener'는 Firebase에 알림을 전송한다.

    3) 'FCM 알림 전송 Listener'는 Firebase로부터 올바른 응답을 수신한 경우, 이벤트가 정상 처리되었음을 이벤트 저장소에 기록한다. (이벤트 처리 완료 판별용 flag 사용)

    4) 정상 처리되지 않은(=Firebase에 전송되지 않은) 이벤트는 이벤트 처리 감지 배치를 통해 해당 이벤트를 재발행하여 재처리되도록 한다.

     

    예상 개선 사항

    • Firebase에 알림을 전송하는 작업을 비동기로 처리하여 알림 생성 API를 호출한 유저/트윗 서비스에 장애를 전파하지 않음
    • 이벤트 저장소(기존 DB)와 배치를 사용하여 알림 이벤트가 유실되지 않고 Firebase에 정상적으로 전송될 수 있게 함
    • Service 클래스가 1) FCM에 전송할 알림 이벤트를 저장하고, 2) FCM에 알림을 전송하는 기능을 포함하므로, 이벤트 발행/구독 형태를 통해 FCM 과의 느슨한 결합을 구성함

     

    참고

    https://velog.io/@backtony/Spring-Event-Driven

    https://techblog.woowahan.com/7835/

     


    <피드백>

     

    📌 추가로 고민할 부분

    1) 여러 건의 이벤트를 한 번에 처리할 수 있는 구조에 대한 고민

    • 여러 요청을 병렬적으로 처리하는 방법 중 하나가 멀티스레드로 처리하는 방식임
    • SpringBoot는 기본적으로 멀티스레드로 동작하므로, Spring Application Event를 사용한다고 했을 때, 이벤트 생성/발행/처리가 멀티스레드 환경에서 병렬적으로 처리되는 구조임
    • 따라서 추가적으로 해야할 부분은
      1. Spring Application Event의 이벤트 발행/처리 처리 방식이 기본적으로 동기 방식이므로, 이벤트 처리 부분은 비동기로 수행되도록 한다.
      2. 이때 @Async는 항상 새로운 스레드를 만들어 할당하므로, Async 설정에서 생성 스레드 개수를 제한하는 thread pool 설정을 추가한다.

     

    2) 리스너의 thread pool 구성에 대한 고민

    • 위의 사진의 2개의 리스너가 동일한 thread pool을 사용한다면, FCM 발송과 DB 처리 부분이 서로 영향을 줄 가능성이 있음
    • 느슨한 결합 구조를 고려한다면, 리스너 별 thread pool을 다르게 설정하는 방안과 thread pool의 사이즈도 같이 고민해봐야 함 ⭐️

     

    2) 위와 같은 구조가 꼭 필요한지에 대한 고민

    위와 같은 이벤트 발행/구독 형태가 개선 방안이라고 생각한 이유

    • 4차 현황보고 전까지 외부 메시지 큐를 사용하지 않고, 현재 주어진 자원 내에서 개선을 수행하고자 함
    • 스케줄러로만 FCM에 알림 전송을 할 경우, 스케줄러 실행 간격으로 인해 사용자의 알림 수신 시점이 늦어진다는 단점이 있을 것이라고 생각되어 스케줄러의 메인 용도는 알림 데이터 유실 방지라고 생각함
    • 또한, 위의 사진에서 Service 클래스가 1) FCM에 전송할 알림 이벤트를 저장하고, 2) FCM에 알림을 전송하는 기능을 포함하므로, 이벤트 발행/구독 형태를 통해 FCM 과의 느슨한 결합을 구성할 수 있다고 생각함

     

    위와 같은 Spring Application Event를 사용한 이벤트 발행/구독 형태 없이도 더 심플하게 처리가 가능한 이유 - 내가 놓친 부분, 메시지 큐의 작동 방식을 제대로 알지 못했음

    • FCM 알림 전송 부분은 실시간이 아니라 준 실시간 성격을 가짐
    • 따라서, '배치의 실행 간격으로 알림 수신이 늦어진다'는 관점으로만 생각하면 잘못된 접근임
    • Kafka와 같은 외부 메시지 큐를 활용한 경우, Consumer를 Polling 형태로 구성할 수 있고, 이는 배치의 처리 방식과 구조적으로 유사함
      • 단지 ms 단위로 좀더 빠르게 데이터를 받아오는 형태라 인지하지 못했던 부분일 뿐임
      • 배치도 수행 간격을 ms 기준으로 설정해 실행한다면, 유사한 처리 성능을 가짐
      • 배치는 N개의 서버를 구성해서 처리할 경우 N개의 배치 서버 간의 데이터 경합을 고려한 구성을 추가로 고민하여 처리해야 함 ⭐️

      

    📌 최종 구조 수립

    구성

    • 배치의 DB Polling 방식으로만 FCM에 알림 전송

    이유

    • 이번 알림 구조 개선 방안의 목적이 외부 메시지 큐를 사용하지 않고 주어진 자원 내에서 이와 유사한 형태를 구현하고자 함이므로, 배치를 통한 DB Polling 방식만으로도 충분히 개선이 가능하다고 판단함

    댓글

Designed by Tistory.