[가상 면접 사례로 배우는 대규모 시스템 설계 기초] 12장. 채팅 시스템 설계

1단계. 문제 이해 및 설계 범위 확정
페이스북 메신저, 위챗(WeChat), 왓츠앱(WhatsApp) 처럼 1:1 채팅에 집중하는 앱, 슬랙(Slack) 같은 그룹 채팅에 중점을 둔 업무용 앱, 게임 채팅에 쓰이는 디스코드(Discord) 같이 대규모 그룹의 소통과 응답지연(latency)이 낮은 음성 채팅에 집중하는 앱
요구사항
1. 응답지연이 낮은 1:1 채팅 기능
2. 최대 100명까지 참여할 수 있는 그룹 채팅 기능
3. 사용자의 접속 상태 표시 기능
4. 다양한 단말 지원. 하나의 계정으로 여러 단말에 동시 접속 지원
5. 푸시 알림
6. 5,000만 DAU(Daily Active User: 일별 능동 사용자 수) 처리
2단계. 개략적 설계안 제시 및 동의 구하기
이 채팅 서비스는 아래 기능 을 제공해야 한다.
* 클라이언트들로부터 메세지 수신
* 메세지 수신자(recipient) 결정 및 전달
* 수신자가 접속(online) 상태가 아닌 경우에는 접속할 때까지 해당 메세지를 보관

채팅 서비스와의 접속에는 Keep-alive 헤더를 사용하면 효율적
1. 클라이언트와 서버 사이의 연결을 끊지 않고 계속 유지할 수 있음
2. TCP 접속 과정에서 발생하는 핸드셰이크(hand-shake) 횟수를 줄일 수 있음
하지만 메세지 수신 시나리오는 이것보다 복잡: 폴링(polling), 롱 폴링(long polling), 웹소켓(WebSocket) 등의 기술을 사용해야 함
폴링(Polling)
폴링(polling) : 클라이언트가 주기적으로 서버에게 새 메세지가 있냐고 물어보는 방법
-. 폴링 비용은 폴링을 자주하면 할수록 올라감: 답해줄 메세지가 없는 경우에는 서버 자원이 불필요하게 낭비.
롱폴링(Long Polling)
클라이언트는 새 메세지가 반환되거나 타임아웃 될 때까지 연결을 유지
-. 클라이언트: 새 메세지를 받으면 기존 연결을 종료하고 서버에 새로운 요청을 보내서 모든 절차를 다시 시작
롱폴링(long polling)의 약점
- 메세지를 보내는 클라이언트와 수신하는 클라이언트가 같은 채팅 서버에 접속하게 되지 않을수도..
- HTTP 서버들은 보통 무상태(STATELEE) 서버 : 로드밸런싱을 위해 라운드 로빈(Round Robin) 알고리즘을 사용하는 경우, 위와 같은 가능성이 생길 수도 있음
?
- 서버 입장에서는 클라이언트가 연결을 해제했는지 아닌지 알 좋은 방법이 없음
- 비효율적: 메세지를 많이 안 받는 클라의 경우 타임아웃이 일어날 때마다 주기적으로 서버에 재접속
웹소켓(Websocket)
서버가 클라이언트에게 비동기(async) 메세지를 보낼 때 가장 널리 사용하는 기술

- 한번 맺어진 연결은 항구적이며 양방향
- 웹소켓은 80이나 443처럼 HTTP 혹은 HTTPS 프로토콜이 사용하는 기본 포트번호를 사용하기 때문에 일반적으로 방화벽이 있는 환경에서도 잘 작동함

웹소켓 이용 시 메세지를 보낼 때나 받을 때 동일한 프로토콜을 사용할 수 있으므로 설계 뿐 아니라 구현 또한 단순하고 직관적
개략적 설계안
주 통신 프로토콜은 웹소켓 사용
1. 무상태 서비스
로그인, 회원가입, 사용자 프로파일 표시 등을 처리하는 전통적인 요청/응답 서비스
로드밸런서 뒤에 위치: 로드밸런서는 요청을 그 경로에 맞는 서비스로 정확하게 전달한다.

서비스들 가운데 상당수가 시장에 완제품으로 나와 있어서 우리가 직접 구현하지 않아도 쉽게 사서 쓸 수 있음.
서비스 탐색(service discovery): 클라이언트가 접속할 채팅 서버의 DNS 호스트명을 클라이언트에게 알려주는 역할
2. 상태유지(stateful) 서비스
상태 유지가 필요한 서비스는 채팅 서비스 : 각 클라이언트가 채팅 서버와 독립적인 네트워크 연결을 유지해야 하기 때문
3. 제3자 서비스 연동
채팅 앱에서 가장 중요한 제3자 서비스는 푸시 알림.
4. 규모 확장성
서버 한 대로 얼마나 많은 접속을 동시에 허용할 수 있느냐

5. 저장소
채팅 시스템이 다루는 데이터의 종류
1. 사용자 프로파일, 설정, 친구 목록처럼 일반적인 데이터
- 안정성을 보장하는 관계형 DB에 보관 : 다중화(replication)와 샤딩(sharding)
2. 채팅 시스템에 고유한 데이터로 채팅 이력(chat history)
키-값 저장소를 추천 사유:
1. 수평적 규모확장(horizontal scaling)이 쉬움
2. 데이터 접근 지연시간(letency)가 낮음
3. 관계형 DB는 데이터 가운데 롱 테일(long tail)에 해당하는 부분을 잘 처리하지 못함
- 인덱스가 커지면 데이터에 대한 무작위적 접근(random access)를 처리하는 비용이 늘어남
4. 페이스북과 디스코드도 현재 사용중
6. 데이터 모델
1:1 채팅을 위한 메세지 테이블
그룹 채팅을 위한 메세지 테이블
7. 메세지 ID
메세지 ID는 다음과 같은 속성을 만족해야 함
1. MESSAGE_ID 의 값은 고유해야 함(uniqueness)
2. ID 값은 정렬 가능해야 하며 시간 순서와 일치해야 함.
즉, 새로운 ID는 이전 ID보다 큰 값이어야 한다.
3단계. 상세 설계
서비스 탐색
서비스 탐색 기능의 주된 역할은 클라이언트에게 가장 적합한 채팅 서버를 추천하는 것
: 사용되는 기준으로는 1) 클라이언트 위치(geograpical location), 2) 서버의 용량(capacity) 등이 있음.

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

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

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

접속상태 표시
사용자 로그인
클라이언트와 실시간 서비스(real-time service) 사이에 웹소켓 연결이 맺어지고 나면
접속상태 서버는 A의 상태와 last-active-at 타임스탬프 값을 키-값 저장소에 보관
→ 이 절차가 끝나고 나면 해당 사용자는 접속 중인 것으로 표시
로그아웃
접속장애
박동(heartbeat) 검사를 통해 이 문제를 해결

상태 정보의 전송
발행-구독 모델(publish-subscribe model) 사용
: 각각의 친구관계마다 채널을 하나씩 두는 것
: 그룹의 크기가 작을 때에 효과적
성능 문제 해소 방법
사용자가 그룹 채팅에 입장하는 순간에만 상태 정보를 읽어가게 하거나, 친구 리스트에 있는 사용자의 접속상태를 갱신하고 싶으면 수동으로(manual) 하도록 유도하기
