대규모 시스템 설계 기초 2 (8장)
가상 면접 사례로 배우는 대규모 시스템 설계 기초 2 (8장) 의 내용 중, 인상적이었던 부분을 발췌 및 요약합니다.
분산 이메일 서비스
이메일 101
이메일을 주고받는 프로토콜은 무엇이 있을까?
이메일 프로토콜
- SMTP : 이메일을 한 서버에서 다른 서버로 보내는 표준 프로토콜이다.
- 이메일을 가져오는 목적으로 널리 사용되는 프로토콜로는 POP 와 IMAP 가 있다.
- POP : 이메일 클라이언트가 원격 메일 서버에서 이메일을 수신하고 다운로드하기 위해 사용하는 표준 트로토콜이다. 다운로드된 이메일은 서버에서 삭제된다. 이메일을 읽기 위해서는 전부 내려받아야 한다.
- IMAP : 클릭하지 않으면 메시지는 다운로드 되지 않으며, 메일 서버에서 지워지지도 않는다. 여러 단말에서 이메일을 읽을 수 있다. 이메일을 실제로 읽기 전에는 헤더만 다운로드 한다.
DNS
DNS 서버는 수신자 도메인의 메일 교환기 레코드 검색에 사용된다. DNS 레코드를 검색하면 MX 레코드가 표시된다. 이로 하여금 우선순위 값을 알 수 있는데, 우선순위가 높은 쪽으로 송신자측 메일 서버는 메시지를 보내려고 시도한다.
첨부 파일
이메일 첨부파일은 일반적으로 메시지와 함께 전송되며 Base64 인코딩을 사용한다.
전통적 메일 서버 아키텍쳐
- 엘리스가 이메일을 보낸다. 이메일은 메일 서버로 전송되며, 이 때 통신 프로토콜은 SMTP 를 사용한다.
- 메일 서버에서는 질의를 통해 수신자 SMTP 서버를 찾는다. 메일 서버 간 통신 프로토콜도 SMTP 다.
- 서버는 이메일을 저장하고 수신자인 밥이 읽어간다.
- 밥이 로그인하면, 클라이언트는 IMAP/POP 서버를 통해 새 이메일을 가져온다.
전통적 메일 서버는 이메일을 파일 시스템으로 저장한다. 이는 결국 사용자가 많아지면 디스크 I/O 로 병목이 발생시킨다.
분산 메일 서버 아키텍쳐
각 컴포넌트는 다음과 같다.
- 웹 메일 : 사용자는 브라우저를 사용해 메일을 받고 보낸다.
- 웹 서버 : 사용자가 이용하는 요청/응답 서비스로 이메일 관련 API 요청을 처리한다.
- 실시간 서버 : 새로운 이메일 내역을 클라이언트에 실시간으로 전달하는 역할을 담당한다.
- 메타데이터 데이터베이스 : 이메일 제목 , 본문 등의 메타 데이터를 저장한다.
- **첨부 파일 저장소 ** : 아마존 S3 같은 객체 저장소를 사용할 것이다.
- 분산 캐시 : 최근에 수신된 이메일을 자주 읽을 것이기에, 클라이언트로 하여금 메모리에 캐시해두도록 하자.
- 검색 저장소 : 검색 저장소는 분산 문서 저장소다. 역 인덱스를 자료 구조로 사용한다.
이메일 전송 절차
- 사용자가 웹메일 환경에서 메시지를 작성하고, 전송한다.
- 로드밸런서는 받은 이메일이 처리율 제한 한도를 넘지 않았다면, 웹 서버로 전송한다.
- 웹 서버는 이메일을 검증한다.
- 검증을 통과한 이메일은 외부 전송 메시지 큐로 전달된다. 큐에 넣기에 첨부 파일이 너무 크다면, 첨부 파일은 객체 저장소에 따로 저장하고 큐에 전달하는 이메일 안에는 해당 저장 위치에 대한 참조 정보만 보관되어야 한다.
- 외부 전송 담당 SMTP 작업 프로세스는 외부 전송 큐에서 메시지를 꺼내어 이메일의 스팸 및 바이러스 여부를 확인한다.
- 검증 절차를 통과한 이메일은, 저장소 계층의 ‘보낸 편지함’ 에 저장된다.
- 외부 전송 SMTP 프로세스가 수신자의 메일 서버로 메일을 전송한다.
이메일 수신 절차
- 이메일이 SMTP 로드밸런서에 도착한다.
- 로드밸런서는 트래픽을 여러 SMTP 서버로 분산한다.
- 이메일의 첨부 파일이 큐에 들어가기에 너무 크다면 첨부 파일 저장소에 보관한다.
- 이메일을 수신 이메일 큐에 넣는다.
- 메일 처리 작업 프로세스(worker)는 스팸 메일을 걸러낸다.
- 검증이 끝난 이메일은, 메일 저장소, 캐시, 객체 저장소 등에 보관한다.
- 수신자가 온라인 상태인 경우 이메일을 실시간 서버로 전달한다.
- 실시간 서버는 수신자 클라이언트가 새 이메일을 실시간으로 받을 수 있도록 하는 웹소켓 서버다.
- 오프라인 상태 사용자의 이메일은 저장소 계층에 보관하다가, 사용자가 온라인이 되면 웹 서버에 rest api 를 통해 연결한다.
- 웹 서버는 새로운 이메일을 저장소 계층에서 가져와 클라이언트에 반환한다.
데이터베이스
이메일 데이터의 헤더는 작고, 빈번하게 이용된다. 그러나 본문의 크기는 다양하며, 사용 빈도가 낮다. 이메일은 적합한 사용자에 의해서만 읽어져야 한다. 데이터의 신선도는 데이터 사용 패턴에 영향을 미친다.
데이터베이스 선정
- 관계형 DB : 이메일 헤더와 본문으로 인덱싱을 해둔다면 빠르게 검색할 수 있을 것이다. 그러나 이메일은 본문에 BLOB 자료형이 들어가야 한다. 이는 검색 질의 성능에 좋지 않으며 큰 데이터를 저장하기에 디스크 I/O 를 크게 발생시킨다.
- 분산 객체 저장소 : 원시 데이터를 객체 저장소에 보관하기 좋지만, 이메일의 읽음 표시, 키워드 검색, 이메일 쓰레드 등의 기능을 구현하기에는 좋지 않다.
- NoSQL 데이터베이스 : 지메일은 구글 빅테이블을 저장소로 사용한다. 빅테이블은 오픈소스가 아니기에, 어떻게 검색을 구현했는지 알 수 없다.
결국, 이메일을 위한 DB 는 다음과 같은 요건을 만족해야 한다.
- 어떤 단일 컬럼의 크기는 한 자릿수 MB 정도일 수 있다.
- 강력한 데이터 일관성이 보장되어야 한다.
- 디스크 I/O 가 최소화되도록 설계되어야 한다.
- 가용성이 아주 높아야 하고 일부 장애를 감내할 수 있어야 한다.
- 증분 백업이 쉬워야 한다.
이메일 쓰레드
이메일이 오간 모든 이력을 쓰레드로 엮어서 보내주려면 어떻게 해야 할까? 이는 JWZ 같은 알고리즘을 통해 구현된다.
JWZ 알고리즘의 이메일 헤더는 MessageID(메시지 식별자), In-Reply-To(이 메시지가 어떤 메시지에 대한 답신인지 나타내는 식별자), References(쓰레드에 관계된 메시지 식별자 목록) 으로 구성되어 있다. 이 필드를 활용하여 쓰레드 내의 모든 메시를 메모리에서 찾아서 재구성할 수 있다.
검색
이메일 검색은 이메일 제목이나, 본문의 특정 키워드를 찾는 것이다. 검색 기능에는 쓰기 연산이 읽기 연산보다 훨씬 많다. ElasticSearch 를 사용하여 검색 기능을 구현하는 방법을 추천한다.