가상 면접 사례로 배우는 대규모 시스테 설계 기초 #12 (채팅 시스템 설계)

박주진·2022년 6월 26일
0

문제 이해 및 설계 범위 확정

어떤 채팅 앱?

  • 1:1 과 그룹 채팅 모두 지원 하는 앱

모바일 앱 or 웹 앱

  • 둘다 지원

트래픽 규모?

  • DAU 5천만

그룹채팅 인원 제한

  • 최대 100명

중요 기능은?

  • 1:1 채팅, 그룹 채팅, 사용자 접속 상태 표시, 텍스트 메시지만 가능

메시지 길이 제한?

  • 100,000자 이하

종단 간 암호화(end-to-end encryption) 필요?

  • 현재는 필요없다. 나중에 시간이 남으면 논의 가능

채팅 이력 보관 기간

  • 영원히

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

  • 클라이언트와 서버와의 통신 방법에 대한 기본적인 지식이 있어야 훌륭한 답을 낼수 있다.
  • 채팅 시스템의 경우 클라이언트는 서로 직접 통신 하지 않고 각각 채팅 서비스와 통신한다.
기본적인 채팅 서비스 기능
  • 클라이언트들로 부터 메시지 수신
  • 메시지 수신자 결정 및 전달
  • 수신자가 접속 상태가 아닌 경우 접속할 때까지 해당 메시지 보관

클라이언트와 메시지 서비스와의 관계도

[sender]메시지 송신 클라이언트 (메시지)-> 채팅 서비스 (메시지)-> [receiver]메시지 수신 클라이언트

메시지 송신

  • 송신 클라이언트는 오래 세월 검증된 HTTP 프로토콜을 사용해서 구현할 수 있다.
    • 메시지 송신시에 어떤 네트워크 프로토콜을 사용할지도 중요한 문제임으로 면접관과 상의 필요
    • facebook과 같은 채팅 프로그램도 초기에는 HTTP 사용했다.
    • keep-alive 헤더를 유지하면 클라이언트와 서버 사이의 연결을 끊지 않고 계속 유지할 수 있어 효율적이다. 연결이 유지되면 TCP 접속 과정에서 발생하는 핸드셰이크 횟수를 줄일 수 있다.

메시지 수신

  • HTTP는 클라이언트가 연결을 만든는 프로토콜이기 때문에 서버에서 클라언트에 임의시점에 메시지를 보내는 데는 쓰기 어렵다.
  • 서버가 연결을 만드는 것처럼 동작할 수 있도록 폴링, 롱폴링, 웹소켓 기법이 제안되었다.
폴링
  • 클라이언트가 주직적으로 서버에게 새 메시지가 있는냐고 물어보는 방식
  • 이 방식은 폴링을 자주하면 할수록 서버/클라이언트 모두 비용이 늘어난다.
롤폴링
  • 클라이언트가 새 메시지가 반환되거나 타임아웃 될 때까지 연결을 유지하는 방식이다. 클라이언트가 새메시지를 받으면 기존 연결을 종료하고 모든 절차를 다시 시작한다.
  • 메시지 송신 클라이언트와 수신 클라이언트가 같은 채팅서버에 접속하지 않을 수 있다. 왜냐하면 HTTP서버 들은 보통 stateless 이고 로드밸런싱시 round robin방식을 채택한 경우 연결되는 채팅 서버는 계속 바뀔수 있기 때문이다.
  • 서버 입장에서 클라이언트가 연결을 해제했는지 아닌지 알 좋은 방법이 없다??
  • 타임아웃이 일어날 때마다 주기적으로 서버에 다시 접속할 것 임으로 여전히 비효율적이다.
웹소켓
  • 서버가 비동기 메시지를 보낼때 가장 널리 사용되는 기술이다.
  • 클라이언트가 웹소켓 연결을 시작하고 한번 맺어진 연결은 계속 유지되고 양방향 이다.
  • 처음 연결에는 http,https 연결이지만 핸드셰이크 절차를 거쳐 웹소켓 연결로 업그레이드 되다.
  • http 프로토콜이 메시지 송신 클라이언트 측에서는 괜찮은 방법이라고 했지만 메시시 송신시에도 엡소켓을 이용하면 좋다. 왜냐하면 메시지 송수신시 같은 프로토콜을 사용하면 설계와 구현이 단순하고 직관적이여 진다. 하지만 웹소켓 연결은 계속 유지되여야 하기 때문에 서버 측에서 연결 관리를 효율적으로 해야 한다.

개략적 설계안

  • 대부분의 기능 (회원가입, 로그인 등)은 일반적인 HTTP 프로토콜 사용 채팅 부분만 웹소켓 이용
  • 채팅 시스템은 무상태 서비스, 상태유지 서비스, 제3자 서비스 연동 이렇게 세부분으로 나눠서 볼 수 있다.

무상태 서비스

  • 로그인 , 회원가입등 전통적인 요청/응답 서비스를 구성하는 부분이다.
  • 이중 조금 특이한 서비스는 서비스 탐색 서비스인데 해당 서비스를 통해 클라이언트가 접속할 채팅 서버의 DNS 호스트명을 클라이언트에 알려줄 수 있다.

상태 유지 서비스

  • 채팅 서비스만 유일하게 상태 유지가 필요하다.
  • 서비스 탐색 서비스는 채팅 서비스와 긴밀히 협력하여 특정 서버에 부하가 몰리지 않게 해야 한다.

제3자 서비스 연동

  • 푸시 알림이 대표적이다.

규모 확장성

  • 트래픽 규모가 얼마 되지 않는다면 모든 기능을 서버 한대로 구현 할 수 있다. 또한 대용량 트래픽일 경우에도 이론적으로 최신 클라우드 서버 한대로 처리가 가능하긴 하다.
  • 한대의 서버로 트래픽을 감당할 수 있더라도 SPOF와 같은 이유로 서버 한대만 담은 설계안은 좋은 점수를 받기 힘들다.
  • 이번 장에서 대략적인 추산치는 동시접속자 1M, 접속당 10K의 서버 메모리 필요, 10GB메모리만 있으면 모든 연결 처리 가능

개략적 설계안

  • chat servers는 클라이언트와의 메시지 중계역할 담당
  • presence server는 사용자의 접속 여부 관리 담당
  • api server는 로그인, 회원가입, 프로파일 변경등 나머지 처리
  • Notification servers는 푸시 발송
  • kv store는 채팅 이력 보관

저장소

  • 어떤 데이터 베이스를 쓰느냐 ? SQL vs NOSQL
  • 데이터의 유형과 읽기/쓰기 연산 패턴을 기반으로 결정해야 한다.
  • 채팅 시스템의 데이터 형태는 2가지 이다.
    • 사용자 프로파일, 설정, 친구 목록과 같은 일반적인 데이터
    • 채팅 이력과 같은 채팅 시스템에 고유한 데이터
      • 채팅 이력은 데이터 양이 엄청나다. 페이스북, 왓츠앱은 매일 60billion 개의 메시지를 처리한다.
      • 최근에 주고 받은 메시지만 빈번하게 사용된다.
      • 검색, 언급등으르 특정 메시지로 점프하여 무작위적인 데이터 접근도 많다.
      • 쓰기와 읽기가 1:1: 비율이다.
  • 일반적인 데이터는 안정성을 보장하는 관계형 데이터베이스 추천
  • 채팅 이력은 키-값 저장소 추천
    • 데이터 접근 지연시간이 낮다.
    • 관계형 데이터베이스는 데이터 중 롱 테일에 해당하는 부분을 잘 처리 못한다. 그리고 인덱스가 커지면 무작위적 접근을 처리하는 비용이 늘어난다.
    • 이미 많은 안정적인 채팅 시스템이 키-값 저장소 채택. 예) 페이스북 - Hbase, 디스코드 - cassandra

데이터 모델

1:1 채팅을 위한 메시지 테이블
message
message_idbigint
message_frombigint
message_tobigint
contenttext
created_attimestamp
그룹 채팅을 위한 메시지 테이블
group message
channel_idbigint
message_idbigint
message_tobigint
contenttext
created_attimestamp
메시지 ID
  • 값이 고유해야한다.
  • 시간 순서와 일치해야 한다. 즉 새로운 ID는 이전 ID보다 큰값이어야 한다.
  • 3가지 방안
    • rdbms라면 auto_increment
    • snow flake와 같은 global sequence number generator
    • local sequence number generator도 가능하다. 왜냐하면 ID유일성은 같은 채팅 세션 안에서만 보증하면 충분하기 때문이다.

상세 설계

  • 서비스 탐색, 메시지 전달 흐름, 사용자 접속 상태 표시 방법에 대해 좀 더 상세하게 살표보자

서비스 탐색

  • 주된 기능은 클라이언트에 가장 적합한 채팅 서버를 위치, 서버 용량을 기준으로 추천하는 것이다.
  • 아파치 주키퍼가 가장 널리 쓰이는 오픈 소스 솔루션이다.
  1. 사용자 A가 시스템에 로그인한다.
  2. 로드 밸런스가 로그인 요청을 API 서버중 하나로 보낸다
  3. 인증후 서비스 탐색 기능이 동작하여 해당 사용자에게 서비스 할 최적의 채팅서버를 찾는다. 여기서는 채팅 server 2가 선정되고 해당 서버 정보가 USER A에게 반환된다.
  4. USER A가 채팅 server2에 연결한다.

메시지 흐름

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

  1. 사용자 A가 chat-server1 로 메시지 전송
  2. chat-server1은 ID 생성기를 사용해 해당 Message ID 결정
  3. chat-server1은 해당 메시지를 message queue로 전송
  4. k-v저장소에 메시지를 보관
  5. a) 사용자가 online이면 사용자 b가 접속 중인 chat-server로 메시지 전송
    b) 사용자가 onffline이면 push server로 메시지 전송
  6. chat-server2는 메시지를 사용자 b에게 전송 (기존에 있는 웹소켓 연결 사용)

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

  • 각 단말은(phoe, laptop) cur_max_message라는 가장 최신 메시지 ID를 추적하는 변수를 가진다.
  • 아래의 두 조건을 만족하는 경우 새 메세지로 간주된다.
    • 수신자 ID가 현재 로그인 사용자와 ID와 같다.
    • 키-값 저장소에 저장된 메시지면서 그 ID가 cur_max_message_id 보다 크다.
  • cur_max_message_id는 단말마다 별도로 유지 관리됨으로, 메시지 동기화도 쉽다. (각 단말기 별로 새로운 메시지를 cur_max_message_id기반으로 k-v store에서 가져오면 되니까)

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

그룹에 3명의 사용자가 있다고 하자.(A,B,C)

  • 사용자 A가 보낸 메시지가 사용자 B,C 메시지 동기화 큐에 복사된다. (메시지 수신함과 비슷하다)
  • 이 설계는 소규모 그룹 채팅에 적합하다. 이유는 다음과 같다.
    • 메시지 동기화 플로우가 단순하다. 왜냐하면 새로운 메시지 확인을 위해 자시 큐만 확인하면 되니까.
    • 그룹이 크지 않으면 메시지를 수신자별로 복사하여 큐에 넣는 작업의 비용이 문제가 되지 않는다.
  • wechat은 이런 접근법을 사용하고 있고 크기는 500명 제한이다. (많으 그룹인원을 지원한다면 메시지를 유저별로 다 복사하는 방식은 문제가 될것이다.)
  • 메시지 동기화큐는 여러 사용자로 부터 오는 모든 메시지를 받을 수 있도록 해야 한다.

접속상태 표시

  • 접속상태 표시는 상당수 채팅 어플리케이션의 핵심 기능이다.
  • 접속상태 서버는 클라이언트와 웹소켓으로 통신하는 실시간 서비스의 일부이다.

사용자 로그인

  • 실시간 서비스와 웹소켓 연결이 맺어지고나면 접속 상태서버는 상태와 last_active_at timestamp 값을 k-v 저장소에 보관한다. 이 절차 후에는 해당 사용자가 접속 중인걸로 표시될 것이다.

사용자 로그아웃

  • Api 서버를 이용해서 로그아웃 하게 되면 api 서버에서 접속상태 서버로 해당 유저의 status를 offline으로 변경 요청을 한다.

접속 장애

  • 사용자의 인터넷 연결이 끊어지면 클라이언트와 서버 사이에 맺어진 웹소켓 같은 지속성 연결도 끊어진다.
  • 간단한 방법은 연결이 끊어지면 오프라인으로 처리하고 연결이 복구되면 온라인으로 처리하는 것이지만 짧은 시간 동안의 인터넷 연결 문제가 일어날때마다 접속 상태를 변경한다는 것은 지나치 일이고 사용자 경험측면에도 좋지않다.
  • 아래 그림과 같은 박동검사를 통해 문제를 해결할 수 있다.
    • 클라이언트는 주기적으로(5s) 박동 이벤트를 서버로 보낸다.
    • x초(5s) 이내에 박동 이벤트를 받으면 해당 사용자의 접속상태를 온라인을 유지한다.
    • 특정 초(5s) 이내에 박동 이벤트가 없으면 오프라인으로 간주하고 상태를 변경한다.

상태 정보의 전송

  • 상태정보 서버는 발행-구독 모델을 사용해서 친구 관계에 있는 사용자들에게 특정 사용자의 상태 변화를 전파한다.
  • 각각의 친구관계마다 채널을 하나씩 두는 방식이다.
  • 방의 그룹 크기가 작을 때는 효과적이나 그룹의 크기가 커지면 접속상태 변화를 알리는 비용이나 시간이 많이 들게 된다.
  • 성능문제를 해결하는 방안으로는 그룹 채팅에 입장하는 순간에만 상태 정보를 읽어가게 하거나, 리스트에 있는 사용자의 접속상태를 갱신을 수동으로 하게 유도하는것이다.

마무리

사진, 비디오 등의 미디어를 지원하는 방법

  • 미디어 파일은 파일의 크기가 크기 때문에 압축방식, 클라우드 저장소, 섬네일 생성 등을 논의해보면 재밌을 것 이다.

종단 간 암호화

  • 왓츠앱은 메시지 전송에 있어 종단간 암호화를 지원한다. 즉 발신인과 수신인 이외에는 아무도 메시지 내용을 볼 수 없다는 뜻

캐시

  • 클라이언트에 이미 읽은 메시지를 캐시해 두면 서버와 주고받는 데이터 양을 줄일 수 있다.

로딩 속도 개선

  • 슬랙은 사용자 데이터, 채널 등을 지역적으로 분산하는 네트워크를 구축하여 로딩 속도를 개선 하였다.

오류 처리

  • 채팅 서버 오류: 채팅 서버 하나에 수십만 사용자가 접속해 있는 상황에서는 하나의 서버가 죽었을때 서비스 탐색 기능이 동작하여 클라이언트에 새로운 서버를 배정하고 접속할 수 있돌고 해야 한다.
  • 매시지 재전송: 재시도나 큐는 메시지의 안정적 전송을 보장하기 위해 사용되는 기법이다.

질문

  • 폴링 또한 수신 클라이언트와 송신 클라이너트가 같이 서버에 접속하지 않을 경우가 존재하지 않나? 이게 문제인가?
  • 관계형 데이터베이스 롱테일 왜 random access 문제가 있나? 그냥 rdb가 index가 커지면서 해당 데이터를 찾는 과정이 느려지는 거아닐까? nosql는 데이터가 분산되어 잇으니 즉 파티션닝 되어있으니 좀 더 빠른거?
  • 여러 단말 사이의 메시지 동기화는 어떤 식으로?
    • 같은 웹소켓에 커넥션에 붙어 있을까?
  • 대규모 그룹 채팅 메시지 흐름은 그럼 어떻게?
  • 접속상태 서버
    • 여기서 사용하는 웹소켓 연결은 채팅 서버 연결과 같은 커넥션? 아니면 접속 상태를 위한 개별 커넥션?
    • 박동 이벤트를 이용한다면 서버에서 push가 필요한 경우는 상태 변경을 broadcast할때 뿐 이지 않을까? 박동 이벤트 부분도 웹소켓으로 해야 하는가? http로 주기적으로 보내는게 더 비용이 클까?
  • 왜 미디어를 지원하면 cloud storage를 사용해야 할까?

채팅 서버끼리 통신은 어떻게 할까?

http 동기식

  • 단점
    • 메시지 순서가 보장되지 않는다.
    • 메시지가 많다면 트래픽이 웹서버에 몰릴수 있다.

중간에 큐를 두는 형식

  • 장점
    • 이방식은 순서 보장을 할 수 있고 중간에 버퍼 작용을 함으로써 트래픽 조절이 가능하다. 하지만 아래와 같은 단점은 존재한다.
  • 단점
    • 대규모 서비스일 경우 큐를 관리하기 까다롭다.
    • 웹서버 하나가 중간에 죽을시 사용자는 새로운 웹소켓 서버에 연결할 텐데 그때 큐에 남겨진 메시지를 어떻게 처리할것 인가?

htttp 동기식에 순서 보장방식

  • previous messageId를 항상 같이 보내서 client가 메시지를 받을때 마다 순서가 맞는지 검사한다. 만약에 안맞을시 전체 히스토리를 다시 서버에 요청해서 받는다. (이방식이 트래픽까지 문제까지 해결할 수 있을까?)
    링크

0개의 댓글