programming6 MIN READ

[시스템 디자인] 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} : 주어진 메세지에대한 모든 정보 반환
  • 분산 메일 서버 아키텍처
    • 웹메일 : 사용자 클라이언트(웹브라우저)
    • 웹서버 : 사용자가 이용하는 요청 / 응답서비스. 이메일 API요청은 모두 웹서버를 통함.
    • 실시간 서버 : 새로운 이메일 내역을 클라이언트에게 실시간으로 전달 . 웹소켓이나 롱폴링 사용.
    • 메타데이터 DB : 이메일의 메타데이터 저장 데이터베이스.
    • 첨부파일 저장소 : 아마존 S3같은 객체저장소 사용.
    • 분산캐시 : 최근 수신 이메일은 자주읽을 가능성이 높음.메모리에 캐싱 → 레디스 사용 추천.
    • 검색 저장소 : 역인덱스를 사용하는 분산 문서 저장소.
  • 이메일 전송 절차

image

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

image

  1. 이메일이 SMTP 로드밸런서에 도착
  2. 로드밸런서는 STMP 서버로 분산해서 메일 보냄
  3. 첨부파일 s3에 저장
  4. 이메일을 수신 이메일 큐에 넣음
  5. 메일 처리 작업 프로세스 진행 - 스팸메일 거르기 - 바이러스차단
  6. 메일을 각각 메일저장소, 캐시, 객체저장소에 보관
  7. 수신자가 온라인일경우 실시간서버(웹소켓)에 전달
  8. 오프라인일경우 저장소계층에 보관
  9. 웹서버는 저장소계층에서 가져와 클라이언트에 반환

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
    • 이메일 스레드 조회
      • 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. 마무리

추가논의 하면 좋은 주제들

  • 결함내성
  • 규정준수 : 지역별 법규
  • 보안
  • 최적화

차주부터..! 공부 전 목차작성해보기