가상 면접 사례로 배우는 대규모 시스템 설계 기초 (System Design Interview) - 저 : 알렉스 쉬, 역 : 이병준 을 읽고 정리한 글입니다.
12장에서는 채팅시스템을 설계해 봅니다. 설계하고자 하는 요구사항은 아래와 같습니다.
- 응답지연이 낮은 1:1 채팅
- 그룹 채팅(최대 100명) 지원
- 다양한 단말 지원 (앱, 웹). 하나의 계정으로 여러 단말에 동시 접속 가능
- 사용자 접속상태 표시
- 채팅 이력 보관
[목차]
- 클라이언트 통신
- 채팅 서비스 개략적 설계
- 채팅 서비스의 데이터 저장소
- 메시지 흐름 과정
📌 01. 클라이언트 통신
클라이언트는 서로 직접 통신하지 않습니다. 각 클라이언트는 위에 나열한 모든 기능을 지원하는 "채팅 서비스"와 통신합니다.
채팅을 시작하려는 클라이언트는 네트워크 통신 프로토콜을 사용하여 서비스에 접속합니다.
- 메시지 송신 클라이언트(sender) 가 클라이언트/서버 어플리케이션에 요청을 보냅니다.
- 메시지 송신 클라이언트(sender)는 채팅 서비스에 HTTP 프로토콜로 연결한 다음 메시지를 보내어 수신자에게 해당 메시지를 전달하라고 알립니다.
- HTTP 프로토콜을 사용할 경우 "keep-alive" 헤더를 사용하면 클라이언트와 서버 사이의 연결을 끊지 않고 계속 유지해 TCP 접속 과정에서 발생하는 handshake 횟수를 줄일 수 있어 효과적입니다.
HTTP 로 효과적인 실시간 연결을 보장하기 위해 많은 기술을 고안해 왔는데 "폴링, 롱폴링, 스트리밍, 웹소켓 등" 과 같은 기술이 존재합니다.
- 폴링 (polling)
- 수신받은 클라이언트 (receiver)가 주기적으로 채팅 서비스(서버) 에게 수신받을 메세지가 존재하냐고 물어보는 방식입니다.
- 폴링 비용은, 폴링을 자주할수록 증가 됩니다.
- 롱 폴링 (long polling)
- 폴링의 개선점으로 나온 방식이 롱 폴링 입니다.
- 롱 폴링은 클라이언트가 새 메시지가 반환되거나, 타임아웃이 될 때 까지 연결을 유지합니다.
- 클라이언트는 새 메시지를 받으면 기존 연결을 종료하고 서버에 새로운 요청을 보내어 모든 절차를 다시 시작합니다.
- 단점
- HTTP 서버는 보통 무상태(stateless) 하기 때문에 로드밸런싱을 위해 라운드 로빈(round robin) 알고리즘을 사용하는 경우 메시지를 받은 서버는 해당 메시지를 수신할 클라이언트와의 롱 폴링 연결을 가지고 있지 않은 서버일 수 있다.
즉, 메시지를 보내는 클라이언트(sender) 와 수신받는 클라이언틍(receiver)가 같은 채팅 서버에 접속하게 되지 않을 수 도 있기 때문에 롱-폴링 비용을 낭비할 수 도 있다. - 서버 입장에서는 클라이언트가 연결을 해지했는지 알 수가 없다.
- 메시지를 많이 받지 않는 클라이언트도 타임아웃이 일어날 때 마다 주기적으로 서버에 다시 접속하기 때문에 여전히 비효율적이다.
- HTTP 서버는 보통 무상태(stateless) 하기 때문에 로드밸런싱을 위해 라운드 로빈(round robin) 알고리즘을 사용하는 경우 메시지를 받은 서버는 해당 메시지를 수신할 클라이언트와의 롱 폴링 연결을 가지고 있지 않은 서버일 수 있다.
- 단점
- 웹 소켓 (websocket)
- 웹소켓은 서버가 클라이언트에게 비동기 메시지를 보낼 떄 가장 널리 사용되는 기술입니다.
- 웹소켓으로 맺어진 연결은 항구적(permanent)이며 양방향성 입니다.
- 과정
- 웹소켓 연결은 클라이언트가 처음 시작합니다. HTTP 연결로 시작하지만 특정 핸드셰이크 절차를 거쳐 웹소켓 연결로 업그레이드 됩니다.
- 웹소켓으로 항구적인 연결이 만들어지면, 서버는 클라이언트에게 비동기적으로 메시지를 전송할 수 있습니다.
- 특징
- 웹소켓은 HTTP or HTTPS 프로토콜일 사용하는 기본 포트번호를 그대로 쓰기 때문에 일반적으로 방화벽이 있는 환경에서도 잘 작동합니다.
- 웹소켓을 이용하면 메시지를 보낼때와 받을 떄 동일한 프로토콜을 사용할 수 있으므로 설계와 구현이 단순하고 직관적이기됩니다.
- 단, 웹소켓 연결은 항구적으로 유지되어야하기 때문에 서버측에서 연결관리를 효율적으로 해야합니다.
📌 02. 채팅 서비스 계략적 설계안
채팅 시스템은 무상태 서비스, 상태 유지(statefull) 서비스, 제 3자 서비스 연동의 세 부분으로 나누어서 살펴볼 수 있습니다.
1) 무상태 서비스
- 무상태 서비스는 기본의 로그인, 회원가입, 사용자 프로파일 표시 등을 처리하는 전통적인 request/response 서비스 입니다.
- 무상태 서비스가 제공하는 기능은 많은 웹사이트와 앱이 보편적으로 제공하는 api 서버를 의미합니다.
- 무상태 서비스는 로드밸런스 뒤에 위치하여, 로드밸런서가 사용자의 요청을 그 경로에 맞는 서비스로 전달합니다.
- 무상태 서비스는 모놀리틱(monolithic) 서비스 일수도 있고, 마이크로 서비스 일 수 있습니다.
2) 상태 유지 서비스
- 채팅 서비스 입니다.
- 각 클랑이언트가 채팅 서버와 독립적인 네트워크 연결을 유지해야 하기 때문에 설계안에서 유일하게 상태 유지가 필요합니다.
- 클라이언트는 보통 서버가 살아 있는 한 다른 서버로 연결을 변경하지 않습니다.
3) 제 3자 서비스 연동
- 푸시 알림 서비스 입니다.
- 새 메시지를 받았다는 앱이 실행중이지 않더라도 알림을 받아야해서 별도의 서버로 분리합니다.
- 이에 관련된 사항은 10장 알림시스템 설계 해당 포스팅을 참고해주세용
여기서 트래픽에 의한 규모 확장성을 생각하여 1대의 서버가 아닌, 여러 서버로 구성한다고 하면 아래와 같은 설계안이 나옵니다.
- 채팅서버는 클라이언트 사이에 메시지를 중계하는 역할을 담당합니다.
- 접속상태 서버(presence server)는 사용자 접속 여부를 관리합니다.
- API 서버는 로그인, 회원가입, 프로파일 변경 등 그 외 나머지 전부를 처리합니다.
- 알림 서버는 푸시 알림을 보냅니다.
- 키-값 저장소에는 채팅 이력(chat-history)를 보관합니다. 시스템에 접속한 사용자는 이전 채팅 이력을 전부 보게됩니다.
📌 03. 채팅 서비스의 데이터 저장소
채팅시스템이 다루는 데이터는 보통 2가지 입니다.
- 사용자 프로파일, 설정, 친구 목록 등 일반적인 데이터
- 채팅이력과 같은 채팅 시스템 고유 데이터
✔️ 1) 번 유형은 보통 안전성을 보장하는 RDB(관계형 데이터베이스)를 사용합니다. 다중화(replication) 과 샤딩(sharding)은 이런 데이터의 가용성과 규모확장성을 보증하기 위한 보편적인 기술입니다.
✔️ 2)번 유형은 키-값 저장소 (key-value) 데이터베이스 추천합니다.
- 키-값 저장소는 수평적 규모확장이 쉽습니다.
- 키-값 저장소는 데이터 접근 지연시간이 낮기 때문에 채팅이력 탐색에 유리합니다.
- 관계형 데이터베이스는 데이터 가운데 롱 테일(long-tail)에 해당하는 부분을 잘 처리하지 못하는 경향이 있다고 합니다. 인덱스가 커지면 데이터에 대한 무작위적 접근을 처리하는 비용이 늘어나기 때문입니다.
📌 04. 메세지 흐름 이해하기
채팅 시스템에서의 종단 기기간 메세지가 전송되고 수신되는 과정을 살펴봅시다.
1) 1:1 채팅 메시지 처리 과정
- 사용자 A 가 채팅 서버 1로 메시지를 전송합니다.
- 채팅 서버 1은 ID 생성기를 사용하여 해당 메세지의 ID를 결정합니다. (데이터 생성)
- 채팅 서버 1은 해당 메시지를 메시지 동기화 큐로 전송합니다.
- 메시지가 키-값 저장소에 보관됩니다.
- (a) 사용자 B가 접속 중인 경우 메시지는 사용자 B가 접속 중인 채팅 서버(본 예제의 경우에는 채팅서버 2)로 전송됩니다.
(b) 사용자 B가 접속중이 아니면 푸시 알림 메시지를 푸시알림 서버로 보냅니다. - 채팅 서버 2는 메시지를 사용자 B에게 전송. 사용자 B와 채팅 서버 2 사이에는 웹소켓 연결이 있는 상태이므로 그것을 이용하여 메시지를 수신합니다.
2) 그룹 채팅 메시지 처리 과정
3명의 사용자 A, B, C가 그룹채팅을 한다고 가정했을 시, 메시지 수신되는 흐름 입니다.
- 사용자 A가 보낸 메시지가 사용자 B와 C의 메시지 동기화 큐(message-sync queue) 에 복사됩니다.
- 각 사용자는 새로운 메시지가 왔는지에 대해 자기 큐를 확인하여 새로운 메시지를 수신합니다.
단 이런 소규모가 아닌 500명 이상의 대규모 그룹채팅이 가능할 경우, 모든 사용자의 큐에 똑같은 메세지를 복사하는 것은 바람직하지 않을 수 있습니다.
따라서 한 수신자는, 여러 사용자로부터 오는 메시지를 수신하는 별도의 개인 수신함(메시지 큐)를 가지고 있는것이 바람직합니다.
즉, 각 사용자의 메시지 동기화 큐는 아래 그림처럼 여러 사용자로부터 오는 메시지를 받을 수 있어야합니다.
+ 추가적으로
접속상태는 유저의 로그인, 로그아웃으로 상태를 표시할 수도 있지만, 네트워크 오류 등과 같은 접속장애나 앱 사용자 같은경우 백그라운드 돌렸을 때 무한정 로그인 상태가 될 수 도있습니다.
이러한 경우를 체크하기위해 박동 이벤트라는 기술이 있는데 저는 자세한 내용은 다루지 않겠습니다
끝!
'📗 개발자 책 읽기 > 가상 면접 사례로 배우는 대규모 시스템 설계 기초' 카테고리의 다른 글
[System Design Interview] 10. 알림 서버 시스템 설계하기 (0) | 2023.05.29 |
---|---|
[System Design Interview] 07. 분산 시스템 환경에서의 고유 유일 ID 값 생성하기 (feat UUID) (0) | 2023.05.29 |
[System Design Interview] 06. ⚙️ 키-값 저장소 설계하기(2) - 분산 저장소 구현에 필요한 기술적 고려사항들 (0) | 2023.01.16 |
[System Design Interview] 06. ⚙️ 키-값 저장소 (비 관계형 데이터베이스) 설계하기(1) - CAP 이론 정리 (2) | 2023.01.16 |
[System Design Interview] 05. ⚙️ 안정해시란? (0) | 2023.01.10 |