본문 바로가기
Boaz/Real-time Data and Kafka

[대규모 실시간 데이터 처리 #4] 대규모 시스템 설계 기초. 12장 채팅 시스템 설계

by 남디윤 2024. 8. 4.

보아즈 멘멘 스터디 중 기록한 내용입니다.

 

 

목차

1단계 문제 이해 및 설계 범위 확정

2단계 개략적 설계안 제시 및 동의 구하기

3단계 상세 설계

4단계 마무리

 

 

 

1단계 문제 이해 및 설계 범위 확정

  • 어떤 채팅 앱
    • 1:1 채팅 vs 그룹 채팅
    • 모바일 vs 웹
    • 트래픽 규모
    • 그룹 채팅 인원 제한
    • 중요 기능
    • 메세지 길이 제한
    • 종단 간 암호화
    • 채팅 이력 보관 등
  • 이 책의 이 장에서 설계하는 앱: 페이스북 메신저와 유사한 채팅 앱

 

 

 

2단계 개략적 설계안 제시 및 동의 구하기

  • 클라이언트: 모바일 앱 or 웹 애플리케이션
    • 클라이언트는 서로 통신 X
    • 기능을 지원하는 채팅 서비스와 통신
  • 채팅 서비스
    • 클라이언트들로부터 메시지 수신
    • 메시지 수신자 recipient 결정 및 전달
    • 수신자가 접속 online 상태가 아닌 경우, 접속할 때까지 해당 메세지 보관

Untitled

서버 ↔ 클라이언트

  • HTTP 프로토콜 사용
    • 송신 클라이언트 → 수신 클라이언트
    • 메세지 전송 용도로 괜찮은 선택
      • 페이스북 같은 많은 대중적 채팅 프로그램이 초기에 HTTP 사용함
      • HTTP는 클라이언트가 연결을 만드는 프로토콜. 서버에서 클라이언트로 임의 시점에 메세지를 보내는 데는 쉽게 사용 불가
    • 서버가 연결을 만드는 것처럼 동작할 수 있도록 하기 위해 많은 기법 제안
    • 폴링 polling, 롱 폴링 long polling, 웹소켓 WebSocket 등
  • 폴링
    • 클라이언트가 주기적으로 서버에게 새 메세지가 있느냐고 물어보는 방법
    • 폴링 비용: 폴링을 자주할수록 상승
  • 롱 폴링
    • 클라이언트는 새 메세지가 반환되거나 타임아웃 될 때까지 연결 유지
    • 새 메세지를 받으면 기존 연결 종료
    • 서버에 새로운 요청을 보내어 모든 절차 다시 시작
    • 약점
      • 송신 클라이언트와 수신 클라이언트가 같은 채팅 서버에 접속하게 되지 않을 수도 있음
      • 서버 입장에서는 연결 해제 여부 알 수 없음
      • 여전히 비효율적 (타임아웃이 일어날 때마다 주기적 서버 재접속)
  • 웹소켓 WebSocket
    • 서버가 클라이언트에게 비동기 async 메세지 보낼 때 가장 널리 사용되는 기술
    • 웹소켓 연결은 클라이언트가 시작
    • 처음에 HTTP 연결 후, 특정 핸드셰이크 절차 이후 웹소켓 연결로 업그레이드
    • 웹소켓 연결은 영구적이며 양방향이다 → 서버 측 연결 효율적 관리 필요
    • 일반적으로 방화벽이 있는 환경에서도 잘 동작
      • 80, 443처럼 HTTP 혹은 HTTPS 프로토콜이 사용하는 기본 포트번호 그대로 사용
    • 메세지를 보낼 때나 받을 때 동일한 프로토콜 사용 → 구현이 단순, 직관적

 

개략적 설계안

  • 무상태 서비스
    • 로그인, 회원가입, 사용자 프로파일 표시 등 처리하는 전통적인 요청/응답 서비스
    • 로드밸런서 뒤에 위치
      • 로드밸런서: 요청을 그 경로에 맞는 서비스로 전달
      • 로드밸런서 뒤에 오는 서비스는 모놀리티 monolithic 서비스 또는 마이크로서비스
  • 상태 유지 서비스
    • 채팅 서비스
    • 각 클라이언트가 채팅 서버와 독립적인 네트워크 연결 유지 필요
      • 클라이언트는 서버와 연결이 살아 있는 한 다른 서버 연결 변경 X
  • 제 3자 서비스 연동
    • 채팅 앱에서 가장 중요한 제3자 서비스 → 푸시 알림
    • 앱이 실행 중이지 않더라도 알림 필요
    • 푸시 알림 서비스와의 통합 매우 중요
  • 규모 확장성
    • 서버 한 대로 구현
      • = 동접자 1M, 접속당 10K의 서버 메모리 필요 → 10GB 메모리 있으면 모든 연결 처리 가능
      • SPOF Single-Point-Of-Failure 발생 가능
      • 면접 시, 한 대 갖는 설계안에서 개선하는 것도 괜찮음. 단, 반드시 시작일뿐 이라는 것을 명확하게 전달 필요

Untitled 1

 

개략적 설계안

  • 채팅 서버는 클라이언트 사이에 메세지 중계 역할
  • 접속상태 서버 presence server는 사용자 접속 여부 관리
  • API 서버는 로그인, 회원가입, 프로파일 변경 등 그 외 나머지 처리
  • 알림 서버는 푸시 알림 전송
  • 키-값 저장소에는 채팅 이력을 보관. 시스템에 접속한 사용자는 이전 채팅 이력 전부 보게 됨.
  • 저장소
    • 어떤 데이터베이스 사용? (관계형 vs NoSQL)
    • → 데이터의 유형과 읽기/쓰기 연산의 패턴
    • 채팅 시스템이 다루는 데이터 유형 1) 일반적인 데이터
      • 사용자 프로파일, 설정, 친구 목독 등
      • 안전성을 보장하는 관계형 데이터베이스에 보관
    • 채팅 시스템이 다루는 데이터 유형 2) 채팅 이력 (채팅 시스템에 고유한 데이터)
      • 가장 빈번하게 사용되는 것: 최근 주고 받는 메세지. 오래된 건 잘 보지 않음
      • 무작위적인 데이터 접근
        • 검색 기능 이용, 특정 사용자가 언급된 메세지 보거나, 특정 메세지로 점프
    • 키-값 저장소
      • 수평적 규모 확장이 용이
      • 데이터 접근 지연시간 latency이 낮음
      • 관계형 데이터베이스는 데이터 가운데 롱 테일long tail 에 대항하는 부분을 잘 처리하지 못하는 경향이 있음. 인덱스가 커지면 데이터에 대한 무작위적 접근 처리 비용 증가
      • 많은 채팅 시스템이 채택 중.
        • 페이스북 메신저, 디스코드 등
        • 페이스북 Hbase, 디스코드 카산드라 Cassandra
  • 데이터 모델
    • 메시지 데이터는 어떻게 보관?
      • 1:1 채팅을 위한 메세지 테이블: message_id를 기본 키 primary key로 사용
      • 그룹 채팅을 위한 메세지 테이블: channel_id, message_id를 기본 키로 사용
    • 메세지 ID
      • 고유해야 함
      • 정렬 가능해야 하며, 시간 순서와 일치해야 함
      • RDBMS의 auto_increment
      • 64-bit 순서 번호 생성기 (e.g,. 스놀플레이크)
      • 지역적 순서 번호 생성기 local sequence number generator
        • 지역적: ID의 유일성은 같은 그룹안에서만 보증하면 충분
        • 메세지 사이의 순서는 같은 채널, 같은 1:1 채팅 안에서만 유지되면 충분

 

 

 

3단계 상세 설계

서비스 탐색

  • 클라이언트에게 가장 적합한 채팅 서버 추천
    • 기준: 클라이언트 위치, 서버의 용량 등
    • 가장 널리 쓰이는 오픈 소스 솔루션: 아파치 주키퍼
    • 사용 가능한 모든 채팅 서버를 등록해두고, 클라이언트가 접속 시도 시 사전에 정한 기준에 따라 최적의 채팅 서버 골라줌
    • 작동 방식 예제
      1. 사용자 A가 시스템에 로그인 시도
      2. 로드 밸런서가 로긍니 요청을 API 서버들 가운데 하나로 보냄
      3. API 서버가 사용자 인증 처리하고 나면, 서비스 탐색 기능이 동작 → 최적의 채팅 서버 찾음(예. 서버2) → 사용자A에게 반환
      4. 사용자 A는 채팅 서버 2와 웹소켓 연결

Untitled 2

메세지 흐름

  • 1:1 채팅 메세지 처리 흐름
    1. 사용자 A가 채팅 서버 1로 메시지 전송
    2. 채팅 서버 1은 ID 생성기를 사용해 메시지 ID 결정
    3. 해당 메시지를 메시지 동기화 큐로 전송
    4. 메시지가 키-값 저장소에 보관됨
    5. (a) 사용자 B가 접속 중인 경우 B가 사용 중인 채팅 서버로 메시지 전송
    6. (b) 사용자 B가 접속 중이 아니라면 푸시 알림 서버로 푸시 알림 메시지 전송
    7. 사용자 B와 채팅 서버 사이에 연결된 웹소켓을 통해 메시지 전송

Untitled 3

  • 여러 단말 사이의 메세지 동기화
    • 예) 사용자 A: 전화기, 노트북 사용 (두 대 단말 사용)
    • 각 단말 cur_max_message_id 라는 변수 유지. 해당 단말에서 관측된 가장 최신 메세지 id 추적 용도
    • 새 메세지 간주 기준
      • 수신자 ID가 현재 로그인한 사용자 ID와 같음
      • 키-값 저장소에 보관된 메세지, 그 messaage_id가 cur_max_message_id 보다 크다

Untitled 4

  • 소규모 그룹 채팅에서의 메세지 흐름
    • 사용자 A가 보낸 메세지가 사용자 B와 C의 메세지 동기화 큐 message sync queue에 복사됨
      • 새로운 메세지 확인 시, 자기 큐만 보면 되기 때문에, 동기화 플로가 단순
      • 큐에 복사하는 작업 비용이 크지 않기 때문에
    • 수신자 관점의 메세지 동기화 큐
      • 여러 채팅 서버에서 (여러 사용자로부터) 오는 메세지 수신 가능해야 함

Untitled 5

접속상태 표시

  • 접속상태 서버를 통해 사용자의 상태 관리
    • 클라이언트와 웹소켓으로 통신하는 실시간 서비스의 일부
  • 사용자 로그인
    • 사용자와 실시간 서비스 사이에 연결이 이루어지면
    • → 접속상태 서버는 사용자의 상태와 last_active_at 타임스탬프를 키-값 저장소에 보관
    • → 이 절차가 끝나면 사용자는 접속 중인 것으로 표시됨

Untitled 6

  • 로그아웃
    • 키-값 저장소에 보관된 사용자 상태가 online에서 offline으로 바뀜
    • → 이 절차가 끝나면 사용자는 접속 중이 아닌 것으로 표시됨
  • 접속 장애
    • 사용자 인터넷 끊어짐
      • 사용자 오프라인 상태 표시 → 연결 복구 → 온라인 상태 변경
      • 문제: 너무 흔할 수 있음. (터널 안에서 짧은 시간 안에 엄청 끊김. 매번 바꾸기 부적합)
    • 박동 heartbeat 검사
      • 온라인 상태의 클라이언트가 박동 이벤트를 접속상태 서버로 보내게 함
      • 마지막 이벤트를 받은 지 x초 이내에 또 다른 박동 이벤트 받으면 → 접속 상태 online
      • 아니면 접속 상태 offline
  • 상태 정보의 전송
    • 상태정보 서버는 발행-구독 모델 사용
      • 각각의 친구관계마다 채널을 하나씩 두는 것
    • 클라이언트와 서버 사이의 통신에는 실시간 웹소켓 사용
    • 그룹 크기가 작을 때 효과적
    • 그룹 크기가 더 커지는 경우
      • 사용자가 그룹 채팅에 입장하는 순간에만 상태 정보를 읽어가게 함
      • 친구 리스트에 있는 사용자의 접속상태 갱신하고 싶으면 수동mannual으로 하도록 유도

Untitled 7

 

 

 

4단계 마무리

정리

  • 클라이언트와 서버 사이의 실시간 통신이 가능하도록 하기 위해 웹소켓을 사용함
  • 실시간 메세징을 지원하는 채팅 서버, 접속 상태 서버, 푸시 알림 서버, 채팅 이력을 보관할 키-값 저장소, 그 외 API 서버 등

추가 논의 사항

  • 사진, 비디오 등의 미디어 지원하도록 하는 방법
  • 종단 간 암호화: 발신인, 수신인 외에는 메세지 내용 볼 수 없음
  • 캐시
  • 로딩 속도 개선
  • 오류 처리