JD의 블로그

대규모 시스템 설계 기초 - 10장 (알림 시스템 설계) 본문

프로그래밍/시스템 디자인

대규모 시스템 설계 기초 - 10장 (알림 시스템 설계)

GDong 2021. 10. 15. 23:56

알림 시스템은 많은 프로그램에서 채택한 인기 있는 기능이다.

이 기능을 갖춘 애플리케이션 프로그램은 최신 뉴스, 제품 업데이트, 이벤트, 선물 등 고객에게 중요할 만한 정보를 비동기적으로 제공한다.

 

알림 시스템은 단순히 모바일 푸시 알림에만 한정되지 않고 SNS 메시지, 이메일, 그리고 푸시 알림 이렇게 3가지로 분류할 수 있다.

 

1단계 문제 이해 및 설계 범위 확정

1. 어떤 종류의 알림을 지원해야 하는지 물어보기

EX) 푸시 알림, SMS 메시지, 그리고 이메일이다.

 

2. 실시간 시스템이어야 하는지 물어보기

EX) soft real-time 시스템이라고 가정한다. 알림은 가능한 빨리 전달되어야 하지만 시스템에 높은 부하가 걸렸을 때 약간의 지연은 무방하다.

 

3. 어떤 종류의 단말을 지원해야 하는지 물어보기

EX) iOS 단말, 안드로이드 단말, 그리고 랩톱/데스크톱을 지원해야한다.

 

4. 사용자에게 보낼 알림은 누가 만들지 물어보기

EX) 클라이언트 애플리케이션 프로그램에서 만들 수 있으며, 서버 측에서 스케줄링 할 수도 있다.

 

5. 사용자가 알림을 받지 않도록 설정할 수 있는지 물어보기

EX) 해당 설정을 마친 사용자는 더 이상 알림을 받지 않는다.

 

6. 하루에 몇 건의 알림을 보낼 수 있어야 하는지 물어보기

EX) 천만 건의 모바일 푸시 알림, 백만 건의 SMS 메시지, 5백만 건의 이메일을 보낼 수 있어야한다.

 

2단계 개략적 설계안 제시 및 동의 구하기

여기서는 iOS 푸시 알림, 안드로이드 푸시 알림, SNS 메시지, 그리고 이메일을 지원하는 알림 시스템의 개략적 설계안을 살펴본다.

  • 알림 유형별 지원 방안
  • 연락처 정보 수집 절차
  • 알림 전송 및 수신 절차

 

알림 유형별 지원 방안

 

iOS 푸시 알림

iOS에서 푸시 알림을 보내기 위해서는 세 가지 컴포넌트가 필요하다.

  • 알림 제공자 : 알림 요청을 만들어 애플 푸시 알림 서비스(APNS: Apple Push Notification Service)로 보내는 주체. 알림 요청을 만들려면 알림 요청을 보내는데 필요한 교유 식별자인 단말 토큰과 알림 내용을 담은 JSON 딕셔너리인 페이로드가 필요하다.
    • 페이로드는 다음과 같은 형태다.
    • {
      	"aps": {
          		"alert": {
                  	"title": "Game Request",
                      "body": "Bob wants to play chess",
                      "action-loc-key": "PLAY"
                  },
                  "badge":5
              }   
      }
  • APNS : 애플이 제공하는 원격 서비스다. 푸시 알림을 iOS 장치로 보내는 역할을 한다.
  • iOS 단말 : 푸시 알림을 수신하는 사용자 단말이다.

 

안드로이드 푸시 알림

안드로이드 푸시 알림도 비슷한 절차로 전송한다. APNS 대신에 FCM(Firebase Cloud Messaging)을 사용한다는 점이 다르다.

 

SMS 메시지

SMS 메시지를 보낼 때는 보통 트윌리오(Twilio), 넥스모(Nexmo)와 같은 제3 사업자의 서비스를 많이 사용한다. 

 

이메일

많은 회사가 상용 이메일 서비스를 이용한다. 그중 유명한 서비스가 센드그리드(Sendgrid), 메일침프(Mailchimp)가 있다. 전송 성공률도 높고, 데이터 분석 서비스도 제공한다.

 

연락처 정보 수집 절차

알림을 보내려면 모바일 단말 토큰, 전화번호, 이메일 주소 등의 정보가 필요하다. 사용자가 우리 앱을 설치하거나 처음으로 계정을 등록하면 API 서버는 해당 사용자의 정보를 수집하여 데이터베이스에 저장한다. 

 

데이터베이스에 연락처 정보를 저장할 테이블 구조는 다음과 같다.

user  
user_id bigint
email varchar
country_code integer
phone_number integer
created_art timestamp

1-* 매핑

device  
id bigint
device_token varchar
user_id bigint
last_logged_in_at timestamp

이메일 주소와 전화번호는 user 테이블에 저장하고, 단말 토큰은 device 테이블에 저장한다. 한 사용자가 여러 단말을 가질 수 있고, 알림은 모든 단말에 전송되어야 한다는 점을 고려했다. 

 

 

알림 전송 및 수신 절차

서비스 1 ~ N까지는 알림 시스템에 요청을 하고 이를 APNS, FCM, SMS 서비스, 이메일 서비스, 그리고 제 3자 제공 서비스를 통해 단말기에 알림을 보낸다.

 

  • 1부터 N까지의 서비스 : 이 서비스 각각은 마이크로서비스 일 수도 있고, 크론잡일 수도 있고, 분산 시스템 컴포넌트일 수도 이다. 사용자에게 납기일을 알리고자 하는 과금 서비스, 배송 알림을 보내려는 쇼핑몰 웹사이트도 될 수 있다.
  • 알림 시스템 : 알림 시스템은 알림 전송/수신 처리의 핵심이다. 이 시스템은 서비스 1~N을 위한 API를 제공해야 하고, 제 3자 서비스에 전달할 알림 페이로드를 만들어 낼 수 있어야 한다.
  • 제3자 서비스 : 이 서비스들은 사용자에게 알림을 실제로 전달하는 역할을 한다. 제 3자 서비스와의 통합을 진행할 때 유의할 것은 확장성이다. 쉽게 새로운 서비스를 통합하거나 기존 서비스를 제거할 수 있어야 한다는 뜻이다. 또 하나 고려해야 할 것은 어떤 서비스는 다른 시장에서는 사용할 수 없을 수도 있다는 것이다. 가령 FCM은 중국에서는 사용할 수 없다. 따라서 중국에서는 제이푸시(Jpush), 푸시와이(PushY) 같은 서비스를 사용해야만 한다. 

이 설계의 문제

  • SPOF(Single-Point-Of-Failure) : 알림 서비스에 서버가 하나만 있으면 그 서버에 장애가 생겼을 경우 전체 서비스의 장애로 이어질 수 있다.
  • 규모 확장성 : 한 대 서비스로 푸시 알림에 관계된 모든 것을 처리하므로, 데이터베이스나 캐시 등 중요 컴포넌트의 규모를 개별적으로 늘릴 방법이 없다.
  • 성능 병목 : 알림을 처리하고 보내는 것은 자원을 많이 필요로 하는 작업일 수 있다. 예를 들어 HTML 페이지를 만들고 제3자 서비스의 응답을 기다리는 일은 시간이 많이 걸릴 가능성이 있는 작업이다. 따라서 모든 것을 한 서버로 처리하면 사용자 트래픽이 많이 몰리는 시간에는 시스템이 과부하 상태에 빠질 수 있다.

개선 후

  • 데이터베이스와 캐시를 알림 시스템의 주 서버에서 분리한다.
  • 알림 서버를 증설하고 자동으로 수평적 규모 확장이 이뤄질 수 있도록 한다.
  • 메시지 큐를 이용해 시스템 컴포넌트 사이의 강한 결합을 끊는다.

 

알림 서버의 역할

  • 알림 전송 API : 스팸 방지를 위해 보통 사내 서비스 또는 인증된 클라이언트만 이용 가능하다.
  • 알림 검증 : 이메일 주소, 전화번호 등에 대한 기본적 검증을 수행한다.
  • 데이터베이스 또는 캐시 질의 : 알림에 포함시킬 데이터를 가져오는 기능이다.
  • 알림 전송 : 알림 데이터를 메시지 큐에 넣는다. 본 설계의 경우 하나 이상의 메시지 큐를 사용하므로 알림을 병렬적으로 처리할 수 있다. 

다음은 이메일 형태의 알림을 보내는 데 사용하는 API 예제이다.

POST https://api.example.com/v/sms/send 

 

API 호출 시 전송할 데이터는 다음과 같다.

{
	"to": [
    	{
        	"user_id":12345
        }
    ],
    "from": {
    	"email": "from_address@example.com"
    },
    "subject": "Hello, World!",
    "content": [
    	{
        	"type": "text/plain",
            "value": "Hello, World!"
        }
    ]
}

캐시는 사용자 정보, 단말 정보, 알림 템플릿 등을 캐시한다.

데이터베이스는 사용자, 알림, 설정 등 다양한 정보를 저장한다.

메시지 큐는 시스템 컴포넌트 간 의존성을 제거하기 위해 사용한다. 대량의 알림이 전송되어야 하는 경우 버퍼 역할도 한다. 

작업 서버는 메시지 큐에서 전송할 알림을 꺼내서 제3자 서비스로 전달하는 역할을 담당하는 서버다.

 

알림이 전송되는 과정

1. API를 호출하여 알림 서버로 알림을 보낸다.

2. 알림 서버는 사용자 정보, 단말 토큰, 알림 설정 같은 메타데이터를 캐시나 데이터베이스에서 가져온다.

3. 알림 서버는 전송할 알림에 맞는 이벤트를 만들어서 해당 이벤트를 위한 큐에 넣는다.

4. 작업 서버는 메시지 큐에서 알림 이벤트를 꺼낸다.

5. 작업 서버는 알림을 제3자 서비스로 보낸다.

6. 제3자 서비스는 사용자 단말로 알림을 전송한다.

 

3단계 상세 설계

추가 요소

  • 안정성
  • 추가로 필요한 컴포넌트 및 고려사항 : 알림 템플릿, 알림 설정, 전송률 제한, 재시도 메커니즘, 보안, 큐에 보관된 알림에 대한 모니터링과 이벤트 추적

안정성

(1) 데이터 손실 방지

알림 전송 시스템의 가장 중요한 요구사항 가운데 하나는 어떤 상황에서도 알림이 소실되면 안된다는 것이다. 알림이 지연되거나 순번이 틀려도 괜찮지만, 사라지면 굉장히 곤란하다. 이 요구사항을 만족하려면 알림 시스템은 알림 데이터를 데이터베이스에 보관하고 재시도 메커니즘을 구현해야 한다. 한 가지 방법은 알림 로그를 데이터베이스에 유지하는 것이다.

 

(2)알림 중복 전송 방지

같은 알림이 여러 번 반복되는 것을 완전히 막는 것은 힘들다. 대부분의 알림이 한 번만 전송되겠지만 분산 시스템의 특성상 가끔은 같은 알림이 중복되어 전송되기도 한다. 그 빈도를 줄이려면 중복을 탐지하는 메커니즘을 도입하고 오류를 신중하게 처리해야 한다. 간단한 중복 방지 로직을 만드는 방법은 보내야 할 알림이 도착했을 때 이벤트 ID를 검사하여 이전에 본 적이 있는 이벤트인지 살피고 중복된 이벤트라면 버리고, 그렇지 않으면 알림을 배송한다. 

 

추가로 필요한 컴포넌트 및 고려사항

알림 템플릿

대형 알림 시스템은 하루에도 수백만 건 이상의 알림을 처리한다. 그런데 그 알림 메시지 대부분은 형식이 비슷하다. 알림 템플릿은 이런 유사성을 고려하여, 알림 메시지의 모든 부분을 처음부터 다시 만들 필요가 없도록 만들어준다. 

 

알림 설정

사용자는 이미 너무 많은 알림을 받고 있어서 쉽게 피곤함을 느낀다. 따라서 많은 웹사이트와 앱에서는 사용자가 알림 설정을 상세히 조정할 수 있도록 한다. 이 정보는 알림 설정 테이블에 보관하며, opt_in 처럼 해당 채널로 알림을 받을 것인지 여부를 boolean 값으로 표시해준다.

 

전송률 제한

사용자에게 너무 많은 알림을 보내지 않도록 하는 한 가지 방법은, 한 사용자가 받을 수 있는 알림의 빈도를 제한하는 것이다. 이것은 굉장히 중요한데 왜냐하면 알림을 너무 많이 보내기 시작하면 사용자가 알림 기능을 아예 꺼 버릴 수도 있기 때문이다.

 

재시도 방법

제3자 서비스가 알림 전송에 실패하면, 해당 알림을 재시도 전용 큐에 넣는다. 같은 문제가 계속해서 발생하면 개발자에게 통지한다. 

 

푸시 알림과 보안

앱의 경우 알림 전송 API는 appKey와 appSecret을 사용하여 보안을 유지한다. 따라서 인증된 혹은 승인된 클라이언트만 해당 API를 사용하여 알림을 보낼 수 있다. 

 

큐 모니터링

알림 시스템을 모니터링 할 때 중요한 메트릭 하나는 큐에 쌓인 알림의 개수이다. 이 수가 너무 크면 작업 서버들이 이벤트를 빠르게 처리하고 있지 못하다는 뜻이고. 그런 경우에 작업 서버를 증설하는 게 바람직할 것이다. 

 

이벤트 추적

알림 확인율, 클릭율, 실제 앱 사용으로 이어지는 비율 같은 메트릭은 사용자를 이해하는데 중요하다. 

따라서 보통 알림 시스템을 만들면 데이터 분석 서비스와도 통합해야한다.