가상 면접 사례로 배우는 대규모 시스템 설계 12장을 참고한 글입니다.
1) 설계 범위 확정
- 1:1 채팅앱인지? 그룹앱인지?
- 플랫폼은?
- DAU는?
- 특정 기능이 있는지? (현재 활동중, 첨부파일 지원 등)
- 메시지 길이 제한이 있는지?
- 채팅 이력 보관 기간은?
2) 설계에 들어가기 앞서 필요한 지식
어떤 네트워크 통신 프로토콜을 사용할 것인지? HTTP를 사용할까?
→ 오랜 세월 검증된 HTTP 또한 좋은 방법일 수 있다. keep-alive 속성으로 TCP 핸드셰이크 횟수를 줄일 수 있다.
하지만 HTTP를 사용한다면 수신 시나리오는 복잡해진다. 클라이언트가 연결을 만드는 프로토콜이기에 서버가 임의 시점에 메시지를 보내는 데 쉽게 쓰일 수 없다.
이 문제를 해결하기 위해 서버가 연결을 만드는 것처럼 동작하는 기법이 개발되어 왔다.
폴링 Polling
클라이언트가 주기적으로 서버에 새 메시지가 있냐고 물어보는 방식이다. 폴링은 비싸다.
롱 폴링 Long Polling
폴링의 단점을 보완하기 위해 제안된 롱 폴링 방식이다. 새 메시지가 반환되거나 타임아웃 될 때까지 연결을 유지한다. 하지 클라이언트와 연결을 종료하기 때문에, 연결 요청을 다시 하는 약점이 존재한다.
라운드로빈으로 로드밸런싱을 수행한 경우, 서버는 클라이언트와 롱 폴링 연결을 가지고 있지 않을 수 있다. 또는 서버 입장에서 클라이언트와 연결을 해제했는지 알 방법이 없다. 타임아웃이 일어나므로 주기적으로 재접속하는 비효율이 존재한다.
웹소켓 Web Socket
서버가 클라이언트에게 비동기 메시지를 보낼 때 가장 널리 사용하는 기술이다.
첫 연결은 HTTP 핸드셰이크 절차지만 웹소켓 연결로 업그레이드 된다. 메시지 양방향 전송까지 가능하게 한다.
웹 소켓을 이용한 채팅 서비스는 다음과 같다. 하지만 서버 측에서 연결 관리를 효율적으로 해야 한다.
클라이언트1이 메시지를 전송하면 해당 그룹에 있는 (모두 서버와 웹소켓 연결이 된 상태의) 모든 클라이언트에게 메시지 전달이 가능해진다.
3) 설계안
무상태 서비스
로그인, 회원가입, 사용자 정보 표시 등 전통적인 요청/응답 서비스라고 생각하면 된다. 무상태 서비스는 로드밸런서 뒤에 위치한다. 로드밸런서가 하는 일은 요청을 그 경로에 맞는 서비스로 정확하게 전달하는 것이다.
상태 유지 서비스
본 설계안에서 유일하게 상태 유지가 필요한 채팅 서비스이다. 각 클라이언트가 서버와 독립적인 네트워크 연결을 유지해야 하기 때문이다. 연결된 서버가 살아 있는 한 다른 서버로 연결을 변경하지 않는다.
제 3자 서비스 연동
채팅 앱에서 중요한 건 third-party 푸시 알림이다. 앱이 실행 중이 아니더라도 새 메시지 알림을 받아야 하기 때문이다.
이를 종합하면 위와 같은 개략적 설계안이 만들어진다.
설계가 되었으면 어떤 기술 스택을 사용하느냐가 중요하다. RDB를 쓸 것인가 NoSQL을 채택할 것인가 (데이터의 유형과 읽기/쓰기 연산의 패턴을 따진다)
- 키-값 저장소는 수평적 규모확장이 쉽고 latency가 낮다.
- 채팅 이력은 매일 600억개 메시지를 처리한다(페이스북 기준)
- 이 데이터 가운데 빈번하게 사용되는 것은 주로 최근에 주고받은 메시지다
- 사용자 정보와 설정 등은 RDB에 보관하는 것이 좋다. 다중화와 샤딩에 보편적으로 사용된다.
- 1:1 채팅의 경우 읽기와 쓰기 비율은 대략 1:1이다
4) 조금 더 상세히
클라이언트에게 적합한 채팅 서버를 추천하기 위해선?
클라이언트의 위치, 서버의 용량 등을 고려해야 한다. 보통 오픈소스 솔루션으로 아파치 주키퍼(Apache Zookeeper)를 사용. 사용가능한 모든 서버를 등록시켜 두고, 클라이언트가 접속을 시도하면 사전에 정한 기준에 따라 최적의 채팅 서버를 고르면 된다.
1:1 메시지 처리 흐름
여러 단말 사이의 메시지 동기화
cur_max_message_id 라는 변수를 유지하는데 해당 단말에서 관측된 가장 최신 메시지의 ID를 추적하는 용도이다.
(1) 수신자 ID가 현재 로그인한 ID와 같다 (2) 키-값 저장소에 보관된 메시지로서, 그 ID가 cur_max_message_id보다 크다. 두 조건을 만족하는 메시지를 새 메시지로 간주한다.
→ 동기화 작업 쉽게 구현 가능!
접속 상태 표시
설계안에서 접속 상태 서버(presense server)를 통해 사용자의 상태를 관리한다고 했었다. 접속 상태 서버는 클라이언트와 웹소켓으로 통신하는 실시간 서비스의 일부라는 점에 유의하자. 사용자의 상태가 바뀌는 몇 가지 경우를 하나씩 살펴보자.
- 사용자 로그인
- 웹소켓 연결이 맺어지고 나면 사용자의 상태와 last_active_at 타임스탬프 값을 키값저장소에 보관한다. 이 절차가 끝나고 나면 접속 중인 것으로 표현된다.
- 로그아웃
- 키값저장소에 보관된 사용자 상태가 offline으로 바뀌게 된다.
- 접속 장애
- 네트워크 문제로 클라이언트와 서버 사이 맺어진 웹소켓 연결이 끊어져 발생한다. 단순히 오프라인 표시 후 온라인 상태로 변경한다면 해결될 문제지만 오버헤드 가능성이 있다. 짧은 시간동안 인터넷 연결이 끊어졌다 복구되는 일은 흔하기 때문이다.(자동차 터널 통과 등)
- heartbeat 검사를 통해 해결할 수 있다. 클라이언트는 주기적으로 서버로 heartbeat event 를 발생시키도록 한다. 마지막 이벤트를 받은 지 x초가 지나면 오프라인으로 상태를 변경하는 것이다.
'Develop > etc' 카테고리의 다른 글
개략적인 영상 스트리밍 시스템 설계에 관한 이야기 (0) | 2024.04.27 |
---|---|
개략적인 검색어 자동완성 시스템에 대한 이야기 (0) | 2024.04.26 |
개략적인 뉴스 피드 시스템 설계에 관한 이야기 (0) | 2024.04.24 |
Adapter Pattern은 무엇인가 (0) | 2023.09.13 |
Nginx 넌 도대체 뭐니 (0) | 2023.09.12 |