반응형

포인트

  • 안정성: 데이터는 소실되면 안됨
  • 가용성: 이메일과 사용자 데이터를 여러 노드에 복제하여 계속 동작하게
  • 확장성: 사용자 수가 늘어나도 성능 저하 안되게
  • 막대한 저장 용량 필요

 

구 프로토콜과 방식

저장소는 파일 시스템의 디렉터리 but 수십억 개의 이메일을 검색하고 백업하기엔 디스크 IO가 병목이 되고 서버 장애 등 안정적이지 못함

단일 장비 위에서만 동작하도록 설계

 

분산 메일 서버 아키텍쳐

보낼 때

외부 전송 큐에 보관되는 모든 메시지에는 이메일을 생성하는데 필요한 모든 메타데이터가 포함되어 있음

위 5번 과정으로 인해 외부 SMTP규모를 독립적으로 조정할 수 있게 된다.

외부 전송 큐에 오래 남아 있으면 확인 필요

1. 이메일을 보낼 큐의 소비자를 추가

2. 받는 서버에 장애 발생: 재전송 필요; 지수적 백오프

 

받을 때

 

디비는? 완벽한 디비는 없음..

RDB? 데이터 크기가 작을 때 적합. BLOB 써도 접근할 때마다 많은 디스크 IO발생

분산 객체 저장소나 Nosql은 키워드 검색이나 읽음 표시 등의 기능을 제공하기 어려움

  • 강력한 데이터 일관성 보장
  • 디스크 IO 최소화
  • 가용성 아주 높아야 하고 일부 장애를 감냉해야
  • incremental backup이 쉬워야

 

유저 키를 파티션키로 사용하여 같은 샤드에 저장

파티션키: 데이터를 여러 노드에 분산하는 역할; 데이터가 모든 노드에 균등하게 분배되도록(유저 키)

클러스터키: 같은 파티션에 속한 데이터를 정렬하는 역할(폴더)

noSQL 기반으로 할 경우 파티션키/클러스터키 외의 필터링을 어플리케이션에서 하거나 쿼리로 하기보단

테이블을 비정규화해서 테이블 자체를 나눠버리는 게 질의 성능을 향상시켜야 하는 대규모 서비스에 더 맞다.

 

일관성을 위해 replica 필요

  • 장애가 발생하면 클라이언트는 main이 복원될 때까지 동기화/갱신 작업을 완료할 수 없음
  • 일관성을 위해 가용성을 희생해야
  • 가용성을 위해서는 여러 지역의 데이터 센터에 다중화하고 일반적으로는 가까운 데이터센터에서 통신하다 장애가 났을 때 타 지역 데이터센터에 보관된 메시지를 이용한다.

 

이메일 스레딩

JWZ 알고리즘의 주요 목적

  • 이메일 간 Reply-To 관계를 파악해 계층적 트리 구조를 만듦
  • 이메일 클라이언트에서 스레드 형태로 메시지를 표시하기 위해 사용.
  • 이메일 그룹화를 통해 사용자에게 논리적이고 직관적인 대화 흐름을 제공.

알고리즘 작동 원리

  1. 헤더 정보 수집
    • Message-ID: 각 이메일의 고유 식별자.
    • In-Reply-To: 답장 대상 이메일의 Message-ID.
    • References: 해당 이메일이 참조한 이전 이메일의 Message-ID 목록.
  2. 이메일 헤더의 아래 정보를 기반으로 스레드 관계를 결정합니다:
  3. 스레드 생성 과정
    1. 모든 이메일의 Message-ID와 **Reply 관계(In-Reply-To, References)**를 분석.
    2. 이메일을 **노드(Node)**로 보고, Message-ID를 키로 하여 초기 노드 목록을 생성.
    3. 각 이메일의 In-Reply-To 및 References를 순회하며 부모-자식 관계를 설정.
    4. 스레드 트리를 형성하며, 루트 노드(시작 이메일)부터 트리가 확장됨.
  4. 루트 노드와 고아 처리
    • 루트 노드: Reply 관계가 없는 독립된 이메일로, 새로운 스레드의 시작점이 됨.
    • 고아 메시지: 부모가 없는 이메일. (헤더 정보가 손상되거나 누락된 경우 발생)
      • 별도의 루트 노드로 처리하거나, 관련성이 가장 높은 메시지에 병합.
  5. 정렬
    • 시간 순서(예: 날짜/시간) 또는 논리적 Reply 순서에 따라 트리를 정렬.

 

 

이메일 검색을 위해

  • elastic search 활용하거나 자체 개발할 솔루션
  • 디스크 I/O 병목 주의
  • 색인을 구축하는 프로세스는 다량의 쓰기 연산을 발생시키므로 LSM(Log Structured Merge) 트리를 사용하여 디스크에 저장되는 색인을 구조화하는 것이 바람직

LSM 트리가 색인 구축에 적합한 이유

  1. 쓰기 연산의 효율성
    • LSM 트리는 데이터를 먼저 메모리(RAM) 내의 MemTable에 기록하고, 특정 조건(예: 크기 초과)이 충족되면 이를 **정렬된 SSTable(파일 단위)**로 디스크에 순차적으로 저장합니다.
    • 디스크에 순차 쓰기가 이루어지므로 디스크 I/O 비용이 감소하고, 색인을 구축할 때 발생하는 다량의 쓰기 작업을 효율적으로 처리할 수 있습니다.
  2. 쓰기 병목 현상 완화
    • 기존의 B-트리와 같은 데이터 구조는 **랜덤 쓰기(Random Write)**가 많아지는 단점이 있습니다.
    • 반면, LSM 트리는 랜덤 쓰기를 최소화하고, 데이터를 버퍼링 후 배치 처리하여 쓰기 병목을 완화합니다.
  3. 색인 업데이트의 성능 향상
    • 색인을 업데이트할 때, 기존 데이터를 즉시 덮어쓰지 않고 새로운 데이터를 추가한 뒤 Compaction(압축) 과정을 통해 병합합니다.
    • 이를 통해 쓰기와 병합 작업이 분리되어 성능이 향상됩니다.
  4. 다량의 색인 생성
    • 색인을 구축하는 도중에 읽기 연산이 발생하더라도, LSM 트리는 메모리 내 데이터와 디스크 데이터를 병합하여 읽기 요청을 처리할 수 있습니다.
    • 색인 작업 도중에도 안정적으로 데이터를 처리할 수 있는 구조를 제공합니다.

LSM 트리 작동 과정

  1. MemTable: 데이터를 메모리에 기록 (쓰기 연산 집중).
  2. Flush: MemTable이 가득 차면, 이를 디스크에 **SSTable(Sorted String Table)**로 기록.
  3. Compaction: 여러 개의 SSTable을 병합하여 디스크 공간을 최적화하고 읽기 성능을 향상.
728x90
반응형

+ Recent posts