[NEWZET] FCM 알림 전송 비동기 배치처리

dodo·2025년 6월 23일

NEWZET

목록 보기
4/5

개요

FCM 알림 전송을 위한 비동기 배치 처리 시스템 구현으로 대량 트래픽 상황에서 시스템 안정성 및 응답성 향상

배경

메일 서비스 특성상 단기간에 대량의 메일 수신이 발생하며, 각 메일 수신 시마다 FCM 알림을 전송해야 하는 상황이다.
동기 처리 방식으로 구현한다면 다음과 같은 문제점이 발생할 것이다:

응답 지연: 메일 수신 → Article 저장 → FCM 전송이 모두 동기로 처리되어 사용자 응답 시간이 급격히 증가
시스템 부하: 대량 메일 수신 시 FCM API 호출이 메인 애플리케이션 스레드를 블로킹
장애 전파: FCM 전송 실패가 Article 저장 프로세스에 영향을 미쳐 전체 시스템 불안정
메모리 부족: 대량의 FCM 요청을 동시에 처리할 때 메모리 사용량 급증

이러한 문제 발생을 방지하기 위해 비동기 배치 처리 시스템이 필요했다.

변경된 점

1. FCM 배치 처리 인터페이스 구조 설계

Feat: BatchProducer와 BatchConsumer를 상속받아 인터페이스 Article용으로 제작
Feat: BatchProducer와 BatchConsumer를 상속받은 FCM용 배치 인터페이스 생성

  • common에 정의된 배치 BatchProducer, BatchConsumer 인터페이스를 FCM 도메인에 맞게 상속하여 개별 인터페이스 제작
  • FcmBatchProducer, FcmBatchConsumer 인터페이스를 통해 도메인별 배치 처리 구현
  • 공통 인터페이스의 재사용성과 코드 통일성 향상, OCP(Open-Closed Principle) 원칙 준수

2. Redis Stream 기반 비동기 배치 처리 시스템 구현

시스템 안정성 및 응답성 향상

Producer: Feat: FcmBatchProducer의 Redis 기반 구현체 추가

  • FCM 알림 요청을 Redis Stream에 즉시 추가 후 응답 반환
  • ReactiveRedisTemplate을 사용한 논블로킹 처리로 메인 스레드 영향 없음
  • 유효하지 않은 알림에 대한 사전 검증 및 필터링

Consumer: Feat: FcmBatchConsumer의 Redis 기반 구현체 추가

  • 전용 스레드 풀에서 배치 단위로 FCM 알림 처리
  • Firebase Messaging API를 통한 실제 알림 전송
  • 배치 크기 및 타임아웃 설정 기반 유연한 처리

장애 격리 및 부하 분산

  • FCM 전송 실패가 Article 저장 프로세스에 영향을 주지 않음
  • 단기간 대량 메일 수신 시 FCM 전송을 시간에 걸쳐 분산 처리
  • Redis Stream을 통한 메모리 효율적인 큐 관리

3. FCM 토큰 관리 및 알림 전송용 아키텍쳐

Repository Layer: Feat: 유저 ID 기반 FcmToken 전체 조회용 repo 구현체 메서드 추가

  • FCM 토큰의 CRUD 작업 처리
  • 사용자별 토큰 조회 및 관리

Service Layer: Feat: fcm 비즈니스 로직에 sendFcmWhenMailReceivedBatch 추가

  • FCM 토큰 등록/갱신/삭제 비즈니스 로직
  • 메일 수신 시 배치 알림 전송 처리
  • 사용자당 다중 디바이스 지원 (중복 FCM 토큰 허용)

Orchestrator Layer: Feat: FcmTokenOrchestrator에 하위 서비스단에서 추가된 로직 추가

  • 서비스 비즈니스 로직 기반 연결 매개체

Controller Layer: Feat: fcm용 실시간 배치 처리 상태 모니터링 및 제어 api 제공 컨트롤러 추가

  • FCM 토큰 관리 REST API 제공

4. 토큰 관리 및 실패 처리

Feat: FcmBatchConsumer의 Redis 기반 구현체 추가

Feat: 배치 처리 결과 전달을 위한 클래스 추가

  • FCM 전송 실패 시 상세 로깅 처리
  • 자동 토큰 정리: 유효하지 않은 토큰(만료/삭제된 앱) 자동 삭제로 불필요한 처리 방지
  • 배치 처리 결과 통계 제공 (FcmBatchProcessingResult)

5. Article 도메인과의 완전한 비동기 연동

Feat: Article 저장 성공 시 FCM 배치 처리 호출 로직 추가

Feat: FcmBatchConsumer의 Redis 기반 구현체 추가

  • Article 저장 완료 후 FCM 알림을 배치 큐에 추가만 하고 즉시 응답
  • ArticleRedisBatchConsumerImpl에서 Article 저장 성공 시 FCM 배치 처리 호출
  • 응답 시간 대폭 단축: 동기 FCM 전송 대기 시간 제거

참고자료

배치 처리 흐름 및 성능 이점

1. 메일 수신 → Article 도메인 배치 처리
2. Article 저장 성공 → FCM 알림 배치 큐에 추가 (비동기)
3. 즉시 사용자 응답 반환
4. 백그라운드에서 FCM 배치 Consumer가 일정 주기/크기로 알림 전송
5. 전송 실패 시 유효하지 않은 토큰 자동 삭제

핵심 성능 최적화 포인트

  • Redis Stream을 통한 비동기 처리로 응답 시간 단축
  • 장애 격리로 FCM 장애가 메일 처리에 영향 없음
  • 배치 처리로 대량 트래픽 상황에서도 안정적 처리
  • ReactiveRedisTemplate 사용으로 논블로킹 I/O 구현
  • 실패한 토큰 자동 정리로 시스템 효율성 지속 향상 및 유저별 다중 디바이스 FCM Token 허용 가능

Firebase 설정 관련 주의사항

firebase-service-account.json 파일을 resourses 하위에 두고 .gitignore에 추가한 상태
향후 컨테이너 환경에서 Firebase 인증 파일 마운트 작업으로 Docker Compose에서 사용할 수 있는 환경 구성 필요

로컬 환경에서 웹 기반 fcm 정상 작동 확인


PR 코멘트 반영 변경항목

1. 인터페이스 → 추상클래스 패턴 기반 아키텍처 도입

commit : Feat: BatchProducer에 대한 추상클래스 구현체 생성

commit : Feat: BatchConsumer에 대한 추상클래스 구현체 생성

변경 전: 인터페이스만 사용하는 구조

Interface
├── BatchProducer<T>
├── BatchConsumer
    │
    ├── ArticleBatchProducer
    ├── FcmBatchProducer  
    ├── ArticleBatchConsumer
    ├── FcmBatchConsumer
        │
        └── 구현체들
            ├── ArticleRedisBatchProducerImpl
            ├── FcmRedisBatchProducerImpl
            ├── ArticleRedisBatchConsumerImpl (Redis 연결, 직렬화, 배치처리 등 중복코드)
            └── FcmRedisBatchConsumerImpl (Redis 연결, 직렬화, 배치처리 등 중복코드)

모든 구현체에서 Redis 처리 로직 등 일부 코드가 중복된다는 문제 발생

변경 후: 추상클래스 + 인터페이스 구조

Interface
├── BatchProducer<T>
├── BatchConsumer
    │
    ├── AbstractBatchProducer<T> (공통 로직)
    ├── AbstractBatchConsumer<T> (공통 로직)
        │
        ├── ArticleBatchProducer
        ├── FcmBatchProducer
        ├── ArticleBatchConsumer  
        ├── FcmBatchConsumer
            │
            └── 구현체들
                ├── ArticleRedisBatchProducerImpl (도메인 로직만)
                ├── FcmRedisBatchProducerImpl (도메인 로직만)
                ├── ArticleRedisBatchConsumerImpl (Article 처리 로직만)
                └── FcmRedisBatchConsumerImpl (FCM 전송 로직만)
  • AbstractBatchProducer 및 AbstractBatchConsumer 추상 클래스 생성
  • Producer와 Consumer의 공통 로직을 추상 클래스로 분리하여 코드 중복 제거
  • 각 도메인별 구현체는 도메인 특화 로직에만 집중하도록 개선

변경으로 인한 개선 효과

  • 코드 중복 제거: ~40% 코드량 감소
  • 유지보수성 향상: 공통 로직 한 곳에서 관리
  • 개발 생산성: 새 배치 추가시 도메인 로직만 구현
  • 관심사 분리: 인프라 로직 vs 비즈니스 로직 명확히 분리

2. FCM 토큰 관리 로직 개선

commit : Refactor: fcmNotification에 대한 검증을 생선단계에서 실행하도록 변경

commit. : Refactor: FcmRedisBatchConsumerImpl에서 유효하지 않은 fcmToken에 대한 삭제를 repo단에서 수행하도록 변경

  • FCM 알림 검증 로직을 생성 단계에서 실행하도록 변경
  • 유효하지 않은 FCM 토큰 삭제 로직을 Repository 레이어로 이동
  • Producer 단계에서 불필요한 유효성 검사 로직 제거

3. 도메인별 배치 구현체 리팩토링

commit : Feat: Aritcle 하위 Batch에 추상클래스 상속

commit : Feat: Fcm 하위 Batch에 추상클래스 상속

  • ArticleRedisBatchProducerImpl / ArticleRedisBatchConsumerImpl을 추상 클래스 상속 구조로 변경
  • FcmRedisBatchProducerImpl / FcmRedisBatchConsumerImpl을 추상 클래스 상속 구조로 변경
  • 각 구현체는 도메인별 특화 로직만 구현하도록 단순화

PR 코멘트 반영 변경항목 2

1. FCM 전송 로직 분리

commit : Feat: FcmToken 비즈니스 로직에서 FcmSender 관련 로직 분리 및 batch가 아닌 Fcm 메시지 전송 로직 구현

  • 기존: FcmTokenService에서 토큰 관리와 FCM 전송을 모두 처리
  • 개선: FcmSenderService 신규 생성하여 FCM 전송 책임만 담당

2. FcmSenderService 신규 생성

@Service
public class FcmSenderService {
    // 배치 전송 메서드
    public void sendFcmWhenMailReceivedBatch(UUID userId, String fromName, String title)
    public void sendFcmNotBatch(UUID userId, String fromName, String title)
    
    // 단일 전송 메서드
    public void send(FcmNotification fcmNotification)
    
    // 내부 유틸리티 메서드들
    private Message buildFcmMessage(FcmNotification fcmNotification)
    private void handleSendFailure(FcmNotification fcmNotification, Exception e)
    private boolean isInvalidTokenError(Exception e)
}

3. Consumer 로직 개선

commit : Refactor: 분리된 FcmSender 로직으로 Article 배치 로직 의존성 변경

  • 기존: Consumer에서 직접 FCM 전송 실패 시 토큰 삭제 처리
  • 개선: FcmSenderOrchestrator를 통해 통합된 전송 로직 사용
    -> FCM 전송 실패 시 토큰 삭제 로직이 FcmSenderService 내부로 캡슐화됨

4. Orchestrator 레이어 활용

  • FcmSenderOrchestrator를 통해 FCM 전송 로직에 대한 진입점 제공
  • Consumer는 더 이상 직접적인 FCM 처리 로직을 포함하지 않음

PR 보러가기 : https://github.com/newzet-dev/mail-server/pull/92

profile
클라우드 데이터 플랫폼 주니어 개발자 도도입니다!

0개의 댓글