Notification System Architecture

System Design Interview의 ‘CHAPTER 10: DESIGN A NOTIFICATION SYSTEM’을 읽고 스터디를 진행하면서 알게 된 것들을 정리해보려 한다.

챕터 내용 자체는 전형적인 대규모 시스템 설계와 크게 다르지 않았다. 전송 서버를 역할별로 분리하고, 큐와 워커 구조로 처리하며, 중복 발송 방지와 캐싱을 위해 Redis를 붙이는 구성이다. 읽는 데 어렵지는 않았다.

그런데 스터디 중 한 분이 흥미로운 질문을 던졌다.

“푸시 알림을 보낼 때 APNS/FCM이 고객 디바이스로 전달하는 과정에서 에러가 발생한다면 어떻게 해결할 것인가?”

이 질문을 계기로 좀 더 깊이 생각해보게 됐다.

푸시 알림 전송 플로우

모바일 푸시 알림이 실제로 어떤 경로로 전달되는지 흐름을 알아보자.

sequenceDiagram
    participant S as 서비스 서버
    participant Q as 메시지 큐
    participant W as 푸시 워커
    participant A as APNS / FCM
    participant D as 모바일 기기
    participant U as 유저

    S->>Q: 알림 이벤트 발행
    Q->>W: 메시지 consume
    W->>A: 푸시 알림 요청
    A-->>W: 응답 (성공 / 요청 오류)

    note over A,D: 블랙박스 구간

    A-)D: 전달 시도

    alt 전달 성공
        D-)U: 푸시 알림 표시
    else 전달 실패 (오프라인 / 에러)
        note over D: 서버에 별도 콜백 없음
    end

이 과정에서 문제는 서버는 APNS/FCM으로부터 “요청 잘 받았어” 혹은 “요청이 잘못됐어”라는 응답만 받을 뿐, 실제로 디바이스까지 전달됐는지는 알 수 없다.

알림의 종류 생각하기

먼저 푸시 알림의 특성을 생각해봤다.

우리가 받는 푸시 알림의 종류를 떠올려보면:

  • 듀오링고 학습 알림
  • 배달앱 배달 현황 알림
  • 메시지 알림
  • 커머스의 세일, 신제품, 배송 알림

이것들의 공통점은 유저가 즉시 읽어주길 바라는 목적으로 보내는 알림이라는 점이다. 유저가 알림을 눌러 앱에 진입하고, 거기서 정보를 확인하는 행동으로 이어져야 비로소 효용이 생긴다.

행동 기반 재전송 전략

이 특성에서 출발하면 하나의 아이디어가 나온다.

푸시 알림이 APNS/FCM 단계에서 누락되면, 유저는 한 시간이고 두 시간이고 앱에 접속하지 않는다. 반대로 말하면, 알림을 보낸 후 일정 시간 내에 유저의 앱 접속 시그널이 없다면 전달이 실패했을 가능성이 있다고 볼 수 있다.

따라서 다음과 같은 흐름을 생각해볼 수 있다:

  1. 푸시 알림 발송
  2. 일정 시간 내 유저의 앱 접속 여부 확인 (토큰 갱신, API 호출 등의 시그널 활용)
  3. 접속 시그널이 없으면 재전송

반박: 폰을 오래 꺼놨다가 켰을 때

당연히 반론이 있다. 유저가 폰을 오랫동안 꺼놓다가 켰을 때, 쌓인 재전송 알림이 한꺼번에 날아올 수 있다.

이건 retry와 backoff 정책을 잘 설계하면 어느 정도 완화할 수 있지만, 근본적으로 완벽한 해결책은 아니다. 결국 trade-off가 존재한다.

iOS에서 기존에 지원하는 기능이 있을까?

스터디원 중 한 분이 iOS에서 이미 관련 기능을 지원한다고 언급했고, UNNotificationServiceExtension을 예시로 들어주셨다.

다만 내가 스터디 도중 제대로 듣지 못한 탓에 잘못 찾아봤을 수도 있다. 해당 문서를 읽어보니, 이 기능은 APNS가 iOS 단말기까지 전달을 완료한 이후의 처리(알림 내용 수정, 미디어 첨부 등)에 관한 것으로 보여서, APNS 전달 실패 대응과는 결이 다른 것 같다.

그런데, 푸시 알림이 그렇게 중요한가?

솔직히 한번쯤 생각해볼 만한 질문이다. 재난문자도 아닌데, 푸시 알림 하나가 누락됐다고 비즈니스나 유저에게 치명적인 타격을 주는 케이스가 과연 얼마나 될까?

위에서 언급한 대부분의 알림들은 하루에 여러 번 보내는 성격이기 때문에, 한 번 누락된다고 크게 문제가 되진 않는다.

만약 정말 중요한 알림이라면? 어차피 그런 서비스라면 유저도 앱에 자주 접속할 것이고, 그렇다면 알림 누락의 임팩트는 더 줄어든다.

알림함(Inbox)으로 보완하기

알림 전달 신뢰성을 높이려는 노력과 별개로, 앱 내 알림함(종 모양 아이콘 → 알림 리스트)을 두는 것만으로도 많은 문제를 해결할 수 있다.

푸시를 놓쳤더라도 앱에 들어오면 확인할 수 있으니, 사용자 경험 측면에서 훨씬 안전하다. 오히려 알림이 잘 안 오는 덕에 앱을 직접 열어보는 리텐션이 올라갈 수도 있지 않을까 ㅋㅋ

카카오톡도 가끔 알림이 안 올 때가 있으니..

결론

완벽한 해결책은 없다(모든 서비스 아키텍쳐 이론 아닐까…), 다만 현실적으로 접근법은 아래라고 생각한다.

  • 앱 접속 시그널 기반 재전송: 알림 발송 후 일정 시간 내 접속이 없으면 retry
  • Retry + Backoff 정책: 재전송 횟수와 간격을 적절히 조절해 스팸 방지
  • 앱 내 알림함: 푸시 신뢰성에 의존하지 않는 보완 수단