////
Search
📤

8장. 분산 이메일 서비스

지메일, 아웃룩, 야후메일과 같은 대규모 이메일 서비스를 설계해보자.
지메일의 활성 사용자는 2020년 기준으로 18억 명 이상이고 아웃룩은 4억 명 이상의 사용자를 보유하고 있다.

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

비기능 요구사항

안정성 : 이메일 데이터는 소실되어서는 안 된다.
가용성 : 이메일과 사용자 데이터를 여러 노드에 자동으로 복제하여 가용성을 보장해야 한다.
확장성 : 사용자가 늘어나도 감당할 수 있어야 한다. 사용자나 이메일이 많아져도 시스템 성능은 저하되지 않아야 한다.
유연성과 확장성 : 새 컴포넌트를 더하여 쉽게 기능을 추가하고 성능을 개선할 수 있는 유연하고 확장성 높은 시스템이어야 한다.

개략적인 규모 추정

10억 명의 사용자
한 사람이 하루에 송신하는 평균 이메일 수는 10건으로 가정, QPS는 대략 10억 * 10 / 86400 = 대략 100,000이다.
한 사람이 하루에 수신하는 평균 이메일 수는 40건으로 가정, 이메일 하나 당 메타데이터는 대략 50KB, 첨부 파일은 가정하지 않는다.
1년간 메타데이터를 유지하기 위한 스토리지 요구사항은 10억 명 * 40건 * 365일 * 50KB이므로 대략 730PB에 달한다.
첨부 파일을 포함하는 이메일의 비율은 20%이며 첨부 파일의 평균 크기는 대략 500KB라고 가정한다.
1년간 첨부 파일을 보관하는데 필요한 저장 용량은 10억 명 * 40건 * 365일 * 20% * 500KB이므로 대략 1460 PB에 달한다.

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

이메일 101

이메일 프로토콜
SMTP, POP, IMAP, HTTPS
도메인 이름 서비스
DNS 서버는 수신자 도메인의 메일 교환기 레코드 검색에 사용된다.
첨부파일
이메일 첨부 파일은 이메일 메시지와 함께 전송되며 일반적으로 Base64 인코딩을 사용한다.
일반적으로 첨부 파일에는 크기 제한이 있다. 보통 시중의 제품들은 20 ~ 25MB로 제한하고 있다.
전통적 메일 서버
전통적 메일 서버 아키텍처
1.
사용자가 아웃룩 등의 클라이언트에 로그인하여 이메일을 작성하고 보내기 버튼을 누른다. 이메일은 아웃룩 메일 서버로 전송된다. 아웃룩의 경우 메일 서버 SMTP 프로토콜을 사용한다.
2.
아웃룩 메일 서버는 DNS 질의를 통해 수신자 SMTP 서버 주소를 찾는다. 주소를 알고 나면 해당 메일 서버로 이메일을 보낸다.
3.
지메일 서버는 이메일을 저장하고 수신자가 읽어갈 수 있도록 한다.
4.
수신자가 지메일에 로그인하면 지메일 클라이언트는 IMAP/POP 서버를 통해 이메일을 가져온다.
저장소
전통적인 메일 서버는 이메일을 파일 시스템의 디렉터리에 저장한다. 이때 각각의 이메일은 고유한 이름을 가진 별도 파일로 보관한다.
각 사용자의 설정 데이터와 메일함은 사용자 디렉터리에 보관한다. 해당 목적으로 Maildir이라는 이름의 디렉터리가 널리 사용된다.

분산 메일 서버

분산 메일 서버는 현대적 사용 패턴을 지원하고 확장성과 안정성 문제를 해결한다.
이메일 API
메시지 전송, 모든 폴더 반환, 모든 메시지 반환, 특정 메시지에 대한 모든 정보 반환
분산 메일 서버 아키텍처
웹메일 : 사용자는 웹브라우저를 사용해 메일을 받고 보낸다.
웹서버 : 웹 서버는 사용자가 이용하는 요청/응답 서비스로 로그인 가입, 사용자 프로필에 대한 관리 기능을 제공한다.
실시간 서버 : 새로운 이메일 내역을 클라이언트에 실시간으로 전달하는 역할을 담당한다.
메타데이터 데이터베이스 : 이메일 제목, 본문, 발신인, 수신인 등의 메타데이터를 저장하는 데이터베이스다.
첨부 파일 저장소 : 아마존 S3와 같은 객체 저장소를 사용한다.
분산 캐시 : 최근에 수신된 이메일은 자주 읽을 가능성이 높으므로 메모리에 캐시해두도록 하면 성능을 향상시킬 수 있다.
검색 저장소 : 분산 문서 저장소의 형태로 고속 텍스트 검색을 지원하는 역 인덱스를 자료 구조로 사용한다.

이메일 전송 절차

1.
사용자가 웹메일 환경에서 메일을 작성한 다음 전송 버튼을 누른다. 요청은 로드밸런서로 전송된다.
2.
로드밸런서는 처리율 제한 한도를 넘지 않는 선에서 요청을 웹 서버로 전달한다.
3.
웹 서버는 다음 역할을 담당한다.
기본적인 이메일 검증 : 이메일 크기 한도처럼 사전에 미리 정의된 규칙에 의거하여 이메일을 검사한다.
수신자 이메일 주소 도메인이 송신자 이메일 주소 도메인과 같은지 검사 : 같다면 이메일의 스팸 여부와 바이러스 여부를 검사한다.
4.
메시지 큐
기본적인 검증을 통과한 이메일은 외부 전송 큐로 전달된다.
실패한 큐는 에러 큐에 보관한다.
5.
외부 전송 담당 SMTP 작업 프로세스는 외부 전송 큐에서 메시지를 꺼내어 이메일의 스팸 및 바이러스 감염 여부를 확인한다.
6.
검증 절차를 통과한 이메일은 저장소 계층 내의 보낸 편지함에 저장된다.
7.
외부 전송 담당 SMTP 작업 프로세스가 수신자의 메일 서버로 메일을 전송한다.

이메일 수신 절차

1.
이메일이 SMTP 로드밸런서에 도착한다.
2.
로드밸런서는 트래픽을 여러 SMTP 서버로 분산한다. SMTP 연결에는 이메일 수락 정책을 구성하여 유효하지 않은 이메일은 반송하도록 하여 불필요한 이메일 처리를 방지할 수 있다.
3.
이메일의 첨부 파일이 큐에 들어가기 너무 큰 경우엔 첨부 파일 저장소에 보관한다.
4.
이메일을 수신 이메일 큐에 넣는다. 이 큐는 메일 처리 작업 프로세스와 SMTP 서버 간의 결합도를 낮춰, 독립적인 규모 확장을 가능케 한다.
5.
메일 처리 작업 프로세스는 스팸 메일을 걸러내고 바이러스를 차단하는 등의 다양한 역할을 수행한다.
6.
이메일을 메일 저장소, 캐시, 객체 저장소 등에 보관한다.
7.
수신자가 온라인 상태인 경우, 이메일을 실시간 서버로 전달한다.
8.
실시간 서버는 수신자 클라이언트가 새 이메일을 실시간으로 받을 수 있도록하는 웹소켓 서버다.
9.
오프라인 상태 사용자의 이메일은 저장소 계층에 보관한다. 해당 사용자가 온라인이 되면 웹메일 클라이언트는 웹 서버에 RESTful API를 통해 연결한다.
10.
웹 서버는 새로운 이메일을 저장소 계층에서 가져와 클라이언트에 반환한다.

3단계: 상세 설계

메타데이터 데이터베이스

이메일 메타데이터의 특성
이메일의 헤더는 일반적으로 작고, 빈번하게 사용된다.
이메일 본문의 크기는 사이즈가 다양하지만 사용 빈도는 낮다. 일반적으로 사용자는 이메일을 한 번만 읽는다.
이메일 가져오기, 읽은 메일로 표시, 검색 등의 이메일 관련 작업은 사용자 별로 격리되어 수행되어야 한다.
데이터의 신선도는 데이터 사용 패턴에 영향을 미친다. 사용자는 보통 최근 메일만 읽는데, 만들어진 16일 이하 데이터에 발생하는 읽기 질의 비율은 82%에 해당한다.
데이터의 높은 안정성이 보장되어야 한다. 데이터 손실은 용납되지 않는다.
올바른 데이터베이스의 선정
관계형 데이터베이스
관계형 데이터베이스를 이메일 서비스의 데이터베이스로 선택하는 주된 동기는 이메일을 효율적으로 검색할 수 있기 때문이다.
그러나 데이터 크기가 작을 때 적합하다. 그렇지 않은 경우, 디스크 I/O가 많이 발생하기 때문에 적합하지 않다.
분산 객체 저장소
아마존 S3와 같은 객체 저장소에 그대로 보관하는 방법이다. 하지만 읽음 표시, 키워드 검색 등의 기능을 구현하기엔 적합하지 않다.
NoSQL 데이터베이스
지메일은 구글 빅테이블을 저장소로 사용한다. 따라서 충분히 실현 가능한 방안이나 오픈소스로 공개되어 있지 않다.
이메일 서비스에 이상적으로 부합하는 데이터베이스는 없다고 봐도 무방하다. 대형 이메일 서비스 업체는 대체로 독자적인 데이터베이스를 만들어 사용한다.
직접 구현할 수는 없으나 다음 조건들을 충족해야 한다는 점은 면접 때 설명할 수 있을 것이다.
어떤 단일 칼럼의 크기는 1~10MB 정도로 클 가능성이 있다.
강력한 데이터 일관성이 보장되어야 한다.
디스크 I/O가 크지 않도록 설계되어야 한다.
가용성이 아주 높아야 하고 어느정도 장애 감내성을 지원해야 한다.
증분 백업이 쉬워야 한다.
데이터 모델
데이터를 저장하는 한 가지 방법은 user_id를 키로 사용하여 특정한 사용자의 데이터를 항상 같은 샤드에 보관하는 것이다.
기본 키는 클러스터 키와 파티션 키 두 개로 구성된다.
클러스터 키는 같은 파티션에 속한 데이터를 정렬하는 구실을 한다.
이를 토대로 다음과 같은 질의를 개략적으로 설계해보자.
특정 사용자의 모든 폴더 질의
파티션 키는 user_id이므로 특정 사용자의 모든 폴더는 같은 파티션 안에 존재하므로 이를 만족한다.
특정 폴더에 속한 모든 이메일 표시
사용자가 자기 메일 폴더를 열면 가장 최근 이메일부터 오래된 것까지 정렬되어 표시된다.
같은 폴더를 질의하려면 folder_id까지 사용하는 복합 키를 사용해야 한다.
이메일 생성/삭제/수신
이메일 상세 정보를 가져오는 것은 email_id를 사용해 질의한다.
읽은 또는 읽지 않은 모든 메일
관계형의 경우, 단순히 WHERE 절에 조건을 추가해주면 되나 NoSQL의 경우, 마땅치 않다, 이 경우 테이블을 둘로 나눠 문제를 해결해볼 수 있다.
이메일 묶음 가져오기
특정 이메일에 대한 답장의 연쇄를 가져오는 기능이다.
전통적으로 이 기능은 JWZ 같은 알고리즘을 통해 구현된다.
보통 이메일 헤더에는 아이디, 어떤 메시지에 대한 답장인지 나타내는 식별자, 해당 메일과 관련된 식별자 목록이 존재하는데, 이 필드들을 통해 모든 메시지를 재구성한다.
일관성 문제
모든 메일함은 반드시 하나의 주 사본을 통해 서비스된다고 가정해야 한다.
이 경우, 장애가 발생하면 클라이언트는 다른 사본을 통해 주 사본이 복원되기까지 동기화 작업을 할 수 없다.
이는 데이터 일관성을 위해 어느정도 가용성을 희생하는 선택이다.

검색

이메일 검색은 보통 제목이나 본문에 특정 키워드가 포함되어있는지를 찾는 것을 의미한다.
고급 기능에는 발신인, 읽음 여부 등의 메일 속성에 따른 필터링 기능이 추가된다.
검색 기능을 제공하려면 이메일마다 색인 작업을 수행해야 하는데, 반면 검색은 사용자가 검색 버튼을 누를 때만 수행된다.
따라서 이메일 시스템의 검색 기능은 쓰기 연산이 읽기 연산보다 훨씬 많이 발생한다.
1.
엘라스틱 서치
엘라스틱 서치를 사용하는 경우, 이메일 검색에 필요한 텍스트 기반 검색을 잘 지원한다.
다만 주 이메일 저장소와 동기화를 맞추는 것이 까다롭다. 이 부분은 CDC 동기화같은걸 고려해보면 좋을듯
2.
맞춤형 검색 솔루션
대규모 이메일 사업자는 보통 본인들의 고유한 요구사항을 만족시키기 위해 검색 엔진을 자체적으로 개발해 사용한다.
소규모의 경우 엘라스틱 서치를 선택하는 것이 좋은 선택지다.

전송 가능성

이메일의 전송 가능성을 높이기 위해선 다음과 같은 요소들을 고려해야 한다.
전요 IP : 이메일을 보낼 때는 전용 IP를 사용하자. 대부분의 이메일 서비스는 아무 이력이 없는 새로운 IP 주소에서 온 메일을 무시한다.
범주화 : 범주가 다른 이메일은 다른 IP 주소를 통해 보내라.
발신인 평판 : 새로운 이메일 서버의 IP 주소는 사용 빈도를 서서 히올리는 것이 좋다. 아마존 SES에 따르면 새로운 IP 주소를 메일 발송에 아무 문제 없이 사용하게 되려면 2~6주 정도의 기간이 걸린다.
스팸 발송자의 신속한 차단 : 스팸을 발송하는 사용자는 이메일 서버에서 신속하게 차단하는 것이 평판에 도움이 된다.
피드백 처리 : ISP 측에서 메일에 대한 피드백을 쉽게 받아 처리할 수 있는 경로를 만드는 것이 중요하다. 피드백의 경우 다음과 같은 사례들이 있으며 각각 요청을 별도의 큐로 받아 관리하는 것이 좋다.
경성 반송 : 수신인의 이메일 주소가 올바르지 않아 ISP가 전달을 거부한 경우
연성 반송 : ISP 측의 이메일 처리 자원 부족 등으로 인해 일시적으로 이메일을 전달할 수 없는 경우
불만 신고 : 수신인이 스팸으로 신고 버튼을 누르는 경우

규모 확장성

각 사용자의 데이터 접근 패턴이 다른 사용자에 의해 영향을 받지 않으므로 시스템의 대부분 컴포넌트는 수평적으로 규모 확장이 가능할 것이다.
가용성을 향상시키기 위해서는 데이터를 여러 데이터 센터에 다중화하는 것이 필수적이다. 사용자는 네트워크 토폴로지 측면에서 자신과 물리적으로 가까운 메일 서버와 통신하기 때문이다.

4단계: 마무리

다음은 추가적으로 살펴보면 좋을 주제들이다.
결함 내성 : 시스템의 다양한 부분에서 문제가 발생할 수 있다. 노드 장애, 네트워크 문제, 이벤트 전달 지연 등의 문제들을 어떻게 다뤄볼 것인가 대해 고민해보면 좋다.
규정 준수 : 이메일 서비스는 전 세계를 대상으로 서비스하며 각 나라마다 준수해야 할 법규를 어떻게 지원할 것인가에 대해 고민해보면 좋다.
보안 : 이메일 보안은 중요하다. 지메일의 경우 피싱 방지, 안전 브라우징, 사전 경고, 계정 안전, 기밀 모드 등의 다양한 보안 기능들을 지원한다.
최적화 : 때로는 같은 이메일이 여러 수신자에게 전송되므로 똑같은 첨부 파일이 S3의 공간을 낭비할 수 있다. 이런 부분들을 최적화해볼 수 있을 것이다.