본문 바로가기
대규모 시스템 설계 기초

대규모 시스템 설계 기초 - 12장

by cafecortado 2025. 4. 20.

12장. 채팅 시스템 설계

 

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

면접관과 대화를 통해 도출한 요구사항:

  • 1:1 채팅과 그룹 채팅 모두 지원
  • 모바일 앱 및 웹 앱 모두 지원
  • 하루 5천만 명의 DAU를 지원
  • 그룹 채팅의 최대 인원은 100명
  • 첨부파일 지원 없이 텍스트 메시지만 지원
  • 사용자 접속상태 표시 지원
  • 메시지 길이는 10만 글자를 넘지 않아야 함
  • 채팅 이력은 영구 보관

이 장에서 설계할 시스템의 기능 요약:

  • 응답 지연이 낮은 일대일 채팅 기능
  • 최대 100명까지 참여 가능한 그룹 채팅 기능
  • 사용자의 접속상태 표시 기능
  • 다양한 단말 지원 (하나의 계정의로 여러 단말에 동시 접속 지원)
  • 푸시 알림

 

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

클라이언트들은 아래 기본 기능을 제공하는 채팅 서비스와 통신:

  • 다른 클라이언트로부터 메시지 수신
  • 메시지 수신자를 결정 및 메시지 전달
  • 수신자가 접속 상태가 아닌 경우, 해당 메시지를 보관했다가 나중에 전달

네트워크 통신 프로토콜 선택:

  • 메세지 전송은 HTTP 사용 가능
  • 메세지 수신은 HTTP 사용하기 어려움 --> 폴링, 롱 폴링, 웹소켓 등이 필요
    (HTTP 연결은 클라이언트가 요청해야 하는데, 수신은 서버에서 클라이언트로 메세지를 보내는 것)

폴링:

  • 주기적으로 서버에 메세지가 있는지 물어봄
  • 폴링을 자주 할수록 비용 증가

 

롱 폴링:

  • 클라이언트는 새 메시지가 도착하거나 타임아웃에 도달할 때까지 연결을 계속 유지
  • 새 메시지를 받으면, 클라이언트는 즉시 서버에 또 다른 요청을 보내서 이 과정을 다시 시작
  • 단점:
    • HTTP 서버들은 보통 무상태 서버, 로드 밸런서가 여러 개의 서버에 요청을 분산시키는 구조
      --> 메세지를 받은 서버가 수신할 클라이언트와 연결되지 않은 서버일 수 있음
    • 서버는 클라이언트가 연결을 해제했는지 알 수 없음 (zombie connection)
      클라이언트 연결이 끊겼어도 서버는 타임아웃될 때까지 메세지를 기다리고 반환할 준비함
    • 메세지를 많이 받지 않는 클라이언트도 주기적으로 연결을 반복하므로 비효율적

웹소켓:

  • 서버가 클라이언트에 비동기 메세지를 보낼 때 가장 널리 사용하는 기술
  • 클라이언트가 HTTP로 연결 시작, 핸드셰이크 절차를 걸쳐 웹소켓 연결로 업그레이드됨
  • 영구적인 양방향 연결 --> 서버 측에서 연결 관리를 효율적으로 해야 함
  • 방화벽 환경에서도 잘 동작함
  • 전송에서도 사용될 수 있음
    (전송, 수신 모두 동일하게 웹소켓 프로토콜 사용하면 구현이 단순해짐)

 

 

 

개략적 설계안:

  • 무상태 서비스:
    • 로그인, 회원가입, 프로필 변경 등 웹사이트 및 앱이 보편적으로 제공하는 기능들을 처리
    • HTTP 기반의 전통적인 API 요청/응답 방식
    • 로드 밸런서 뒤에 위치
    • 시장에 완제품으로 나와 있어서 직접 구현하지 않고 구매하여 사용 가능
    • e.g. 클라이언트가 접속할 채팅 서버의 DNS 호스트명을 클라이언트에게 알려주는 서비스 탐색 서비스
  • 상태 유지 서비스:
    • 채팅 서비스가 이에 해당
      (채팅 클라이언트가 서버와 지속적인 웹소켓 연결을 유지하고 있기 때문에
      채팅 서버는 각 클라이언트의 연결 상태를 기억해야 함)
    • 서비스 탐색 서비스와 협력하여 특정 서버에 부하가 몰리지 않도록 함
  • 제3자 서비스 연동
    • 푸시알림이 이에 해당
    • 새 메세지를 받았다면 앱이 실행 중이지 않더라도 알림을 받아야 함
  • 규모 확장성
    • 소규모일 때는 앞서 설명한 모든 기능들을 하나의 서버에서 구현 가능
    • 이론상 동시접속자가 100만명이고 접속 당 10KB의 메모리를 사용한다고 가정하면, 10GB 메모리 만으로 모든 연결 처리 가능
    • 하지만 SPOF 등의 문제로 한 대의 서버에서 모든 트래픽을 처리하도록 설계하는 것은 좋지 않음, 규모 확장성을 고려해야 함

  •  
    • 전체 설계:
      • 클라이언트는 채팅 서버와 지속적인 웹소켓 연결을 유지
      • 채팅 서버는 클라이언트 사이에 메시지 전송과 수신을 중계
      • 접속 상태 서버는 사용자의 접속 여부 관리
      • API 서버는 로그인, 회원가입, 프로필 변경 등 나머지 전부를 처리
      • 알림 서버는 푸시 알림 전송
      • 키-값 저장소는 채팅 이력 보관, 사용자가 접속하면 이전 채팅 이력을 전부 조회 가능
  • 저장소:
    • 데이터의 유형과 읽기/쓰기 연산의 패턴을 고려하여 어떤 DB를 사용할지 결정해야 함
    • 채팅 시스템이 다루는 데이터:
      • 사용자 프로필, 설정, 친구 목록과 같이 일반적인 데이터 --> 관계형 DB에 저장
      • 채팅 이력과 같은 채팅 시스템에 고유한 데이터 --> 키-값 저장소:
        • 채팅 이력 데이터는 양이 많음 (페이스북이나 왓츠앱은 매일 600억개 메세지 처리)
        • 오래된 메세지보단 최근에 주고 받은 메세지를 빈번히 사용
        • 검색이나 멘션 등을 통한 무작위 데이터 접근도 지원해야 함
        • 1:1 채팅 앱의 경우 읽기:쓰기 비율은 약 1:1
      • 키-값 저장소의 장점:
        • 수평적 규모확장이 쉬움
        • 데이터 접근 지연시간이 낮음
        • 관계형DB는 롱테일을 잘 처리하지 못하는 경향이 있으며, 인덱스가 커지면 데이터 무작위 접근 비용이 증가
        • 기존의 안정적인 채팅 시스템들이 키-값 저장를 채택하였음

데이터 모델:

  • 1:1 채팅을 위한 메세지 테이블:
    • pk: message_id
    • message_id는 메세지 순서를 정하는 역할도 함

  • 그룹 채팅을 위한 메세지 테이블:
    •  pk: (channel_id, message_id) 복합 키
    • channel: 채팅 그룹, partition key로도 사용

  • 메세지 ID:
    • message_id 가 만족해야 할 속성:
      • 고유해야 함
      • 정렬 가능해야 하며, 시간 순과 일치해야 함
    • RDBMS는 auto_increment 사용 가능하나, NoSQL에는 보통 없음
      --> 스노플레이크 같은 전역적 64-bit 순서 번호 생성기 사용
      --> 지역적 순서 번호 생성기 사용 (같은 그룹 내에서만 ID 유일성 보장)

 

3단계: 상세 설계

서비스 탐색

  • 클라이언트에게 위치나 서버 용량 등의 기준을 바탕으로 가장 적합한 채팅 서버를 추천하는 역할
  • 대표적인 오픈 소스 솔루션: 아파치 주키퍼
  • 사용 가능한 모든 채팅 서버를 서비스 탐색에 등록시켜두고, 클라이언트가 접속을 시도하면 사전에 정한 기준에 따라 최적의 채팅 서버를 골라줌
  • 주키퍼로 구현한 서비스 탐색 시스템 동작:
    1. 사용자 A가 앱에 로그인 시도
    2. 로드 밸런서가 로그인 요청을 API 서버 중 하나에 전달
    3. 백엔드가 사용자 인증 처리 후, 서비스 탐색 기능이 사용자 A에게 가장 적합한 채팅 서버를 찾음
      (e.g. 서버 2가 선택되어, 해당 서버 정보가 사용자 A에게 반환됨)
    4. 사용자 A는 채팅 서버 2와 웹소켓 연결 맺음

 

메세지 흐름

  • 1:1 채팅 메세지 처리 흐름
    • 사용자 A가 사용자 B에게 메세지를 보낸 경우 처리:
      1. 사용자 A가 채팅 서버 1로 메시지를 전송
      2. 채팅 서버 1은 ID 생성기로 메시지 ID 발급
      3. 채팅 서버 1은 메시지를 메시지 동기화 큐로 전송
      4. 메시지는 키-값 저장소에 저장됩니다.
      5. (a) 사용자 B가 접속 중이라면, 사용자 B가 연결된 채팅 서버 2로 메세지 전송
        (b) 사용자 B가 접속 중이 아니면, 푸시 알림 서버로 푸시 알림 메세지 전송
      6. 채팅 서버 2는 사용자 B에게 메시지를 전달
        사용자 B와 채팅 서버 2 사이에 있는 웹소켓 연결을 사용

  • 여러 단말 사이의 메세지 동기화
    • 사용자 A는 핸드폰과 노트북 두 개 단말 사용
      • 사용자가 핸드폰으로 채팅 앱에 로그인하여 채팅 서버 1과 웹소켓 연결
      • 사용자가 노트북도 로그인하여 채팅 서버 1과 웹소켓 연결
    • 각 단말은 cur_max_message_id 변수를 통해 단말 별 가장 최신 메세지 ID를 추적
      • 아래 조건을 만족하면 최신 메세지로 간주:
        • 수신자 ID가 현재 로그인된 사용자 ID와 같음
        • 키-값 저장소에 보관된 메시지이고, 메세지 ID가 cur_max_message_id보다 큼
      • cur_max_message_id는 단말마다 별도로 유지 관리하면 되므로 키-값 저장소에서 새 메세지를 가져오는 동기화 작업에 용이함

  • 소규모 그룹 채팅에서의 메세지 흐름
    • 사용자 A가 사용자 A, B, C의 세 명이 속한 그룹 채팅에 메시지를 보내면,
      이 메시지를 B, C의 메시지 동기화 큐(받은 편지함)에 복사하는 방식
      • 소규모 그룹 채팅에 적합:
        • 클라이언트는 자신의 받은 편지함만 확인하면 되기 때문에 동기화 흐름이 단순
        • 그룹 인원이 적을 경우, 각 수신자에게 메시지를 복사 저장하는 비용이 크지 않음
      • 대규모 그룹에서는 모든 구성원에게 메시지를 복사하는 방식이 비효율적

    • 수신자 관점의 흐름:
      • 한 수신자의 수신함(메세지 동기화 큐)는 여러 사용자로부터 오는 메세지를 수신

 

접속 상태 표시

  • 접속상태 서버는 클라이언트와 웹소켓으로 통신하는 실시간 서비스의 일부
  • 사용자 로그인
    1. 로그인 흐름은 '서비스 탐색' 절에서 설명된 것처럼 동작
    2. 클라이언트와 실시간 서비스(채팅 서버) 간에 웹소켓 연결 형성
    3. 접속 상태 서버는 사용자 A의 상태와 last_active_at 타임스탬프 값을 키-값 저장소에 보관
    4. 사용자가 접속 중으로 표시됨

  • 로그아웃
    • 사용자가 로그아웃하면, 키-값 저장소의 사용자 상태가 오프라인으로 바뀜
    • 사용자가 오프라인으로 표시됨

  • 접속 장애
    • 현실적으로 인터넷 연결이 항상 안정적일 수 없음
    • 사용자 인터넷 연결이 끊기면, 클라이언트와 서버 간의 웹소켓 같은 지속 연결도 끊어짐
    • 이때 단순하게 사용자를 오프라인으로 처리하고, 연결이 복구되면 다시 온라인으로 바꾸는 방식을 생각해볼 수 있음
      --> 수시로 변경해야 하고 사용자 경험 측면에서도 나쁨
    • 박동 검사:
      • 온라인 상태인 클라이언트는 주기적으로 박동 이벤트를 접속상태 서버로 전송
      • 서버가 x초 이내에 박동을 수신하면 해당 사용자를 온라인으로 간주하고, 그렇지 않으면 오프라인으로 간주
      • e.g. 매 5초마다 서버로 박동 이벤트를 보내고, x=30초 동안 메세지가 없으면 오프라인 상태로 변경

  • 상태 정보의 전송
    • 사용자 A의 친구들이 A의 접속 상태 변화를 인지하는 방법:
      • 상태정보 서버는 발행-구독 모델을 사용
      • 각 친구관계마다 채널을 하나씩 둠
      • 사용자 A의 접속 상태가 변경되면 그 사실을 A-B, A-C, A-D 채널에 각각 작성
      • 이 채널들은 각각 사용자 B, C, D가 구독
      • 이러한 클라이언트와 서버 간의 통신은 웹소켓을 사용
    • 그룹 크기가 작을 때는 유용하나, 그룹 크기가 커지면 비용과 시간 증가
      --> 사용자가 그룹 채팅에 입장하는 순간에만 상태 정보 읽거나, 친구 목록의 접속상태 갱신이 수동으로 이뤄지게 유도

 

4단계: 마무리

클라이언트와 서버 간의 실시간 통신에는 웹소켓을 사용

이 채팅 시스템의 구성 요소:

  • 채팅 서버: 실시간 메시지 전송/수신
  • 접속 상태 서버: 사용자 상태 관리
  • 푸시 알림 서버: 푸시 알림 전송
  • 키-값 저장소: 채팅 이력 저장
  • API 서버: 사용자 로그인, 회원가입, 프로필 변경 등 나머지 기능 처리

추가 논의 사항:

  • 미디어 파일 지원: 미디어 파일은 텍스트보다 훨씬 크기 때문에 압축, 클라우드 저장소, 썸네일 생성 등의 논의 필요
  • 종단 간 암호화: 발신자와 수신자만이 메시지를 읽을 수 있게 하는 방식
  • 클라이언트 측 캐싱: 메시지를 클라이언트 측에서 캐싱하여, 클라이언트와 서버 간의 데이터 전송량을 줄이는 방안
  • 로딩 시간 개선: 사용자 데이터, 채널 등을 캐싱하기 위해 지역적으로 분산된 네트워크를 구축하여 로딩 시간을 개선하는 방안
  • 오류 처리:
    • 채팅 서버 오류: 하나의 채팅 서버에 수십만 개 이상의 웹소켓 연결이 있는 상황에서 채팅 서버가 중단되면, 서비스 탐색 기능(주키퍼)이 클라이언트에게 새로운 채팅 서버를 할당
    • 메시지 재전송 기법: 재시도나 큐를 통해 메세지의 안정적 전송을 보장