문제 이해 및 설계 범위 확정
어떤 종류의 알림을 지원해야 하나요?
실시간 시스템이어야 하나요?
- 연성 실시간 시스템 (soft real-time)이라 가정합니다. 가능한 빨리 알림이 전달되어야 하지만 시스템에 높은 부하가 걸렸을때 약간의 지연은 무방하다.
어떤 종류의 단말을 지원해야 하나요?
- ios, android, laptop, desktop
사용자에게 보낼 알림은 누가 만들 수 있나요?
- client 애플리케이션 또는 서버 측 스케줄링이 가능함
사용자가 알림을 받지 않도록 (opt-out)설정 가능?
- 넵 해당 설정시 사용자는 더 이상 알림을 받지 않습니다.
하루에 몇 건의 알림을 보낼 수 있나요?
- 천만건 모바일 푸시, 백만건 sms, 오백만건의 이메일
개략적 설계안 제시 및 동의 구하기
알림 유형별 지원 방안
IOS 푸시 알림
알림 제공자 -> APNs -> IOS 단말
-
알림 제공자: 알림 요청을 만들어 APNS (Apple Push Notification Service)로 보내는 주체이다. 요청을 만들려면 아래와 같은 데이터가 필요하다.
- 단말토큰: 알림 요청을 보내는 데 필요한 고유 식별자
- 페이로드: 알림 내용을 담은 JOSN 객체
{
"aps" {
"alert": {
"title": "Game Request",
"body": "Bob wants to play"
"action-lock-key": "PLAY"
},
"badge": 5
}
}
-
APNS: 애플이 제공하는 원격 서비스. 푸시 알림을 IOS 장치로 보내는 역할
-
IOS 단말: 푸시 알림을 수신하는 사용자 단말
Android 푸시 알림
알림 제공자 -> FCM -> 안드로이드 단말
- IOS다른 점은 FCM (firebase cloud messaging)을 이용한다는 점
SMS 메시지
알림 제공자 -> SMS 서비스 (Twillo, Nexmo) -> SMS 수신 단말
이메일
알림 제공자 -> 이메일 서비스 -> 이메일 수신 단말
- 책에는 이메일 수신 단말 즉 휴대폰 단말 처럼 특정 단말로 수신되는건 처럼 표현되었지만 실제로는 수신측 메일 서버에 보관 되었다가 수신자가 해당 메일 서버에 수신된 자기 메일을 웹이나 어플리케이션으로 확인 하는 것이다.
연락처 정보 수집 절차
- 알림을 보내기 위해서는 단말 토큰, 전화번호, 이메일 주소 등의 정보가 필요하다.
- 처음 계정 등록 또는 앱 설치시 사용자 정보를 수집하여 데이터 베이스에 저장한다.
USER |
---|
user_id (bigint) |
email (varchar) |
country_code (integer) |
phone_number (integer) |
created_at (timestamp) |
DEVICE |
---|
id (bigint) |
device_token (varchar) |
user_id (bigint) |
last_logged_in_at (timestamp) |
- 테이블 설계는 대략적으로 위에 같인 진행한다. 사용자가 여러 단말을 가질 수 있기 모든 단말에 전송되어야 한다는 점을 고려하여 device테이블을 분리한다.
알림 전송 및 수신 절차
개략적 설계 초안
- 1부터 N까지 서비스: 마이크로 서비스, 크론, 분산 시스템 컴포넌트등 다양한 서비스가 될 수 있다.
- 알림 시스템: 1개라고 가정한다. 외부 서비스에 알림 전송을 API를 제공하고 제3자 서비스에 전달할 알림 페이로드를 만들어 낼 수 있어야 한다.
- 제3자 서비스: 실제 사용자에게 알림을 전달하는 역할을 담당한다. 제3자 서비스와 통합을 진행할때 유의할 것은 확장성이다. 쉽게 새로운 서비스를 통합 또는 제거할 수 있어야 한다. 그리고 특정 서비스는 다른 시장에서 사용할 수 없을 수 있다. 예) 중국에서는 FCM 사용불가 Jpush, PushY를 이용해야 함.
- 단말: 사용자는 자기 단말에서 알림을 수신
문제점
- SPOF: 알림 서비스가 한대여서 장애시 전체 장애로 이어짐
- 규모 확장성: 서버 한대로 푸시 알림과 관계된 모든 작업을 처리함으로, 데이터 베이스나 캐시 등 중요 컴포넌트의 규모를 개별적으로 늘릴수 없다.
- 성능 병목: 알림을 처리하고 보내는건 많은 자원이 필요하다. 예를 들어 HTML 페이지를 만들고 제 3자 서비스의 응답을 기다리는일은 시간이 많이 걸릴 가능성이 있다. 따라서 한 서버에서 처리하면 트래픽이 몰리는 시간에 과부하 여지가 있다.
개략적 설계 (개선안)
초안의 문제점을 다음과 같이 개선한다.
- 데이터베이스와 캐시를 알림 시스템의 주 서버에서 분리하여 알림 시스템과, 데이터 베이스, 캐시를 개별적으로 확장할 수 있게 한다.
- 알림 서버를 수평적 규모 확장이 이루어질 수 있도록 한다.
- 메시지 큐를 이용해 시스템 컴포넌트 사이의 강결합을 끊는다. (재시도, 여러 알림 병렬처리, 버퍼 역할 등 추가적인 장점이 있는듯 하다.)
- 1부터 N까지 서비스: API로 알림을 보낼 서비스
- 알림 서버
- 알림 전송 API: 스팸 방지를 위해 인증된 클라이언트만 이용하게 제한
- 알림 검증: 이메일 주소, 전화번호 등에 대한 기본 검증
- 데이터 베이스 또는 캐시 질의: 알림에 필요한 데이터 가져오기
- 알림 전송: 알림 데이터를 메시지 큐에 넣는다.
- 캐시: 사용자 정보, 단말 정보, 알림 템플릿 등을 캐시한다.
- DB: 사용자, 알림, 설정 등 다양한 정보 저장
- 메시지 큐
- 시스템 컴포넌트 간 의존성 제거 (외부 서비스를 알림 서버가 직접 호출하지 않음으로 의존하지 않게 된다)
- 다량의 알림이 전송되어야 하는 경우 버펴 역할
- 알림 종류별로 메시지 큐가 분리되어 있어 하나의 장애가 다른 알림에 영향을 주지 않는다.
- 작업 서버: 메시지 큐에서 알림을 꺼내 제3자 서비스로 전달
- 제3자 서비스: 이전과 동일
- 단말: 사용자는 이전과 동일
알림 전송 프로세스
- API를 호출하여 알림 서버로 알림을 보낸다.
- 알림 서버는 캐시나 데이터 베이스 질의를 통해 메타 데이터(사용자 정보, 단말토큰, 알림설정)를 가져온다.
- 알림 서버는 전송할 알림에 맞는 이벤트를 만들어 적절한 큐에 넣는다.
- 작업 서버는 메시지 큐에서 알림 이벤트를 꺼낸다.
- 작업 서버는 알림을 제3자 서비스로 보낸다.
- 제3자 서비스는 사용자 단말로 알림을 전송한다.
상세 설계
추가적으로 자세히 살펴볼 사항들
안정성
데이터 손실 방지
- 알림이 지연되거나 순서가 틀려도 무방하지만 사라지면 안된다.
- 별도로 알림 로그와 같은 데이터 베이스를 두고 재시도 메커니즘을 구현한는 것도 방법이다.
알림 중복 전송 방지
- 분산 시스템 특성상 같은 알림이 여러 번 반복되는 것을 100% 막는것은 불가능하다.
- 중복 발송 빈도를 줄이기 위해서 아래와 같은 프로세스를 추가 할 수 있다.
- 보내야 할 알림이 도착하면 이벤트 ID를 검사하여 중복 발송이면 버리고 아니면 발송한다.
추가로 필요한 컴포넌트 및 고려사항
알림 템플릿
- 알림 메시지 대부분 형식이 비슷하다.
- 알림 템플릿 인자, 스타일, 링크만 조정하여 사전에 지정한 형식에 맞춰 알림을 만들어내는 틀이다.
- 형식을 일관성 있게 유지할 수 있고, 오류 가능성 알림 작성에 드는 시간도 줄일 수 있다.
알림 설정
- 사용자는 이미 너무 많은 알림을 받고 있어 피로함을 늒니다.
- 알림 설정을 상세히 조정할 수 있도록 하고 이 정보를 테이블에 보관한다.
- 알림 발송전 알림이 켜져 있는지 반드시 확인한다.
전송률 제한
- 한 사용자가 너무 많은 알림을 보내지 않도록 빈도를 제한 한다.
재시도 방법
- 알림 전송에 실패하면 재시도 전용 큐에 넣는다
- 같은 문제가 지속적을 발생하면 개발자에게 통지한다.
푸시 알림과 보안
- ios, 안드로이드 앱의 경우 알림 전송 api는 appKey와 appScret을 사용하여 보완을 유지한다.
- 인증도니 혹은 승인된 클라이언트만 api로 알림 발송 가능
큐 모니터링
- 큐에 쌓인 알림의 개수 모니터링이 중요하다.
- 쌓인 알림의 개수가 너무 크면 서버가 빠르게 처리하고 있지 못하다는 뜻이다.
이벤트 추적
- 알림 확인율, 클릭율, 실제 앱 사용으로 이어지는 비율 같은 메트릭은 사용자를 이해하는데 중요하다.
- 고로 데이터 분석 서비스와 알림 시스템을 통합해야 한다.
수정된 설계안
추가된 컴포넌트
- 알림 서버에 인증과 전송률 제한 기능을 추가 하였다.
- 전송 실패에 대응하기 위한 재시도 기능 추가 (지정된 횟수만 큼만 재시도)
- 전송 템플릿을 사용하여 알림 생성 과정을 단순화 하고 알림 내용을 일관성 있게 유지한다.
- 모니터링과 추적 시스템을 이용하여 시스템 상태을 확인하고 시스템을 개선하기 쉽도록 하였다.
마무리
- 알림은 중요 정보를 알려준다는 점에 있어서 꼭 필요한 기능이다.
- 규모 확장이 쉽고, 결합도 낮은 그리고 다양한 정보 전달 방식을 지원하는 알림 시스템을 구축해 보았다.
- 특히 아래 주제에 집중하였다.
- 안정성: 전송 실패율을 낮추기 위해 재시도 매커니즘 도입
- 보안: 인증된 클라이언트만 보낼 수 있도록 appKey, appSecret등 메커니즘 이용
- 이번트 추적 및 모니터링: 알림 전반적인 과정 추적 및 시스템 상태 모니터링 할 수 있도록 구축
- 사용자 설정: 사용자가 알림 수신 설정을 조절할 수 있는 구조
- 전송률 제한: 사용자에게 보내는 알림 빈도를 제한할 수 있는 구조
추가
- 이메일 수신 프로세스
- 알림 서비스 재시도 메커니즘 상세
- 왜 exactly once는 실현 불가능 인가?
- 요점은 sender가 reciever에게 메시지를 보낸후 receiver로 부터 ack를 못 받은 경우 우리는 한번 더 보내서 ack를 받거나 아니면 안보내는 선택지 말고는 없다. ack를 못 받은 경우 실제로 reciever가 못받었을수도 있으나 받았는데 ack를 보내는 도중 네트워크 문제가 발생 했을수 있다.
- 그래서 주로 at-least-once + 멱등한 api 또는 중복 제거 처리
- 링크1
- 링크2
- 예시 (완벽히 똑같지는 않지만 이런 network timeout 이슈가 있을 수 있으나 무조건 한번만 처리되어야 하는 경우 예시)
- 모든 네트워크 통신은 그럼 동일하지 않을까? 메시지 큐 뿐만아니라??
- 카프카로 메시지 던질떄도 동일하지 않을까 내가 보냈는데 응답이 없으면 다시 보낼지도??