12장. 채팅 시스템 설계
1단계: 문제 이해 및 설계 범위 확장
면접관과 대화를 통해 도출한 요구사항:
- 1:1 채팅과 그룹 채팅 모두 지원
- 모바일 앱 및 웹 앱 모두 지원
- 하루 5천만 명의 DAU를 지원
- 그룹 채팅의 최대 인원은 100명
- 첨부파일 지원 없이 텍스트 메시지만 지원
- 사용자 접속상태 표시 지원
- 메시지 길이는 10만 글자를 넘지 않아야 함
- 채팅 이력은 영구 보관
이 장에서 설계할 시스템의 기능 요약:
- 응답 지연이 낮은 일대일 채팅 기능
- 최대 100명까지 참여 가능한 그룹 채팅 기능
- 사용자의 접속상태 표시 기능
- 다양한 단말 지원 (하나의 계정의로 여러 단말에 동시 접속 지원)
- 푸시 알림
2단계: 개략적 설계안 제시 및 동의 구하기
클라이언트들은 아래 기본 기능을 제공하는 채팅 서비스와 통신:
- 다른 클라이언트로부터 메시지 수신
- 메시지 수신자를 결정 및 메시지 전달
- 수신자가 접속 상태가 아닌 경우, 해당 메시지를 보관했다가 나중에 전달

네트워크 통신 프로토콜 선택:
- 메세지 전송은 HTTP 사용 가능
- 메세지 수신은 HTTP 사용하기 어려움 --> 폴링, 롱 폴링, 웹소켓 등이 필요
(HTTP 연결은 클라이언트가 요청해야 하는데, 수신은 서버에서 클라이언트로 메세지를 보내는 것)
폴링:
- 주기적으로 서버에 메세지가 있는지 물어봄
- 폴링을 자주 할수록 비용 증가

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

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

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

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

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

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

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

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

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

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

4단계: 마무리
클라이언트와 서버 간의 실시간 통신에는 웹소켓을 사용
이 채팅 시스템의 구성 요소:
- 채팅 서버: 실시간 메시지 전송/수신
- 접속 상태 서버: 사용자 상태 관리
- 푸시 알림 서버: 푸시 알림 전송
- 키-값 저장소: 채팅 이력 저장
- API 서버: 사용자 로그인, 회원가입, 프로필 변경 등 나머지 기능 처리
추가 논의 사항:
- 미디어 파일 지원: 미디어 파일은 텍스트보다 훨씬 크기 때문에 압축, 클라우드 저장소, 썸네일 생성 등의 논의 필요
- 종단 간 암호화: 발신자와 수신자만이 메시지를 읽을 수 있게 하는 방식
- 클라이언트 측 캐싱: 메시지를 클라이언트 측에서 캐싱하여, 클라이언트와 서버 간의 데이터 전송량을 줄이는 방안
- 로딩 시간 개선: 사용자 데이터, 채널 등을 캐싱하기 위해 지역적으로 분산된 네트워크를 구축하여 로딩 시간을 개선하는 방안
- 오류 처리:
- 채팅 서버 오류: 하나의 채팅 서버에 수십만 개 이상의 웹소켓 연결이 있는 상황에서 채팅 서버가 중단되면, 서비스 탐색 기능(주키퍼)이 클라이언트에게 새로운 채팅 서버를 할당
- 메시지 재전송 기법: 재시도나 큐를 통해 메세지의 안정적 전송을 보장
'대규모 시스템 설계 기초' 카테고리의 다른 글
| 대규모 시스템 설계 기초 - 14장 (0) | 2025.04.26 |
|---|---|
| 대규모 시스템 설계 기초 - 13장 (1) | 2025.04.20 |
| 대규모 시스템 설계 기초 - 11장 (1) | 2025.04.12 |
| 대규모 시스템 설계 기초 - 10장 (0) | 2025.04.12 |
| 대규모 시스템 설계 기초 - 9장 (0) | 2025.04.12 |