100만 명 이상 사용자를 위한 신뢰성 높은 알림 시스템 설계 (푸시, SMS, 이메일)
Source: Dev.to
핀테크에서는 알림이 “선택 사항”이 아닙니다.
제품의 신뢰 계층의 일부이기 때문입니다.
사용자가 돈을 이체했는데 확인 메시지를 받지 못하면 당황하게 됩니다.
소규모에서는 알림 전송이 간단해 보입니다.
Application → Twilio/SendGrid → Done
하지만 수백만 명의 사용자, 여러 채널, 재시도, 공급자 장애, 트래픽 급증을 다루게 되면… 알림 시스템은 분산 시스템 문제가 됩니다.
분산 시스템은 주로 장애를 우아하게 처리하는 것이 핵심입니다.
예를 들어 핀테크 플랫폼이 다음과 같은 알림을 보낸다고 가정해 보세요.
- 거래 알림
- OTP
- 포트폴리오 업데이트
- 가격 알림
- 보안 알림
이를 100만 명 이상의 사용자에게 다음 채널을 통해 전달합니다.
- 푸시 알림
- SMS
- 이메일
문제는 단순히 “메시지를 보내는 것”이 아니라, 시스템이 다음을 보장하도록 하는 것입니다.
- 중복 전송을 하지 않음
- 메시지를 조용히 잃어버리지 않음
- 공급자 장애를 견딤
- 트래픽 급증 시에도 확장 가능
- 문제가 발생했을 때 관찰 가능
아래는 제가 추천하는 아키텍처입니다.
[앱 서비스]
│
▼
[알림 큐 (Redis Streams / SQS / Kafka)]
│
▼
[워커 풀]
│
▼
[프로바이더 라우터]
│
┌────┼───────────────────────────────┐
▼ ▼ ▼
SMS 이메일 푸시
│ │ │
▼ ▼ ▼
Twilio SendGrid FCM/APNs
│
▼
대체 프로바이더
(Termii / Mailgun / Direct APNs)
│
▼
[전송 로그 + 멱등성 저장소]
(PostgreSQL + Redis)
API 설계 변경
팀이 초기에 가장 많이 저지르는 실수 중 하나는 API 요청 흐름에서 바로 알림을 전송하는 것입니다.
이 방식은 트래픽이 급증하기 전까지는 동작합니다.
블랙 프라이데이, 암호화폐 시장 붕괴, 급여 지급일 등을 떠올려 보세요.
앱이 Twilio나 SendGrid의 응답을 기다리면서 사용자에게 응답을 반환한다면, 애플리케이션 전체가 외부 공급자에 인질 잡힌 셈입니다.
따라서 API는 다음 한 가지 일만 해야 합니다.
- 요청을 빠르게 받아들이고 알림 이벤트를 큐에 넣는다.
그 뒤 워커 프로세스가 비동기적으로 전송을 담당합니다.
큐를 사용하면 얻을 수 있는 장점
- 수평 확장성
- 재시도 기능
- 백프레셔 처리
- 장애 격리
공급자가 느려지면 큐가 스파이크를 흡수해 애플리케이션이 다운되는 일을 방지합니다.
이 규모에서는 큐가 선택이 아닌 필수 인프라가 됩니다.
추천 기술 스택
- Redis Streams
- AWS SQS
- Kafka (특히 초고처리량 시스템)
중복 전송 방지
알림 시스템에서 가장 어려운 문제는 실패한 전송이 아니라 중복 전송입니다.
사용자는 지연된 알림은 어느 정도 용인하지만, 같은 출금 알림을 세 번 받는 것은 용인하지 않습니다.
중복은 주로 재시도 과정에서 발생합니다.
예시
- 워커가 SMS 전송
- 공급자는 실제로 성공
- 네트워크 타임아웃 발생 → 응답을 받지 못함
- 워커가 실패로 판단하고 재시도
- 사용자는 중복 SMS 수신
이를 방지하려면 모든 알림에 idempotency_key 를 부여하고, 전송 전 다음을 확인합니다.
“우리는 이미 이 알림을 처리했는가?”
예시 제약조건
UNIQUE(user_id, notification_type, idempotency_key)
이 작은 설계 선택이 나중에 막대한 운영 고통을 예방합니다.
재시도가 여러 번 일어나더라도 데이터베이스가 최종 방어선이 됩니다.
전송 시도 로그
모든 전송 시도는 성공 여부와 관계없이 기록되어야 합니다.
notification_attempts
기록 내용
- 사용된 공급자
- 응답 상태
- 재시도 횟수
- 타임스탬프
- 공급자 오류 페이로드
운영 중 문제가 발생하면 추측이 아니라 증거가 필요합니다.
공급자 장애는 흔한 일
- Twilio가 일시적으로 성능 저하
- SendGrid가 다운
공급자가 항상 가용하다고 가정하는 설계는 위험합니다.
신뢰할 수 있는 시스템은 실패를 정상으로 가정합니다.
따라서 단일 공급자에 하드코딩하지 말고, 프로바이더 라우팅 레이어를 도입하세요.
공급자 라우팅 테이블
| 채널 | 주요 | 대체 |
|---|---|---|
| SMS | Twilio / Termii | Flutterwave SMS |
| 이메일 | SendGrid | Mailgun |
| 푸시 | Firebase FCM | Direct APNs |
워커 흐름
- 주요 공급자 시도
- 타임아웃·실패 감지
- 지능형 재시도
- 필요 시 자동 페일오버
사용자는 공급자가 “나쁜 하루”를 보냈다는 사실을 눈치