분산 이메일 서비스
- 8.1 문제 이해 및 설계 범위 확정
- 8.2 개략적 설계안 제시
- 8.3 상세 설계
- 8.4 마무리
분산 이메일 서비스는 Gmail이나 Outlook 같은 서비스를 밑바닥부터 설계하는 문제야. 이메일은 인터넷 초창기부터 있던 서비스지만, 대규모로 설계하려면 이메일 프로토콜, 검색 인덱싱, 멀티 디바이스 동기화까지 다뤄야 해서 복잡도가 꽤 높지.
기능 요구사항을 보면:
- 이메일 송수신 — 이메일을 보내고 받을 수 있다
- 편지함 관리 — 받은편지함, 보낸편지함, 스팸함 등 폴더로 관리
- 이메일 검색 — 보낸 사람, 제목, 본문 등으로 검색
- 읽음/안읽음 표시, 라벨링 등 기본 기능
- 첨부 파일 지원
비기능 요구사항은:
- 가용성 — 이메일은 항상 받을 수 있어야 해
- 확장성 — 10억 사용자, 각 사용자당 수천 건의 이메일
- 멀티 디바이스 동기화 — 모바일에서 읽으면 PC에서도 읽음 표시
이메일 시스템을 이해하려면 프로토콜부터 알아야 하지.
SMTP (Simple Mail Transfer Protocol) — 이메일을 보내는 프로토콜이야. 이메일 서버 간 메시지를 전달할 때 사용하지. 25번 포트.
POP3 (Post Office Protocol 3) — 이메일을 받는 프로토콜이야. 서버에서 이메일을 다운로드하면 서버에서 삭제하지 (기본 설정). 단일 기기에서만 이메일을 관리하는 구식 방식이야.
IMAP (Internet Message Access Protocol) — 이메일을 받는 프로토콜이지만 POP3와 다르게 서버에 이메일을 유지해. 여러 기기에서 같은 이메일함을 동기화하는 데 적합하지. 현대 이메일 서비스는 대부분 IMAP 또는 독자 프로토콜이야.
HTTPS — 웹 메일은 브라우저에서 HTTPS로 이메일 서버의 API를 호출해. Gmail이 이 방식이지. 내부적으로는 SMTP로 다른 메일 서버에 전달하고.
이메일 송신 흐름을 보면:
- 사용자가 이메일 작성 후 전송 (HTTPS 또는 SMTP)
- 발신 서버가 메시지 큐에 이메일을 넣는다
- 발신 처리 서비스가 큐에서 꺼내서, 수신자의 메일 서버를 DNS MX 레코드로 찾는다
- 해당 메일 서버에 SMTP로 전달
- 전송 실패 시 재시도 큐에 넣고 재시도
이메일 수신 흐름은:
- 외부 메일 서버가 SMTP로 이메일을 전달
- 수신 서버가 이메일을 받아서 바이러스/스팸 검사
- 통과하면 메시지 저장소에 저장
- 사용자의 받은편지함에 표시
- 사용자가 IMAP/HTTPS로 이메일을 읽음
이메일은 어디에 어떻게 저장할 것인가?
RDBMS? — 이메일 본문이 크고, 첨부 파일도 있고, 사용자당 수천 건이면 RDBMS는 적합하지 않아.
분산 객체 저장소(S3 등)? — 이메일 본문과 첨부 파일을 저장하기 좋지만, 메타데이터 검색이 어렵지.
분산 문서 DB 또는 커스텀 저장소 — 실제로 Gmail은 Bigtable 기반의 커스텀 저장소를 써. 사용자 ID를 파티션 키로 해서 한 사용자의 모든 이메일이 같은 파티션에 저장되도록 하지. 이러면 "내 편지함" 조회가 빠르거든.
데이터 모델은 이래.
- 메타데이터 — 보낸 사람, 받는 사람, 제목, 시각, 읽음 여부, 라벨, 폴더 등. 자주 조회하고 필터링하니 인덱스가 중요
- 본문(body) — HTML 또는 텍스트. 크기가 크니 메타데이터와 분리 저장할 수도 있어
- 첨부 파일 — 크기가 더 크니 **객체 저장소(S3)**에 별도 저장하고 참조만 유지
이메일 검색은 **전문 검색(full-text search)**이 필요해. "프로젝트 제안서"라고 검색하면 제목이나 본문에 이 단어가 포함된 이메일을 찾아야 하지.
방법은 두 가지야.
Elasticsearch/Lucene 활용 — 이메일이 저장될 때 비동기로 검색 인덱스를 구축해. 역 인덱스(inverted index) 기반이라 키워드 검색이 매우 빠르지. 단점은 인덱스 구축 비용과 저장소 비용이 추가돼.
LSM 트리 기반 커스텀 인덱스 — 쓰기가 많은 워크로드에 적합한 LSM 트리를 사용해서 검색 인덱스를 구축해. 쓰기 최적화가 되어 있어서 이메일이 계속 들어오는 환경에 잘 맞지.
어느 방법이든 검색 인덱스는 사용자 ID 기준으로 파티셔닝해. 한 사용자의 검색은 해당 파티션만 뒤지면 되니까.
핸드폰에서 이메일을 읽으면 PC에서도 읽음 표시가 돼야 하잖아. 이를 위해 각 이메일의 상태(읽음/안읽음, 라벨 등)가 서버에 단일 소스로 존재하고, 각 디바이스가 이 상태를 동기화하지.
효율적인 동기화를 위해 각 변경에 변경 시퀀스 번호를 부여해. 디바이스가 마지막으로 동기화한 시퀀스를 기억하고 있다가, "나 이후의 변경 사항 다 줘"라고 요청하면 돼. 전체 데이터를 매번 다시 받는 게 아니라 **증분 동기화(incremental sync)**를 하는 거야.
이메일 서비스의 중요한 축 하나가 스팸 필터링이야.
- 송신 측: SPF, DKIM, DMARC 같은 인증 프로토콜로 발신자가 진짜인지 검증
- 수신 측: 머신러닝 기반 스팸 분류기가 이메일 내용, 발신자 평판, 패턴 등을 분석해서 스팸 여부를 판단
이메일은 비동기 프로토콜이야. 수신 서버가 잠깐 죽어도 발신 서버가 재시도하니까 이메일이 바로 유실되지 않지. 하지만 장시간 장애는 문제가 되니, 이메일 저장소의 복제와 메시지 큐의 내구성이 중요해.
이메일은 1970년대에 나온 프로토콜 위에 현대적인 대규모 시스템을 얹은 독특한 구조야. SMTP라는 레거시 프로토콜과 공존하면서 검색, 동기화 같은 현대적 기능을 제공해야 하는 게 설계의 재미있는 점이지.
정리
8장 읽고 기억할 거 세 가지:
- 이메일 시스템은 SMTP(송신)와 IMAP/HTTPS(수신) 프로토콜 위에 설계돼. 서버 간 통신은 SMTP, 사용자와의 인터페이스는 IMAP 또는 웹 API지.
- 이메일 검색에는 전문 검색 인덱스가 필수이고, 사용자 ID 기준 파티셔닝으로 검색 범위를 한정해. Elasticsearch 같은 도구나 커스텀 LSM 트리 인덱스를 활용하지.
- 멀티 디바이스 동기화는 변경 시퀀스 번호 기반의 증분 동기화로 해결해. 매번 전체 데이터를 보내지 않고, 마지막 동기화 이후의 변경만 전달하지.