[시스템 디자인] 08. 분산 이메일 서비스
[시스템 디자인] 08. 분산 이메일 서비스
시스템 디자인 시리즈의 글입니다.
대규모 이메일 서비스를 설계해보자
1. 문제 이해
기능 요구사항
- 이메일 발송/수신
- 모든 이메일 가져오기
- 읽음 여부에 따른 이메일 필터링
- 제목, 발신인, 메일 내용에 따른 검색기능
- 스팸 및 바이러스 방지 기능
비기능 요구사항
- 안정성 : 이메일 데이터는 소실되면 안됨
- 가용성 : 데이터가 여러 노드에 복제되어있어서 가용성이 보장되어야함. 부분장애 발생시에도 시스템은 동작해야함.
- 확장성 : 사용자 수가 늘어나도 감당할 수있어야함.
- 유연성과 확장성 : 쉽게 기능을 추가하고 성능을 개선할 수 있어야함.
개략적인 규모 추정
- 10억명(=1B) 사용자
- 1인 평균 송신 10건, 수신 40건이라 가정
- QPS : 10\^9 * 10 / 10\^5 = 10\^5
- 1년간 메타데이터는 10\^9명 * 하루40건 * 365일 * 50KB = 730PB
- 첨부파일 = 총 이메일의 1/5 수준, 평균크기 500KB 라 가정시 = 1,460PB ⇒ 많은 데이터를 처리해야하고, 분산 데이터베이스 솔루션이 필요함
2. 개략적 설계
이메일101
- 이메일 프로토콜
- SMTP : 이메일을 한 서버에서 다른 서버로 보내는 표준 프로토콜 ⇒ SMTPS
- POP, IMAP : 이메일 클라이언트가 원격 메일 서버에서 이메일 수신하는데 사용되는 표준 프로토콜. 현대에와서는 거의 사용되지않음
- HTTPS : 메일 전송 프로토콜은 아니지만 보통 웹기반 이메일 시스템에서 클라이언트 통신으로 사용됨.
- 도메인이름서비스 (DNS)
- 수신자 도메인의 메일 교환기레코드 검색에 사용됨
- DNS에서 검색한 MX레코드들 중 우선순위가 높은 메일서버 순서대로 메세지 송신을 시도하게됨
- 첨부파일
- 일반적으로 Base64인코딩 사용
- MIME(첨부파일 표준규격)을 사용
- 전통적 메일서버
- 서버한대로 동작했던시절..
- 송신측 클라에서 SMTP로 메일전송 → DNS 에서 수신사 서버정보 찾음 → 해당 서버로 SMTP통해 메일 송신 → 수신측에서 메일 저장 → 수신측 클라는 IMAP/POP 서버로 메일 수신
- 이메일은 파일시스템 디렉토리에 저장됨.
- 이 방식은 수십억개의 이메일 검색 및 백업에는 사용될 수 없음. (디스크 I/O 병목)
분산 메일 서버
- 이메일 API : HTTP 프로토콜을 사용한다고 가정하겠음.
- POST
/v1/messages: 수신자에게 메세지 전송 - GET
/v1/folders: 이메일 계정에 존재하는 모든 폴더 반환 - GET
/v1/folders/{:folder_id}/messages: 주어진 폴더 아래 모든 메세지 반환 - GET
/v1/messages/{:message_id}: 주어진 메세지에대한 모든 정보 반환
- POST
- 분산 메일 서버 아키텍처
- 웹메일 : 사용자 클라이언트(웹브라우저)
- 웹서버 : 사용자가 이용하는 요청 / 응답서비스. 이메일 API요청은 모두 웹서버를 통함.
- 실시간 서버 : 새로운 이메일 내역을 클라이언트에게 실시간으로 전달 . 웹소켓이나 롱폴링 사용.
- 메타데이터 DB : 이메일의 메타데이터 저장 데이터베이스.
- 첨부파일 저장소 : 아마존 S3같은 객체저장소 사용.
- 분산캐시 : 최근 수신 이메일은 자주읽을 가능성이 높음.메모리에 캐싱 → 레디스 사용 추천.
- 검색 저장소 : 역인덱스를 사용하는 분산 문서 저장소.
- 이메일 전송 절차

- 사용자 전송버튼 누르고 로드밸런스로 요청 전송
- 로드밸런서 웹서버로 요청 전달
- 웹서버는 아래와 같은 작업 수행 - 이메일 검증 - 수신자 이메일, 송신자 이메일 도메인 검사. 동일도메인일시 - 스팸, 바이러스 감염여부 검사 - 보낸편지함, 받은편지함 저장
- 외부이메일일시 외부전송큐로 전달. - 첨부파일 s3에 저장 - 검증실패시 에러큐에 저장
- STMP 는 검증후 외부 이메일 서버로 메일전송
- 메일이 오랫동안 큐에 남아있는경우
- 수신자측 메일서버에 장애발생 : exponential backoff 전략으로 재전송
- 이메일 보낼 큐의 소비자수가 불충분 : 소비자 추가
- 이메일 수신 절차
- 메일이 오랫동안 큐에 남아있는경우

- 이메일이 SMTP 로드밸런서에 도착
- 로드밸런서는 STMP 서버로 분산해서 메일 보냄
- 첨부파일 s3에 저장
- 이메일을 수신 이메일 큐에 넣음
- 메일 처리 작업 프로세스 진행 - 스팸메일 거르기 - 바이러스차단
- 메일을 각각 메일저장소, 캐시, 객체저장소에 보관
- 수신자가 온라인일경우 실시간서버(웹소켓)에 전달
- 오프라인일경우 저장소계층에 보관
- 웹서버는 저장소계층에서 가져와 클라이언트에 반환
3. 상세설계
메타데이터 데이터베이스
- 이메일 메타데이터의 특성
- 이메일 헤더 : 작고 빈번하게 이용
- 이메일 본문 : 크기가 다양하고 사용빈도 낮음.
- 이메일관련 작업은 사용자별로 격리 수행
- 신선한 데이터만 많이읽고 (82%읽기발생) 오래된데이터는 거의안읽음
- 데이터의 높은 안정성이 필요함
- 올바른 데이터베이스 선정
- RDB : 검색은 효율적으로 할 수 있으나 비정형데이터(BLOB)에대한 검색지원이 좋지않음. 이메일 본문 검색시마다 많은 디스크I/O가 발생할것임. → ❌
- 분산 객체 저장소 : s3 를 저장소로 이용한다면.. 읽음표시, 키워드검색, 이메일 스레드 구성등이 불가능 → ❌
- NoSQL : 구글이 지메일에 빅테이블쓰는거 생각하면 가능은함.
결국 우리가 필요로하는 기능을 완벽히 지원하는 DB는 없음. 아래 내용을 충족하는 독자적 DB를 만들어서 사용하는게 보통의 해결방법임.
- 단일컬럼의 크기는 MB 이상
- 강력한 데이터 일관성 보장
- 디스크 I/O 최소화
- 가용성 높음
- 결함내성
- 증분백업이 쉬워야함
- 데이터 모델
- 파티션 키 : 데이터를 여러 노드에 분산. 데이터가 노드에 균등하게 분산되도록 하는 키를 골라야함. 보통 user_id 를 파티션키로 사용하여 특정 사용자의 데이터는 같은 샤드에 보관
- 클러스터 키 : 같은 파티션에 속한 데이터를 정렬
- 지원 질의
- 주어진 사용자의 모든 폴더 조회
- user_id 파티션키
- 특정 폴더내의 모든 이메일 조회
- <user_id, folder_id > 파티션키
- email_id : timeuuid타입 클러스터키
- 메일 생성, 삭제, 조회
- user_id 파티션키
- email_id 클러스터키
- 읽은메일 조회, 안읽은메일 조회
- emails_by_folder를 비정규화
- read_emails
- unread_emails
- emails_by_folder를 비정규화
- 이메일 스레드 조회
- JWZ 알고리즘
- 이메일 헤더의 3개필드 사용
- Message-Id
- In-Reply-To
- References
- 주어진 사용자의 모든 폴더 조회
- 일관성문제
- 정확성이 더 중요..
- 하나의 primary 사본으로 서비스
- 장애발생시 다른 사본이 primary로 복원될때까지 동기화/갱신은 중단.
- 일관성을 위해 가용성 희생
이메일 전송 가능성
신뢰도 높은 서버가 아니면 이메일 전송 가능성은 매우 낮아짐. 아래 요소들을 고려해야함
- 전용 IP : 이력없는 IP는 무시당할수있음
- 범주화 : 마케팅목적 이메일은 분리해서 보내야 중요이메일전송시 영향 안받을수있음
- 발신인 평판 : 새 서버 IP주소는 사용빈도를 서서히 높히는것이 신뢰도 향상에 도움이됨
- 스팸 발송자의 신속한 차단
- 피드백처리 : ISP측의 피드백을 처리할수있는 경로를 만들어 빠르게 처리하도록함
- 경성반송 : 수신인이메일주소 올바르지않음
- 연성반송 : ISP쪽 자원부족으로인한 일시적 반송
- 불만신고 : 수신인의 스팸신고
- 이메일 인증 : SPF, DKIM, DMARC등의 검증을 거치게됨
이메일이 목적지까지 성공적으로 도착하기는 꽤 어려움. ISP(Internet Service Provider)와도 좋은관계 유지가 필요함 https://www.mail-tester.com/
검색
보통 이메일 제목, 본문에 키워드가 포함되어있는지 찾게됨.
- elasticsearch 사용
- user_id를 파티션키로 같은노드에 할당
- 색인작업 수행.
- 이때 카프카를 활용하여 이메일 전송/수신/삭제시 비동기로 처리
- 유저의 검색 자체는 API를 사용해 처리되며 동기로 처리
- 맞춤형 검색 솔루션
- 보통 LSM트리를 사용해 검색엔진 자체개발.
- 자주바뀌는 데이터(폴더)와 그렇지않은데이터(이메일)를 분리하기위함
- 자주바뀌는건 낮은계층(메모리), 안바뀌는건 디스크에 위치하게됨.
규모 확장성 및 가용성
- 데이터를 여러데이터센터에 다중화
- 네트워크 토폴로지 : 기본적으로 물리적으로 가까운 메일서버와 통신
- 장애시 다른 데이터센터에 보관된 메세지 이용
4. 마무리
추가논의 하면 좋은 주제들
- 결함내성
- 규정준수 : 지역별 법규
- 보안
- 최적화
차주부터..! 공부 전 목차작성해보기