채팅 시스템 설계

1단계: 문제 이해 및 설계 범위 확정

채팅 앱이라는 말은 사람마다 다르게 받아들인다. 그래서 무엇을 만들 것인지 먼저 좁히는 게 가장 중요하다.

실제로 시장의 앱들만 봐도 방향이 제각각이다.

중점
메신저, 위챗, 왓츠앱1:1 채팅
슬랙업무용 그룹 채팅
디스코드대규모 그룹 + 저지연 음성

면접관이 1:1 채팅을 생각하는데 그룹 채팅을 설계하면 곤란해진다. 최소한 1:1인지 그룹인지는 초반에 확정해야 한다.

던져볼 질문들

  • 1:1 채팅인가, 그룹 채팅인가 → 둘 다
  • 모바일인가, 웹인가 → 둘 다
  • 트래픽 규모는? → DAU 5천만
  • 그룹 인원 제한은? → 최대 100명
  • 어떤 기능이 필요한가? → 1:1, 그룹, 접속상태 표시. 텍스트만
  • 메시지 길이 제한은? → 10만 자 이하
  • 종단 간 암호화(E2EE)가 필요한가? → 지금은 불필요
  • 채팅 이력 보관 기간은? → 영구 보관

확정된 설계 범위

메신저와 유사한 채팅 앱을 만든다.

  • 응답지연이 낮은 1:1 채팅
  • 최대 100명이 참여하는 그룹 채팅
  • 사용자 접속상태 표시
  • 다양한 단말 지원 (한 계정으로 여러 단말 동시 접속)
  • 푸시 알림
  • DAU 5천만 처리

2단계: 개략적 설계안 제시 및 동의 구하기

채팅 서비스의 역할

클라이언트(모바일 앱 또는 웹 앱)는 서로 직접 통신하지 않는다. 모든 통신은 가운데 있는 채팅 서비스를 거친다.

채팅 서비스가 해야 할 일은 세 가지다.

  • 클라이언트로부터 메시지 수신
  • 메시지 수신자(recipient)를 판별하고 전달
  • 수신자가 오프라인이면, 접속할 때까지 메시지를 보관

메시지 전송: HTTP로 충분하다

일반적인 클라이언트/서버 구조에서 요청을 시작하는 건 클라이언트다. 채팅도 마찬가지로 송신자(sender)가 요청을 보낸다.

그래서 메시지를 보내는 쪽은 HTTP로 해결된다. 여기에 keep-alive 헤더를 쓰면 연결을 끊지 않고 재사용할 수 있어, TCP 핸드셰이크 횟수를 줄일 수 있다.

실제로 메신저를 포함한 여러 채팅 서비스가 초기에 HTTP를 사용했다.

메시지 수신: 여기서 문제가 생긴다

HTTP는 클라이언트가 연결을 시작하는 프로토콜이다. 서버가 원하는 시점에 클라이언트로 메시지를 밀어넣는 데는 맞지 않는다.

그런데 채팅에서 수신은 정확히 그 반대 방향이다. 상대가 언제 메시지를 보낼지 모르는데, 클라이언트가 먼저 요청할 수는 없다.

이를 해결하기 위해 서버가 연결을 시작하는 것처럼 동작하게 만드는 기법들이 나왔다.

  • 폴링(polling)
  • 롱 폴링(long polling)
  • 웹소켓(WebSocket)

셋을 차례로 살펴본다.

폴링(Polling)

클라이언트가 주기적으로 서버에 "새 메시지 있나요?"라고 물어보는 방식이다.

문제는 두 가지다.

  • 자주 물어볼수록 비용이 올라간다
  • 답해줄 메시지가 없으면 그 요청은 전부 낭비다

롱 폴링(Long Polling)

폴링의 비효율을 줄이려고 나온 방식이다.

클라이언트가 요청을 보내면 서버는 새 메시지가 생기거나 타임아웃될 때까지 응답을 미룬다. 응답을 받은 클라이언트는 연결을 끊고 곧바로 새 요청을 보내 같은 과정을 반복한다.

그래도 세 가지 약점이 남는다.

① 송신자와 수신자가 다른 서버에 붙을 수 있다

HTTP 서버는 보통 무상태(stateless)다. 로드밸런서가 라운드 로빈으로 분배하면, 메시지를 받은 서버가 정작 그 메시지를 받을 클라이언트와 연결되어 있지 않을 수 있다.

② 연결 해제를 감지하기 어렵다

서버 입장에서 클라이언트가 연결을 끊었는지 알 마땅한 방법이 없다.

③ 여전히 비효율적이다

메시지를 거의 받지 않는 클라이언트도 타임아웃마다 계속 재접속한다.

웹소켓(WebSocket)

서버가 클라이언트에게 비동기 메시지를 보낼 때 가장 널리 쓰이는 방식이다.

  • 연결은 클라이언트가 시작한다
  • 처음엔 HTTP 연결이지만, 핸드셰이크를 거쳐 웹소켓으로 업그레이드된다
  • 한번 맺어지면 항구적(persistent)이고 양방향이다
  • 연결이 살아 있는 동안 서버는 원할 때 메시지를 보낼 수 있다
  • 80·443 포트를 그대로 쓰므로 방화벽 환경에서도 잘 동작한다

그래서 웹소켓

앞서 메시지 전송에는 HTTP도 괜찮다고 했다. 하지만 웹소켓은 양방향이므로 전송까지 함께 처리할 수 있다. 굳이 둘로 나눌 이유가 없다.

송수신에 같은 프로토콜을 쓰면 설계도 구현도 단순해진다.

다만 웹소켓 연결은 계속 유지되어야 하므로, 서버 측 연결 관리가 중요한 과제가 된다.

개략적 설계안

주 통신 프로토콜은 웹소켓으로 정했다. 하지만 모든 곳에 웹소켓을 쓸 필요는 없다. 회원가입, 로그인, 프로필 조회처럼 요청-응답으로 끝나는 기능은 일반 HTTP로 충분하다.

전체 시스템은 세 부분으로 나눌 수 있다.

1. 무상태 서비스

로그인, 회원가입, 프로필 표시 등을 처리하는 전통적인 요청/응답 서비스다. 대부분의 웹사이트가 공통으로 갖는 기능이라 특별할 게 없다.

  • 로드밸런서 뒤에 위치하며, 로드밸런서는 요청 경로에 맞는 서비스로 전달한다
  • 모놀리식이든 마이크로서비스든 상관없다
  • 상당수는 완제품으로 나와 있어 직접 구현하지 않아도 된다

여기서 나중에 다시 볼 것이 서비스 탐색(service discovery) 이다. 클라이언트가 접속할 채팅 서버의 DNS 호스트명을 알려주는 역할을 한다.

2. 상태 유지 서비스

상태를 유지해야 하는 건 채팅 서비스뿐이다.

클라이언트마다 채팅 서버와 독립적인 연결을 들고 있어야 하기 때문이다. 그리고 그 서버가 살아 있는 한 클라이언트는 보통 다른 서버로 옮기지 않는다.

그래서 특정 서버에 연결이 몰릴 수 있는데, 앞서 언급한 서비스 탐색이 채팅 서비스와 협력해 이 부하를 분산한다.

3. 제3자 서비스 연동

가장 중요한 건 푸시 알림이다. 앱이 실행 중이 아니어도 새 메시지를 알려줘야 하기 때문이다.

규모 확장성

이론적으로는 서버 한 대로도 가능하다. 계산해보면 이렇다.

동시 접속 1M × 접속당 메모리 10KB ≈ 10GB

메모리 10GB면 100만 연결을 다 감당할 수 있다. (언어와 구현에 따라 달라지는 대략적인 수치다.)

하지만 서버 한 대짜리 설계안을 그대로 내밀면 안 된다. SPOF(단일 장애점)를 비롯해 실무에서 그렇게 하지 않는 이유가 여럿 있기 때문이다.

다만 한 대에서 출발해 점차 다듬어 나가는 접근 자체는 괜찮다. "이건 출발점일 뿐"이라는 걸 분명히 밝히면 된다.

컴포넌트별 역할

클라이언트는 실시간 송수신을 위해 채팅 서버와 웹소켓 연결을 끊지 않고 유지한다.

컴포넌트역할
채팅 서버클라이언트 간 메시지 중계
접속상태 서버(presence server)사용자의 온라인 여부 관리
API 서버로그인, 회원가입, 프로필 변경 등 나머지 전부
알림 서버푸시 알림 발송
키-값 저장소채팅 이력 보관

저장소

데이터 계층에서 결정할 핵심 질문은 관계형이냐 NoSQL이냐다. 답을 내려면 데이터 유형과 읽기/쓰기 패턴을 봐야 한다.

채팅 시스템의 데이터는 두 종류다.

1. 일반 데이터 → 관계형 DB

사용자 프로필, 설정, 친구 목록 등. 안정성이 중요하므로 관계형 DB에 보관하고, 다중화(replication)와 샤딩(sharding)으로 가용성과 확장성을 확보한다.

2. 채팅 이력 → 키-값 저장소

채팅 시스템에 고유한 데이터다. 읽기/쓰기 패턴이 독특하다.

  • 양이 엄청나다 — 대형 메신저는 하루 600억 건을 처리한다
  • 최근 메시지에 접근이 몰린다 — 오래된 메시지는 잘 보지 않는다
  • 그래도 무작위 접근이 필요하다 — 검색, 멘션 확인, 특정 메시지로 점프
  • 읽기:쓰기 비율이 약 1:1 — 1:1 채팅 기준

키-값 저장소를 선택하는 이유

  • 수평적 규모 확장이 쉽다
  • 데이터 접근 지연시간이 낮다
  • 관계형 DB는 롱 테일 데이터를 잘 처리하지 못한다. 인덱스가 커지면 무작위 접근 비용이 급격히 올라간다
  • 이미 검증됐다 — 메신저는 HBase, 디스코드는 카산드라를 사용한다

이어서 대윤아에게 맡기겠다

1개의 댓글

comment-user-thumbnail
2일 전

이 글을 읽고 폴링을 시작했습니다.
이웃님께 폴링 인 러브 ..!

답글 달기