12장은 채팅 시스템 설계에 다룬다.
요구사항 예시
- 1:1 채팅과 그룹 채팅 모두 지원
- 모바일 앱, 웹 앱 모두 지원
- 50M DAU
- 그룹 채팅은 최대 100명
- 1:1 채팅, 그룹 채팅, 사용자 접속상태 표시 지원하고 텍스트 메시지만 주고받을 수 있음
- 메시지 길이는 100,000자 이하
- 채팅 이력은 영구적으로 보관
채팅 시스템을 설계하기 위해선 클라이언트-서버간 통신 방법에 대한 기본 지식을 갖추고 있어야 한다.
채팅 시스템은 다음 기능을 제공해야한다.
- 클라이언트로부터 메시지 수신
- 메시지 수신자(recipient) 결정 및 전달
- 수신자가 접속상태가 아닌 경우에는 접속할 때까지 해당 메시지 보관
채팅 서비스는 HTTP 프로토콜을 사용할 수 있지만 실시간으로 처리되어야 하기 때문에 HTTP 프로토콜은 사용하지 않고 Web socket을 사용하거나 pub-sub 구조를 주로 사용한다.
Polling
클라이언트가 주기적으로 서버와 통신하여 새 메시지가 있는지 확인하는 방법이다.
폴링 주기가 짧으면 폴링 비용이 올라가며, 서버 자원이 불필요하게 낭비된다는 게 단점이다.
Long Polling
폴링을 개선한 방법으로 클라이언트가 주기적으로 서버와 통신할 때 새 메시지가 반환되거나 타임아웃 될 때까지 연결을 유지하는 방법이다.
채팅 서버를 여러 개로 운영한다면 로드밸런서가 라운드로빈 알고리즘을 사용할 때 송신 클라이언트와 수신 클라이언트가 같은 채팅 서버에 접속하게 되지 않을 수도 있다.
Web Socket
웹 소켓은 서버가 클라이언트에게 비동기 메시지를 보낼 때 가장 널리 사용하는 기술이다.
위 그림과 같이 웹 소켓 연결은 클라이언트가 시작하고 한 번 맺어진 연결은 항구적이며 양방향이다.
웹 소켓 연결은 항구적이기 때문에 서버 측에서 연결 관리를 효율적으로 해야 한다는 것을 유의해야 한다.
웹 소켓은 HTTP, HTTPS 프로토콜이 사용하는 기본 포트번호를 사용하기 때문에 방화벽이 있는 환경에서도 잘 동작한다.
개략적 설계
채팅 시스템은 위 사진과 같이 HTTP 통신으로 전통적인 요청/응답 서비스를 제공하는 무상태 서비스, 실시간 서비스를 제공하는 상태 유지 서비스, 알림을 제공하는 서드 파티 서비스 세 부분으로 나눌 수 있다.
채팅 시스템은 대량의 트래픽을 처리해야 하는 경우에도 한 대의 서버로 구현할 수 있지만 SPOF(Single-Point-Of-Failure)를 방지하기 위해 분산 서버를 사용하는 것이 좋다.
- 채팅 서버: 클라이언트 사이에 메시지 중계 역할
- 접속 상태 서버: 사용자의 접속 여부 관리
- API 서버: 채팅을 제외한 나머지 API 처리
- 알림 서버: 새 메시지 푸시 알림
- KV store: 채팅 이력 보관
채팅은 I/O latency 중요하기 때문에 I/O latency가 낮은 키-값 저장소를 사용하는 것이 효율적이다.
상세 설계
서비스 탐색
서비스 탐색 기능의 주된 역할은 클라이언트에게 가장 적합한 채팅 서버를 추천하는 것이다.
서비스 탐색 기능은 아파치 주키퍼 같은 오픈 소스 솔루션을 사용하여 구현한다.
메시지 흐름
1:1 채팅 메시지 처리 흐름
- 사용자 A가 채팅 서버로 메시지 전송
- 채팅 서버는 ID 생성기를 사용해 해당 메시지의 ID 결정
- 채팅 서버는 해당 메시지를 메시지 동기화 큐로 전송
- 메시지가 키-값 저장소에 보관됨
- 사용자 B가 온라인일 경우 사용자 B가 접속 중인 채팅 서버로 전송
- 사용자 B가 오프라인일 경우 사용자 B에게 푸시 알림 전송
- 채팅 서버는 사용자 B에게 메시지 전송
여러 단말 사이의 메시지 동기화
각 단말은 cur_max_message_id 라는 변수를 유지하는데, 해당 단말에서 관측된 가장 최신 메시지의 ID를 추적하는 용도로써 키-값 저장소에서 새 메지시를 가져오는 동기화 작업을 쉽게 구현할 수 있다.
다음 두 조건을 만족하는 메시지는 새 메시지로 간주한다.
- 수신자 ID가 현재 로그인한 사용자 ID와 같다
- 키-값 저장소에 보관된 메시지로서, 그 ID가 cur_max_message_id 보다 크다.
소규모 그룹 채팅에서의 메시지 흐름
그룹 채팅 인원수만큼 메시지 큐가 존재하며 A가 메시지를 보내면 A의 메시지 큐의 내용이 B, C의 메시지 큐에 복사된다.
이 설계안은 소규모 그룹 채팅에 적합하며, 위챗이 이러한 접근법을 사용하고 있다.
- 새로운 메시지가 왔는지 확인하려면 자기 큐만 보면 되니깐 메시지 동기화 플로우가 단순
- 그룹이 크지 않으면 메시지를 수신자별로 복사해서 큐에 넣는 작업의 비용이 문제가 되지 않음
접속상태 표시
개략적 설계에 접속상태 서버를 통해 사용자의 상태를 관리한다.
접속상태 서버는 웹소켓으로 통신하는 실시간 서비스의 일부다.
사용자 로그인
클라이언트와 실시간 서비스 사이에 웹소켓 연결이 맺어지면 status와 last_active_at 타임스탬프 값을 키-값 저장소에 저장하고 온라인으로 표시한다.
로그아웃
로그아웃을 하면 키-값 저장소에 보관데 status 값이 offline으로 바뀌며 오프라인으로 표시한다.
접속 장애
네트워크는 환경에 따라 안정성이 다르기 때문에 불안정 상황에 대응할 수 있는 설계를 준비해야 한다.
이런 장애에 대응하는 간단한 방법은 사용자를 오프라인 상태로 표시하고 연결이 복구되면 온라인 상태로 변경하는 것이다. 하지만 이 방법은 짧은 시간 동안 네트워크가 끊어졌다 복구되는 일에는 바람직하지 않다
해당 문제는 heartbeat 검사를 통해 이 문제를 해결한다.
heartbeat 는 클라이언트로 하여금 주기적으로 heartbeat event를 접속상태 서버로 보내도록 하고, 마지막 이벤트를 받은 지 x초 이내에 또 다른 박동 이벤트 메시지를 받으면 해당 사용자의 접속상태를 계속 온라인으로 유지한다.
상태 정보의 전송
상태정보 서버는 pub-sub model을 사용해서 접속 상태를 변경한다.
이러한 구조는 그룹 크기가 100K일 때 상태 변화 건당 10만 건의 이벤트 메시지가 발행되기 때문에 소규모일 때만 사용할 수 있다.
대규모의 그룹을 가지는 경우엔 사용자의 접속상태를 manual로 관리한다. ex) 디스코드, slack
추가 논의거리
- 채팅 앱에 미디어 지원 - 압축 방식, 클라우드 저장소(S3), 썸네일 생성(media convert) 논의
- 종단 간 암호화
- 캐시
- 로딩 속도 개선
- 오류 처리
- 채팅 서버 오류
- 메시지 재전송
'기술독서 > 대규모 시스템 설계 기초' 카테고리의 다른 글
[대규모 시스템 설계 기초] 14장 - 유튜브 설계 (1) | 2024.04.28 |
---|---|
[대규모 시스템 설계 기초] 13장 - 검색어 자동완성 시스템 설계 (0) | 2024.04.24 |
[대규모 시스템 설계 기초] 11장 - 뉴스 피드 시스템 설계 (0) | 2024.03.20 |
[대규모 시스템 설계 기초] 10장 - 알림 시스템 설계 (1) | 2024.03.15 |
[대규모 시스템 설계 기초] 9장 웹 크롤러 설계 (0) | 2024.03.06 |