FCM 알림 기능 붙일 때 처음엔 단순히 API에서 바로 Firebase 호출하면 되지 않나 생각했었음.
근데 실제 서비스에서는 생각보다 고려할 게 많았음.
특히 아래 같은 문제들이 있었음.
- 요청이 몰릴 때 API 응답이 느려질 수 있음
- FCM 전송 실패를 API 요청 흐름 안에서 바로 처리하면 사용자 응답까지 지연될 수 있음
- 재시도 로직, 실패 로그 추적, 토큰 만료 처리 등을 한 군데서 관리하기 어려움
- 대량 발송이나 비동기 처리 시 안정성이 떨어질 수 있음
그래서 BullMQ + Redis + Firebase Admin SDK 조합으로 알림 구조를 분리해서 처리했음.
이번 글에서는
FCM 알림이 생성되고, 큐에 적재되고, 워커가 처리하고, 실패 토큰을 정리하는 전체 흐름을 정리해보겠음.
왜 API에서 바로 FCM을 보내지 않았음?
처음에는 서버 API 안에서 바로 아래처럼 FCM 호출하는 구조를 생각할 수 있음.
await admin.messaging().send(message);
이 방식은 구현은 간단함.
근데 운영 환경에서는 몇 가지 단점이 있음.
1) API 응답 시간 증가
알림 전송은 외부 서비스(Firebase)에 대한 네트워크 호출임.
이걸 사용자 요청 흐름 안에서 바로 처리하면, 알림 전송 속도에 따라 API 응답도 같이 느려질 수 있음.
2) 실패 처리 복잡
FCM 전송 실패 원인은 다양함.
- 토큰 만료
- 등록 해제된 토큰
- 일시적 네트워크 오류
- 메시지 포맷 오류
이걸 API 안에서 전부 분기 처리하면 서비스 로직이 지저분해지기 쉬움.
3) 재시도/로깅/추적이 어려움
실패했을 때 몇 번 재시도할지,
어떤 요청이 실패했는지,
어떤 토큰에서 반복적으로 문제가 나는지 추적하기가 어려움.
그래서 알림 생성과 알림 발송을 분리하는 게 좋다고 판단했음.
전체 구조
내가 정리한 구조는 대략 아래 흐름임.
[사용자 이벤트 발생]
↓
[API 서버]
알림 발송 데이터 생성
↓
[BullMQ Queue에 Job 추가]
↓
[Redis]
Job 저장
↓
[Worker]
Queue에서 Job 가져옴
↓
[Firebase Admin SDK]
FCM 전송
↓
[성공/실패 로그 기록]
↓
[실패 시 토큰 비활성화 또는 재시도]
조금 더 현실적으로 풀면 아래와 같음.
- 채팅, 시스템 이벤트, 공지 등 알림 발생
- API 서버는 알림 payload를 만들고 바로 큐에 넣음
- 사용자에게는 알림 발송 완료가 아니라 발송 요청이 등록됨
- 별도 Worker 프로세스가 Redis Queue에서 job을 가져감
- Worker가 Firebase Admin SDK로 실제 FCM 전송 수행
- 성공하면 로그 남기고 종료
- 실패하면 에러 코드에 따라 재시도하거나 토큰 비활성화 처리
'TIL' 카테고리의 다른 글
| Ansible 쓰는이유 (0) | 2026.03.16 |
|---|---|
| 트래픽 몰려도 멀쩡히 웹서버 rollout을 해보자 (0) | 2026.03.13 |
| Docker - ContainerD 차이 (0) | 2026.03.07 |
| 리눅스 파일 찾을때 명령어 저장 (0) | 2026.03.06 |
| Kubernetes 구성요소 (0) | 2026.03.04 |