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

채팅 시스템 설계

막이86 2023. 11. 9. 17:49
728x90

가상 면접 사례로 배우는 대규모 시스템 설계 기초 "채팅 시스템 설계"을 요약한 내용입니다.

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

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

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

  • 채팅 서비스는 아래 기능을 제공
    • 클라이언트로부터 메시지 수신
    • 메시지 수신자 결정 및 전달
    • 수신자가 접속 상태가 아닌 경우에는 접속할 때까지 해당 메시지 보관

폴링

  • 폴링은 클라이언트가 주기적으로 서버에 새 메시지가 있는지 확인
  • 폴링 비용은 폴링을 자주하면 할수록 올라간다
    • 답해줄 메시지가 없는 경우에는 서버 자원이 불필요하게 낭비됨

롱 폴링

  • 폴링은 여러가지로 비효율적일 수 있어서 나온 기법이 롱 롤링(Long Polling)이다
  • 클라이언트는 새 메시지가 반환되거나 타임아웃 될 때까지 연결을 유지
  • 새 메시지를 받으면 기존 연결을 종료하고 서버에 새로운 요청을 보냄

단점

  • 메시지를 보내는 클라이언트와 수신하는 클라이언트가 같은 채팅 서버에 접속하게 되지 않을 수 있다.
    • 메시지를 받은 서버는 해당 메시지를 수신할 클라이언트와 롱폴링 연결을 가지고 있지 않은 서버일 수 있다.
  • 서버 입장에서는 클라이언트가 연경을 해제했는지 아닌지 알 좋은 방법이 없다.

웹소켓

  • 서버가 클라이언트에게 비동기 메시지를 보낼 때 가장 널리 사용하는 기술
  • 한번 맺어진 연결은 항구적이며 양방향이다.
  • 처음에는 HTTP 연결이지만 특정 핸드셰이크 절차를 거쳐 웹소켓 연결로 업그레이드 된다.
  • 항구적인 연결이 만들어지고 나면 서버는 클라이언트에게 비동기적으로 메시지를 전송할 수 있다.
  • 웹소켓은 일반적으로 방화벽이 있는 환경에서도 잘 동작한다.
    • 80이나 443처럼 HTTP 혹은 HTTPS 프로토콜이 사용하는 기본 포트번호를 그대로 쓰기 때문
  • 웹소켓은 양방향 메시지 전송이 가능하므로 웹소켓 대신 HTTP를 굳이 고집할 이유는 없음
  • 유의할 것은 웹소켓 연결은 항구적으로 유지되어야 하기 때문에 서버측에서 연결 관리를 효율적으로 해야함

개략적 설계안

  • 채팅 기능의 경우 웹소켓을 사용하는 것이 효율적임
  • 그외 대부분의 기능(회원가입, 로그인, 사용자 프로파일 등)은 일반적인 HTTP에서 구현해도 됨
  • 전체 시스템의 개략적 설계안을 살펴보자
    • 채팅 시스템은 세 부분으로 나누어짐
    • 무상태 서비스, 상태유지 서비스, 제 3자 서비스 연동

무상태 서비스

  • 로그인, 회원가입, 사용자 프로파일 표시 등의 처리하는 서비스
  • 무상태 서비스는 로드밸런서 뒤에 위치
  • ‘서비스 탐색(Service Discovery)’ 서비스는 클라이언트가 접속할 채팅 서버의 DNS 호스트명을 클라이언트에게 알려주는 역할

상태 유지 서비스

  • 유일하게 상태 유지가 필요한 서비스는 채팅 서비스
  • 각 클라이언트가 채팅 서버와 독립적인 네트워크 연결을 유지해야 하기 때문
  • 클라이언트는 서버가 살아 있는 한 다른 서버로 연결을 변경하지 않음
  • 서비스 탐색 서비스는 채팅 서비스와 긴밀히 협력하여 특정 서버에 부하가 몰리지 않도록 한다.

제 3자 서비스 연동

  • 제 3자 서비스는 푸시 알림이다.
  • 새 메시지를 받았다면 앱이 실행 중이지 않더라도 알림을 받아야 한다.
  • ‘알림 시스템 설계’ 참고

규모 확장성

  • 트래픽 규모가 얼마 되지 않을 때는 모든 기능을 서버 한대로 구현할 수 있음
    • 면접에서 원하는 답변은 아닐 것
    • 보통은 규모가 있는 트래픽을 서버 한 대로 처리하려고 하지 않을 것
    • SPOF(Single Point Of Failure)가 발생 할 수 있음
  • [그림 12-8]
    • 실시간으로 메시지를 주고받기 위해 클라이언트는 채팅 서버와 웹소켓 연결을 끊지 않고 유지 해야함
    • 채팅 서버는 클라이언트 사이에 메시지를 중계하는 역활을 담당
    • 접속상태 서비스는 사용자의 접속 여부를 관리
    • API 서버는 로그인, 회원가입, 프로파일 변경 등 그외 나머지 전부를 처리
    • 알림 서버는 푸시 알림을 보낸다
    • 키-값 저장소에는 채팅 이력을 보관
    • 시스템에 접속한 사용자는 이전 채팅 이력을 전부 보게 될 것

저장소

  • 관계형 데이터베이스를 쓸것 인가 아니면 NoSQL을 채택할 것인가?
    • 중요하게 따져야할 것은 데이터의 유형과 읽기/쓰기 연산의 패턴
  • 채팅 시스템이 다루는 데이터는 보통 두가지 이다.
    • 사용자 프로파일, 설정, 친구 목록 처럼 일반적인 데이터
      • 관계형 데이터베이스에 보관
    • 채팅 이력
      • 채팅 이력 데이터의 양은 엄청나다
        • 페이스북 메신저나 와츠앱은 매일 600억 개의 메시지를 처리
      • 빈번하게 사용되는 것은 주로 최근에 주고받은 메시지
        • 대부분은 오래된 메시지를 들여다보지 않는다.
      • 사용자는 대체로 최근에 주고받은 메시지 데이터만 보게 되는 것이 사실이나, 검색 기능을 이용하거나, 특정 사용자가 언급된 메시지를 보거나, 특정 메시지로 점프 하거나 하여 무작위적인 데이터 접근을 하게 되는 일도 있음
      • 1:1 채팅 앱의 경우 읽기:쓰기 비율이 대략 1:1 정도
      • 이런 기능을 지원하기 위해서는 키-값 저장소를 추천
        • 키-값 저장소는 수평적 규모 확장이 쉬움
        • 데이터 접근 지연시간이 낮다
        • 관계형 데이터베이스는 데이터 가운데 롱 테일에 해당하는 부분을 잘 처리하지 못하는 경향이 있음
        • 관계형 데이터베이스는 인덱스가 커지면 데이터에 대한 무작위적 접근을 처리하는 비용이 늘어난다
        • 많은 안정적인 채팅 시스템이 키-값 저장소를 채택하고 있음
          • 페이스북 메시저(HBase), 디스코드(Cassandra) 등

데이터 모델

  • 1:1 채팅을 위한 메시지 테이블
    • message_id: bigint
      • 기본 키, 메시지 순서를 쉽게 정할 수 있도록 하는 역활
    • message_from: bigint
    • message_to: bigint
    • content: text
    • create_at: timestamp
  • 그룹 채팅을 위한 메시지 테이블
    • channel_id: bigint
      • 파티션 키로도 사용할 수 있음
    • message_id: bigint
    • message_from: bigint
    • message_to: bigint
    • content: text
    • create_at: timestamp
    • channel_id, message_id는 복합키를 기본키로 사용

메시지 ID

  • message_id는 고유해야 한다
  • ID 값은 정렬 가능해야 하며 시간 순서와 일치해야 한다.
  • 새로운 ID는 이전 ID 값보다 큰 값이어야 한다.
  • RDBMS의 경우 auto_increment가 대안, NoSQL은 해당 기능을 제공하지 않음
  • 스노플레이크 같은 전역적 64-bit 순서 번호 생성기를 이용하는 것
  • 지역적 순서 번호 생성기를 이용하는 것
    • 여기서 지역적이라 함은 ID의 유얼성은 같은 그룹 안에서만 보증하면 충분하다는 것
    • 메시지 사이의 순서는 같은 채널 혹은 같은 1:1 채팅 세션 안에서만 유지되면 충분하기 때문
    • 전역적 ID 생성기에 비해 구현하기 쉬운 접근법

3단계 상세 설계

서비스 탐색

  • 서비스 탐색 기능의 주된 역활은 클라이언트에게 가장 적합한 채팅 서버를 추천하는 것
  • 추천하는 기준으로는 클라이언트의 위치, 서버의 용량 등이 있다.
  • 널리 쓰이는 오픈 소스 솔루션으로는 아파치 주키퍼 같은 것이 있다.
  • 사용 가능한 모든 채팅서버를 여기에 등록시켜 두고, 클라이언트가 접속을 시도하면 사전에 정한 기준에 따라 최적의 채팅 서버를 골라 주면 된다.
  • [그림 12-11]은 주키퍼로 구현한 서비스 탐색 기능이 어떻게 동작하는지 보여준다.
    • 사용자가 A 시스템에 로그인
    • 로드밸런서가 로그인 요청을 API 서버들 가운데 하나로 보낸다.
    • API 서버가 사용자 인증을 처리하고 나면 서비스 탐색 기능이 동작하여 해당 사용자를 서비스할 최적의 채팅 서버를 찾는다.
    • 사용자 A는 채팅 서버 2와 웹소켓 연결을 맺는다.

메시지 흐름

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

  • [그림 12-12]는 1:1 채팅에서 사용자 A가 B에게 보낸 메시지가 어떤 경로로 처리 되는지 보여줌
    • 사용자 A가 채팅 서버 1로 메시지 전송
    • 채팅 서버 1은 ID 생성기를 사용해 해당 메시지의 ID 결정
    • 채팅 서버 1은 해당 메시지를 메시지 동기화 큐로 전송
    • 메시지가 키-값 저장소에 보관됨
    • 사용자 B가 접속 중인 경우 메시지는 사용자 B가 접속중인 채팅 서버로 전송됨
    • 사용자 B가 접속 중이 아닌 경우 푸시 알림 메시지를 푸시 알림 서버로 보냄
    • 채팅 서버 2는 메시지를 사용자 B에게 전송, 사용자 B와 채팅 서버 2 사이에는 웹소켓 연결이 있는 상태이므로 그것을 이용
728x90