Study

대규모 시스템 설계 - 채팅 시스템 설계

에디개발자 2021. 12. 7. 07:00
반응형

나를 닮았다고 한다...

들어가기 전에

가상 면접 사례로 배우는 대규모 시스템 설계 기초 라는 도서 중 12장인 채팅 시스템 설계를 정리한 내용입니다. 

http://www.kyobobook.co.kr/product/detailViewKor.laf?mallGb=KOR&ejkGb=KOR&barcode=9788966263158 

 

가상 면접 사례로 배우는 대규모 시스템 설계 기초 - 교보문고

2020년 출간 이후 아마존베스트셀러!아마존 컴퓨터네트워크응용 분야 베스트셀러 1위!《가상 면접으로 배우는 대규모 시스템 설계 기초》는 16가지 실제 시스템 설계 면접 문제와 상세한 답안을

www.kyobobook.co.kr

스터디 그룹에서 공유를 목적으로 작성한 글이기 때문에 이전 글과는 다르게 중요한 내용만 추려 작성하였습니다. ( PPT 방식 )

 

목차

  1. 요구사항
  2. 개략적 설계안
    1. 무상태 서비스
    2. 상태 서비스
    3. 제 3자 서비스 연동
    4. 규모확장성
    5. 저장소
  3. 상세 설계
    1. 서비스 탐색
    2. 메시지 흐름
    3. 접속상태 표시
  4. 마무리

1. 요구사항

  • 응답지연이 낮은 1 : 1 채팅 기능
  • 최대 100명까지 참여할 수 있는 그룹 채팅 기능
  • 사용자의 접속상태 표시 기능
  • 다양한 단말 지원. 하나의 계정으로 여러 단말에 동시 접속 지원
  • 푸시 알림
  • 5,000만 DAU 처리 가능

 

2. 개략적 설계안

채팅 서비스 기능

  • 클라이언트들로부터 메시지 수신
  • 메시지 수신자 결정 및 전달
  • 수신자가 접속 상태가 아닌 경우에는 접속할 떄까지 해당 메시지 보관

 

메세지 송신

  • Http Protocol 방식 사용
  • keep-alive 헤더 사용
더보기

keep-alive란?
Http Protocol은 TCP 기반으로 동작합니다. 즉, TCP 전송이 끝나면 연결이 끊어지기 때문에 Http도 마찬가지로 연결이 끊어집니다. 하지만 매번 같은 IP로 요청할 때마다 이처럼 맺고 끊고를 반복한다면 자원낭비가 심할 것 입니다. 이런 단점을 보완하기 위해 keep-alive가 탄생하였습니다. 

keep-alive는 최소 특정 시간동안 최대 요청을 알려줄 수 있습니다. 

예시) 최소 특정 시간: 2초, 최대 요청 500

최소 2초 동안은 500개의 요청이 들어와도 connection을 끊지 않고 유지할 수 있습니다.

 

Http 1.1부터는 기본설정으로 적용되어 있습니다.

  • 페이스북과 대중적 채팅 프로그램이 초기 사용했던 방식

 

메세지 수신을 위한 기법

1. polling

  • 주기적으로 호출(폴링)하기 때문에 불필요한 리소스 낭비가 심함

 

2. long polling

  • 폴링 기법 보완
  • 메세지를 보내는 클라이언트와 수신하는 클라이언트가 같은 채팅 서버에 접속하지 못할 수 있음
더보기

같은 채팅 서버에 접속하지 않아도 DB로 공유되니깐 상관없지 않은가??

  -> DB에 채팅 데이터를 가지고 있지 않음

  -> https://www.erlang-factory.com/upload/presentations/31/EugeneLetuchy-ErlangatFacebook.pdf 19, 20page 참조

  • 서버 입장에서는 클라이언트가 연결 해제 여부를 알 수 없음
  • 메세지를 많이 받지 않는 클라이언트도 타임아웃 일어날 때마다 주기적으로 서버에 재접속해야 하므로 비효율적

 

3. WebSocket

  • 웹소켓의 연결은 클라이언트가 진행하며 연결은 영구적 ( 초기에는 HTTP 연결이지만 특정 핸드세이크 절차를 커쳐 웹소켓 연결로 업그레이드 됨 )
  • 영구적 연결이 만들어지면 서버는 클라이언트에게 비동기적으로 메시지를 전송 가능
  • 웹소켓은 방화벽이 있는 환경에서도 정상 작동 ( 80(HTTP), 443(HTTPS) 그대로 사용 )
  • 단 예외처리를 효율적으로 진행해야함
    • 영구적 유지이기 때문에 서버에서 연결 관리를 잘해야함 ( 메모리.. )
    • 연결이 끊겼을 경우 재요청에 대한 프로세스도 필요

 

2-1. 무상태 서비스

  • 인증 서비스
  • 서비스 탐색
  • 그룹 관리
  • 사용자 프로파일

무상태 서비스는 로드밸런서 뒤에 위치

2-2 상태 유지 서비스

  • 채팅 서비스

각 클라이언트와 채팅 서버와 독립적인 네트워크 연결을 유지해야 함

2-3 제 3자 서비스 연동

  • 푸시 서비스

이번 장에서는 채팅 시스템이라는 서비스에 대한 정리에 포커스를 맞추는 장이니 푸시 서비스에 대한 설명은 생략

 

2-4 규모 확장성

트래픽 규모가 적은 경우 위에서 설명한 모든 기능을 한 대로 구현해도 무방, 하지만 대량의 트래픽을 처리할 경우에는 한 대로 구현하기 힘들 것이다.

 

  • 서버 한 대로 얼마나 많은 접속을 동시에 허용하는 지 체크!
  • SPOF
더보기

SPOF란?

 

Sing-Point_Of-Failure로 시스템 구성 요소 중에서 하나라도 정상적으로 작동하지 않으면 전체 시스템이 중단

 

가장 중요한 포인트는 클라이언트와 채팅 서버는 웹소켓 연결을 끊지 않고 유지해야 함

 

  • 채팅: 클라이언트 사이에 메세지를 중계하는 역할
  • 접속 상태: 사용자의 접속 여부를 관리
  • API: 로그인, 회원가입, 프로파일 변경 등 그 외 나머지 처리
  • 알림: 푸쉬
  • 키-값 저장소: 채팅 이력을 보관

 

2-5 저장소

채팅 서비스에 적합한 저장소는 무엇일까?

DB 성격
RDB - 안정성 보장
- 관계형 데이터
- 데이터 무결성, 정합성 보장, 높은 가용성
NoSql - 데이터의 양이 매우 많음
- 모든 데이터를 조회할 필요 X, 특정 검색을 통해 접근
- 수평적 확장이 용이

 

하나의 서비스에서 하나의 저장소만 선택하는 것은 아니다. 데이터의 유형에 따라 저장소를 선택하여 처리한다.

채팅 시스템은 2가지 유형의 데이터로 나뉜다.

 

일반적인 데이터

  • 사용자 프로파일, 설정, 친구 목록과 같은 데이터

 

RDB 사용

  • 안정성을 보장해야하는 RDB에 적재
  • 이런 일반적인 데이터의 가용성과 규모확장성을 보증하기 위해 다중화샤딩을 보편적으로 사용

 

고유한 데이터인 채팅 이력

  • 양이 많다. ( facebook -> 매일 600억개 )
  • 최근에 적재된 데이터 위주로 조회 ( but, 검색을 이용하여 특정 사용자, 특정 메시지 조회도 가능해야함 )
  • 1:1 채팅의 경우 쓰기, 읽기 비율을 1:1

 

키-값 저장소 사용

  • 수평적 규모확장이 쉬움
  • 데이터 접근 지연시간이 낮음
  • RDB는 인덱스가 커지면 데이터에 대한 무작위적 접근을 처리하는 비용이 높음
  • 안정적인 채팅 시스템이 키-값 저장소를 사용하고 있음

facebook은 HBase

더보기

https://www.facebook.com/notes/10158791457767200/

 

로그인 또는 가입하여 보기

Facebook에서 게시물, 사진 등을 확인하세요.

www.facebook.com

 

페이스북 현재 상태...

3억 5천명이 매달 150억개 메시지를 보낸다. 페이스북은 매달 1,200억개 메시지를 보내는 3억명의 유저까지 커버가능

 

페이스북은 모니터링을 통해 2가지 유형의 데이터를 찾게된다.

- 변덕스러운 유형의 짧은 시간의 제약을 받는 데이터

- access 할 수 없을 만큼 증가하는 데이터 셋

 

MySQL, Apache Cassandra, Apache HBase 중 선택의 고민하다 HBase 선택

- MySQL: 은 long tail의 데이터를 잘 처리하지 못하고 인덱스 및 데이터가 커지면 성능 저하 

- 카산드라: 메세지의 패턴이 카산드라의 궁극적 일관성 모델로 처리하기 어려운 패턴

 

HBase 장점

- 카산드라보다 더 단순한 일관성 모델링을 제공

- 관련 작업 부하에 매우 우수한 성능 검증

- 자동 로드밸런싱, 페일오버, 압축 지원, 서버당 다중 샤드 지원

디스코드는 카산드라

더보기

https://blog.discord.com/how-discord-stores-billions-of-messages-7fa6ec7ee4c7

 

How Discord Stores Billions of Messages

Our blog has moved to our main website! Check us out on discord.com/blog to stay in the loop about future posts.

blog.discord.com

 

양이 너무 많음... 읽다 포기.. ㅠ

대충 내용은 몽고를 쓰다가 한계가 와서 다른 디비로 교체하는데 그 와중에 자기들이 놓인 숙제들을 리스트업해놓고 가장 적합한 디비를 고르는 글.. 

결론 카산드라

 

데이터 모델

테이블 설계에 대한 내용이지만 구체적인 테이블 설계는 생략하고 ID에 대한 내용만 작성

  • message_id의 값은 유니크
  • ID값은 정렬 가능, 시간 순서와 일치

RDBMS에는 auto_increment가 있지만 NoSQL은 없다. ( MongoDB 썼을땐 ObjectId가 있었는데..? )

스노플레이크 사용

더보기

https://blog.twitter.com/engineering/en_us/a/2010/announcing-snowflake

 

Announcing Snowflake

Announcing Snowflake

blog.twitter.com

내용을 간단하게 요약하자면 카산드라에는 mysql 에서 지원하는 auto_increament 기능이 없다. 그래서 스노플레이크를 사용해서 만들었다. 

생성 방식은 조정되지 않은 방식으로 대략 정렬된 64비트 ID를 생성하기 위해 타임스탬프, 작업자 번호 및 시퀀스 번호의 구성을 결정한다.

 

3 상세 설계

서비스 탐색, 메시지 전달 흐름, 사용자 접속 상태 표시에 대해 상세 설계

 

3-1 서비스 탐색

클라이언트에게 가장 적합한 채팅 서버 추천

  • 클라이언트의 위치
  • 서버의 용량

 

서비스 탐색 기능으로 주키퍼와 같은 오픈 솔루션 사용

  1. 로그인 시도
  2. 로드밸런서가 로그인 요청을 API 서버에게 요청
  3. 인증 처리 완료 후 Service Discovery 동작 후 채팅 서버를 찾음
  4. 채팅 서버에 연결

 

3-2 메시지 흐름

1:1 채팅 메시지 처리 흐름

  1. 채팅 서버 1로 메시지 전송
  2. ID 생성기로 ID 생성
  3. 메세지를 메시지 동기화 큐로 전송
  4. 키-값 저장소에 보관
  5. (a) 사용자 B가 접속 중인 경우 메시지는 채팅 서버로 전송 (b) 접속 중이 아닌 경우 푸시 알림 서버를 통해 알림 푸시
  6. 채팅 서버2는 사용자 B에게 메시지 전송

 

여러 단말 사이의 메시지 동기화

  • 사용자 A는 테스크탑, 휴대폰으로 채팅 서버에 접속하고 있다.
  1. 먼저 데스크 탑으로 메시지를 전달
  2. (잠시 후) 휴대폰으로 메시지를 전달
  3. 채팅 서버에서는 653보다 높은 cur_max_message_id를 감지 후 user_id 체크
  4. 653과 951 id의 user_id가 동일한 것을 감지
  5. 데스크 탑 채팅서버에 사용자 A가 메시지 전달한 것으로 출력  

 

소규모 그룹 채팅에서의 메시지 흐름

송신자 관점

  1. 사용자 A가 채팅 서버1에 메시지를 전송
  2. 채팅 서버1은 2개의 메시지 동기화 큐에 각각 메시지를 보냄
  3. 메시지 동기화 큐는 각 사용자에게 메시지를 전달

이 경우는 소규모 그룹 채팅에 적합하다. 이유는 아래와 같다

  • 새로운 메시지가 왔는지 확인하려면 큐만 보면 됨 ( 메시지 동기화 플로우가 단순 )
  • 그룹이 크지 않으면 메시지를 수신자별로 복사해서 큐에 넣는 작업의 비용이 문제되지 않음

위쳇은 이런 방법을 사용하고 있다. ( 500명 제한 ) 만약 사용자 수가 많아진다면 사용자별 큐를 생성해야하므로 적합하지 않은 방법이다.

 

수신자 관점

위 방법을 수신자 관점에서 표현하면 위 그림과 같다. 즉 메시지 동기화 큐는 여러 사용자로부터 오는 메시지를 받을 수 있어야한다.

 

3-3 접속상태 표시

접속 상태 표시는 채팅 애플리케이션의 핵심적 기능이다. 사용자의 상태가 바뀌는 시나리오는 3가지가 있다. 하나씩 살펴보자.

 

사용자 로그인

  1. 사용자 로그인 시 접속 상태 서버와 웹소켓이 맺어짐
  2. 접속상태 서버는 키-값 저장소에 접속 상태와 마지막 행동 시간을 저장

 

로그아웃

  1. 사용자가 로그아웃 요청
  2. 키-값 저장소에 접속 상태가 online에서 offline으로 변경

 

접속 장애

간단하게 먼저 생각해보자

- 웹소켓이 끊어지면 offline, 웹소켓이 연결되면 online으로 하면 된... 안된다.  

짧은 시간 동안 인터넷 연결이 끊어졌다 보구되는 일은 흔히 발생 -> heartbeat event를 사용하자

  1. 클라이언트에서 접속상태 서버로 일정 주기마다 event 발생시킴
  2. 일정 주기 동안 event가 들어오지 않아도 online 상태 유지
  3. 설정한 시간 이상으로 event가 들어오지 않으면 offline으로 변경
  4. 다시 event가 들어오면 online으로 변경

 

상태 정보의 전송

사용자 A가 온라인, 오프라인 상태를 변경하는 로직에 대해 알아봤다. 그렇다면 사용자 A의 친구들은 어떻게 사용자 A의 상태값을 알 수 있을까?

 

상태정보 서버는 pub-sub model을 사용한다. 각각의 친구관계마다 채널을 하나씩 두는 방식을 이용한다.

이 방식은 소규모 일 경우 효과적인 방법이다.

 

대규모일 경우?

그룹채팅에 입장하는 순간에만 상태를 읽어오거나 수동으로 갱신하고 싶을 때 갱신하는 방법을 사용하도록 유도하는 것이다.

 

그 외에 논의하면 재밌을 것

  • 사진이나 비디오 등의 미디어 지원방법
    • 압축 방식, 클라우드 저장소, 썸네일 생성
  • 종단 간 암호화
    • 메시지 발신인과 수신자 이외에는 아무도 메시지 내용을 볼 수 없게 설계
  • 캐시
    • 클라이언트에 이미 읽은 메시지를 캐시해 두어 서버의 부하를 줄임
  • 로딩 속도 개선
    • 슬랙은 사용자의 데이터, 채널 등을 지역적으로 분산하는 네트워크를 구축하여 앱 로딩 속도를 개선
    • 더보기
      속도 이슈의 키는 클라이언트에서 최소한의 정보만 로드해오는 방식, 그리고 이후에 채널과 유저에 대한 정보가 필요할 때 로드한다.

      1. 클라이언트 시작 시 사용자, 채널, 봇등의 데이터를 캐싱한다.
      2. 그 다음 요청 시 클라이언트가 가져올 쿼리 API를 제공한다.
      3. 다음에 요청될 데이터를 예측하고 고객에게 데이터를 푸시한다.

      예) 채널에서 동료에 대해 mention한다는 가정...
      해당 메시지를 채널에 있는 사람들에게 모두 브로드캐스팅하는 동안 일부 클라이언트가 최근에 mention된 사용자에 대한 정보를 로드하지 않는 것을 확인한다. 메시지를 보내기 직전에 해당 클라이언트에게 사용자 데이터를 전송하여 round-trip query를 저장한다. 

      헉.. 뭔소리인지 하나도 모르겠다. 한국어인가.. 
  • 오류 처리
    • 채팅 서버 오류: 채팅 서버 하나에 수십만 사용자가 접속해 있는 상황이에 그 서버가 죽는다면 Discovery 기능(주키퍼)이 동작하여 클라이언트에게 새로운 서버를 배정하고 재접속할 수 있도록 제공
    • 메시지 재전송: 재시도나 큐로 메시지의 안정적 전송을 보장한다.

 

 

long tail이란?

더보기

MaxCompute SQL의 JOIN 문이 실행되면 동일한 JOIN 키를 가진 데이터가 동일한 인스턴스로 전송되어 처리된다. 키에 많은 양의 데이터가 포함된 경우 관련 인스턴스는 다른 인스턴스보다 데이터를 처리하는 데 시간이 더 오래 걸립니다. 실행 로그에서 이 조인 작업의 일부 인스턴스는 실행 상태로 유지되는 반면 다른 인스턴스는 완료된 상태로 유지됩니다. 이것은 긴 꼬리라고 불립니다.

 

반응형