
알림 시스템은 사용자에게 중요한 정보나 이벤트를 전달하는 시스템
대표적인 알림 방식은 다음과 같음.
알림 시스템은 보통 정해진 시간 안에 빠르게 전달되는 것이 중요하지만, 일부 지연이 발생했다고 시스템 전체가 실패한 것으로 보지는 않기 때문에 연성 실시간(Soft Real-Time) 시스템에 가까움.
반대로 경성 실시간(Hard Real-Time) 시스템은 정해진 deadline을 반드시 지켜야 하며 이를 넘기는 것이 시스템 실패로 이어질 수 있음.
알림 시스템을 설계하기 전에 다음 사항을 먼저 확인해야 함.
어떤 종류의 알림을 지원할 것인가
어떤 종류의 단말을 지원할 것인가
알림을 생성하는 주체는 누구인가
사용자가 특정 알림을 거부하거나 opt-out 할 수 있어야 하는가
하루에 몇 건의 알림을 처리해야 하는가
어느 정도의 알림 지연까지 허용할 것인가
iOS에 푸시 알림을 보내기 위해서는 크게 다음 세 요소가 필요함.
알림 요청을 생성하여 APNs로 전달하는 서버임.
알림을 생성하기 위해 다음과 같은 정보가 필요함.
Device Token
Payload
Apple Push Notification service
Apple이 제공하는 푸시 알림 서비스로, 알림 서버에서 받은 요청을 실제 iOS 단말로 전달함.
알림 서버
→ APNs
→ iOS 앱
Android에서는 일반적으로 FCM(Firebase Cloud Messaging)을 사용함.
개략적인 구조는 iOS와 유사함.
알림 서버
→ FCM
→ Android 앱
다만 APNs와 FCM은 사용하는 API와 인증 방식 등 세부 구현에는 차이가 있음.
SMS는 통신사와 직접 연동하기보다 제3자 서비스를 사용하는 경우가 많음.
대표적인 서비스:
알림 서버
→ SMS 제공 사업자
→ 이동통신망
→ 사용자 휴대전화
이메일 역시 제3자 이메일 서비스를 사용할 수 있음.
대표적인 서비스:
이러한 서비스는 대량 이메일 전송뿐 아니라 전송 성공률이나 클릭률 등의 데이터 분석 기능도 제공할 수 있음.
알림을 보내기 위해 다음과 같은 정보가 필요함.
이메일 주소와 전화번호 등의 기본 정보는 user 테이블에 저장하고, Device Token은 별도의 device 테이블에 저장함.
이렇게 분리하는 이유는 한 사용자가 여러 단말을 사용할 수 있기 때문임.
User A
├─ Device 1
├─ Device 2
└─ Device 3
즉 관계는 다음과 같음.
User : Device
= 1 : N
필요한 경우 한 사용자가 등록한 모든 단말로 알림을 전송할 수 있음.


알림 시스템의 주요 컴포넌트는 다음과 같음.
알림을 발생시키는 서비스임.
예:
예를 들어:
주문 서비스 → 주문 완료 알림
결제 서비스 → 결제 완료 알림
배송 서비스 → 배송 시작 알림
즉 알림 시스템은 서로 다른 서비스들이 생성하는 알림을 공통으로 처리하는 인프라 역할을 함.
알림 시스템의 핵심 컴포넌트임.
주요 역할:
알림 서버를 한 대만 두면 다음 문제가 발생할 수 있음.
따라서 알림 서버를 여러 대로 구성하고 DB와 캐시도 별도 컴포넌트로 분리함.
자주 조회되는 다음 정보를 저장함.
DB 접근을 줄이고 응답 시간을 단축할 수 있음.
다음과 같은 데이터를 영구 저장함.
시스템 컴포넌트 사이의 강한 결합을 줄이는 역할을 함.
알림 서버
→ Message Queue
→ Worker
대량의 알림이 한꺼번에 발생할 경우 알림을 임시로 저장하는 버퍼 역할도 담당함.
메시지 큐에서 알림 이벤트를 가져와 실제 제3자 서비스로 전달함.
Push Queue
→ Push Worker
→ APNs / FCM
SMS Queue
→ SMS Worker
→ SMS Provider
Email Queue
→ Email Worker
→ Email Provider
실제로 사용자에게 알림을 전달함.
예:
제3자 서비스와 통합할 때는 새로운 서비스 사업자를 쉽게 추가하거나 기존 사업자를 제거할 수 있도록 설계하는 것이 중요함.
특정 국가나 시장에서는 특정 서비스 제공자를 사용할 수 없을 수도 있으므로 대체 사업자를 지원할 수 있어야 함.
POST /api/v1/email/send
요청 예:

서비스
→ 알림 서버
→ 메시지 큐
→ Worker
→ 외부 알림 서비스
→ 사용자
책의 설계 요구사항에서는 다음과 같이 가정함.
알림은 지연되거나 순서가 달라질 수는 있지만 시스템 내부에서 소실되어서는 안 됨.
이를 위해 알림 데이터를 지속적으로 저장하고 전송 실패 시 재시도해야 함.
한 가지 방법은 알림 로그 DB를 유지하는 것임.
알림 이벤트 생성
→ 로그 또는 지속 가능한 큐에 저장
→ 전송 시도
→ 결과 저장
전송에 실패하면 저장된 데이터를 이용해 다시 시도할 수 있음.
분산 시스템에서는 재시도로 인해 동일 이벤트가 여러 번 처리될 수 있음.
따라서 각 이벤트에 고유한 event_id를 부여할 수 있음.
event_id = 8237471
Worker가 이벤트를 받으면:
event_id 확인
↓
이미 처리했는가?
├─ YES → 폐기
└─ NO → 알림 전송
이를 통해 중복 발송 빈도를 줄일 수 있음.
매번 알림 문구를 새로 작성하는 대신 미리 형식을 정의해둘 수 있음.
예:
[배송 알림]
{사용자명}님의 상품이 배송을 시작했습니다.
송장번호: {송장번호}
장점:
사용자가 너무 많은 알림을 받으면 알림 기능 자체를 꺼버릴 수 있음.
따라서 사용자가 종류별로 알림을 조정할 수 있어야 함.
마케팅 알림 OFF
배송 알림 ON
결제 알림 ON
이벤트 알림 OFF
알림을 보내기 전에 사용자 설정을 확인해야 함.
한 사용자에게 지나치게 많은 알림을 보내지 않도록 제한함.
예:
마케팅 Push
→ 사용자당 하루 최대 3건
이를 통해:
등의 효과를 얻을 수 있음.
전송 실패 시 알림을 재시도 전용 큐로 이동시킴.
전송 실패
→ Retry Queue
→ 재시도
계속 실패할 경우 오류를 기록하고 운영자나 개발자에게 알림을 보낼 수 있음.
재시도 횟수와 재시도 간격도 제한해야 함.
인증되지 않은 클라이언트가 임의로 알림을 보낼 수 없어야 함.
APNs와 FCM은 각각 별도의 인증 방식을 사용함.
APNs
FCM
즉 단순한 appKey + appSecret으로 일반화하기보다 플랫폼별 인증 체계를 사용한다고 보는 것이 적절함.
알림 시스템에서 중요한 지표 중 하나는 메시지 큐에 쌓인 알림의 수임.
Queue Size ↑
큐가 계속 증가한다면:
알림 생성 속도
>
Worker 처리 속도
일 가능성이 있음.
이 경우 다음을 확인해야 함.
필요하면 Worker를 증설할 수 있음.
다음 지표를 수집하면 알림의 효과를 분석할 수 있음.
예:
Push 발송
→ 알림 확인
→ 클릭
→ 앱 실행
→ 구매
이벤트 추적 데이터는 사용자 행동과 알림 품질을 개선하는 데 활용할 수 있음.
책에서는 이 부분의 참고 자료로 다음 글을 제시함.
Tyler Treat, You Cannot Have Exactly-Once Delivery
핵심 내용은 네트워크로 분리된 두 컴포넌트 사이에서 메시지가 정확히 한 번 전달됐다는 사실을 완벽하게 확인하기 어렵다는 것임.
예를 들어 서버 A가 서버 B에게 메시지를 보냈는데 ACK를 받지 못했다고 가정함.
가능한 경우:
1. 메시지가 네트워크에서 유실됨
2. B가 메시지를 받았지만 처리 전에 장애 발생
3. B가 메시지를 처리 중이지만 매우 느림
4. B가 처리를 완료함
5. B가 ACK를 보냈지만 ACK가 유실됨
A는 ACK를 받지 못했다는 사실만으로 B가 메시지를 처리했는지 알 수 없음.
블로그에서는 이러한 분산 시스템의 불확실성을 설명하기 위해 Two Generals Problem, Byzantine Generals Problem, FLP 결과 등을 함께 언급함.
FLP는 exactly-once delivery 자체에 대한 직접적인 증명이라기보다는 비동기 분산 시스템에서 장애와 합의가 갖는 근본적인 한계를 설명하는 이론적 배경으로 볼 수 있음.
최대 한 번 전달하는 방식.
블로그의 단순한 예에서는 메시지를 받은 뒤 처리 전에 ACK를 보냄.
메시지 수신
→ ACK
→ 처리
ACK 이후 처리 중 장애가 발생하면 Sender는 이미 성공했다고 생각하므로 재전송하지 않음.
따라서:
At-most-once
→ 중복 가능성 낮음
→ 메시지 유실 가능
알림 서비스라면 사용자가 받아야 할 알림이 사라질 수 있음.
최소 한 번 이상 전달하는 방식.
메시지를 처리한 뒤 ACK를 보냄.
메시지 수신
→ 처리
→ ACK
하지만 처리는 완료됐는데 ACK가 유실되면 Sender는 실패로 판단하고 다시 메시지를 보냄.
처리 완료
→ ACK 유실
→ 재전송
→ 동일 메시지 다시 처리
따라서:
At-least-once
→ 메시지 유실 가능성을 줄임
→ 중복 처리 가능
알림 서비스라면 동일한 알림이 두 번 전달될 수 있음.
이상적인 목표는:
메시지 유실 없음
+
중복 없음
=
Exactly-once
이지만 네트워크에서 메시지나 ACK가 유실될 수 있으므로 네트워크 수준에서 exactly-once delivery를 완벽하게 보장하기 어려움.
따라서 실무에서는 중복 가능성을 받아들이고 애플리케이션 계층에서 이를 흡수함.
대표적인 방법은 두 가지임.
같은 연산을 여러 번 수행해도 최종 결과가 같도록 설계함.
상태 = 활성화
를 여러 번 수행해도 최종 상태는 동일함.
각 메시지에 고유한 Event ID를 부여하고 처리 이력을 확인함.
event_id = abc123
처음 들어온 경우:
처리 이력 없음
→ 처리
→ 완료 기록
같은 이벤트가 다시 들어온 경우:
이미 처리됨
→ 폐기
알림 발송은 같은 함수를 두 번 실행하면 실제 알림도 두 번 나갈 수 있으므로 자연스럽게 멱등한 연산이 아님.
따라서 알림 시스템에서는 Event ID 기반 중복 제거가 특히 중요함.
전체 흐름은 다음과 같음.
At-least-once
↓
중복 가능
↓
Event ID
↓
Deduplication 또는 Idempotency
↓
중복으로 인한 부작용 방지
즉 분산 알림 시스템에서는 중복 전달 자체를 완전히 없애려 하기보다, 재전송을 허용하고 Event ID와 중복 제거 또는 멱등 처리를 이용해 중복으로 인한 문제를 흡수하는 방식이 현실적임.
최종적인 알림 시스템 구조는 다음과 같음.

추가적으로 다음 기능을 포함함.
핵심은 단순히 알림을 보내는 것에 그치지 않고 다음 사항을 함께 고려하는 것임.