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

송현진·5일 전
0

System Design

목록 보기
9/11

문제 정의

사용자들이 실시간으로 1:1 혹은 그룹 채팅을 할 수 있는 시스템을 설계한다. 메시지는 안정적으로 전달되고 지연은 최소화되며 동시에 수백만 사용자가 접속해도 확장성과 안정성을 유지해야 한다.

요구사항 정리

기능

  • 사용자 로그인/로그아웃
  • 친구 추가 / 대화방 생성
  • 1:1 및 그룹 채팅
  • 메시지 전송, 수신, 읽음 표시
  • 오프라인 메시지 저장 및 재전송
  • 메시지 검색 및 기록 조회

비기능

  • 낮은 지연 (RTT 수준의 실시간 경험)
  • 높은 가용성 (노드 장애 시에도 서비스 지속)
  • 확장성 (수천만 동시 사용자)
  • 내구성 (메시지 영속 저장)
  • 보안 (TLS, 사용자 인증)

핵심 설계 결정

전송 프로토콜

  • WebSocket: 양방향 연결, 낮은 지연, 지속 연결에 적합
  • 대안: HTTP Long Polling (fallback 용도)

메시지 전달 보장

  • 최소 한 번 전달(at-least-once) → 멱등 키 사용
  • 서버는 ACK 기반 재전송 지원
  • 클라이언트는 메시지 ID로 중복 처리

저장소

  • Hot storage (캐시/큐): Redis, Kafka → 빠른 전달
  • Cold storage (영구 저장): RDBMS 또는 NoSQL (Cassandra, HBase)
  • 사용자 단위 파티셔닝: (userId % N) → 특정 샤드에 메시지 저장

확장 아키텍처

  • Connection Server: 사용자와 WebSocket 유지
  • Chat Service: 메시지 라우팅 / ACK 처리
  • Message Store: 영속 저장, 검색 지원
  • Notification Service: 오프라인 사용자 푸시 알림

API 설계

POST /api/v1/users/{id}/messages
GET  /api/v1/users/{id}/messages?cursor=<opaque>&limit=50
POST /api/v1/conversations
GET  /api/v1/conversations/{id}/messages?cursor=<opaque>
  • 메시지는 커서 기반 페이지네이션 지원
  • 메시지 ID는 Snowflake 같은 전역 고유 ID

데이터 모델

-- 사용자
User(id PK, name, status, last_seen)

-- 대화방
Conversation(id PK, type, created_at)

-- 대화방 참가자
Participant(conversation_id, user_id, last_read_message_id, PRIMARY KEY(conversation_id, user_id))

-- 메시지
Message(id PK, conversation_id, sender_id, text, created_at, status)

last_read_message_id로 읽음 표시 구현
메시지 상태: sent, delivered, read

아키텍처 개요

[Client]
   |
[Load Balancer]
   |
[Connection Server]  <-- WebSocket 유지
   |
[Chat Service]  <-- 라우팅, ACK 처리
   |
+----------+----------------+
|          |                |
[Message Store]      [Redis/Kafka] (실시간 큐)
|                              |
[Search/History Service]   [Notification Service]
  • Write Path: 클라이언트 → Connection Server → Chat Service → 큐 → 저장소
  • Read Path:
    • 온라인: 실시간 WebSocket push
    • 오프라인: 저장소 조회 + 푸시 알림

메시지 흐름

  1. 클라이언트가 메시지를 전송하면 Connection Server로 전송
  2. Chat Service가 메시지 ID 생성 후 큐에 적재
  3. Message Store에 영구 저장
  4. 대상 사용자 온라인 시 WebSocket으로 push
  5. 오프라인이면 Notification Service로 푸시 알림
  6. 수신자가 읽으면 last_read_message_id 갱신 → 송신자에게 읽음 표시

확장 전략

  • 샤딩: 사용자/대화방 단위로 메시지 저장소 샤딩
  • CQRS 분리: 쓰기(실시간 큐)와 읽기(검색/조회) 분리
  • Replication: 메시지 저장소 이중화
  • 멀티 데이터센터: 지역별 사용자 라우팅

장애·성능·운영

  • 재전송 메커니즘: ACK 미수신 시 클라이언트/서버 모두 재시도
  • 백프레셔: 큐 적체 시 비필수 기능(알림 등) 지연 처리
  • 관측: 메시지 전달 지연, 전송 성공률, 연결 유지율, 오프라인 재전송율
  • 보안: TLS 연결, 사용자 인증 토큰, 메시지 암호화(E2EE 가능)

📝 배운점

채팅 시스템은 단순히 메시지를 주고받는 구조가 아니라 실시간성과 안정성, 확장성의 균형을 맞추는 종합적인 문제였다. 특히 수백만 사용자가 동시에 접속하더라도 연결을 유지하려면 Connection Server와 메시지 라우팅 레이어를 분리해 확장성을 확보해야 하고 메시지를 잃지 않으면서도 빠르게 전달하기 위해 큐와 영구 저장소를 함께 사용하는 이중화 전략이 필수적이었다.

또한 읽음 표시나 오프라인 메시지 재전송 같은 기능은 겉보기에는 단순해 보이지만
내부적으로는 멱등성, 재시도, ACK 기반 신뢰성 같은 설계 원칙이 뒷받침되어야 한다는 점도 중요하게 배웠다. 결국 채팅 시스템은 “낮은 지연”만큼이나 “메시지 유실 없는 보장”이 핵심이며 이를 위해 프로토콜 선택(WebSocket), 데이터 모델링, 큐 아키텍처, 샤딩까지 다각적으로 고려해야 한다는 교훈을 얻었다.

profile
개발자가 되고 싶은 취준생

0개의 댓글