본문으로 건너뛰기

WebSocket 채팅 서버의 수평 확장 전략

들어가며

  • 다중 서버 환경에서 실시간 채팅 서비스를 제공하기 위해서는 WebSocket 서버의 수평 확장이 필수적입니다.
  • 하지만 WebSocket의 stateful한 특성으로 인해 이는 생각보다 까다로운 문제입니다.
  • 이 글에서는 이 문제를 어떻게 해결했는지 공유하고자 합니다.

1. 문제 상황: Stateful 서버의 확장성 이슈

1.1 WebSocket 서버가 Stateful한 이유

  • 클라이언트는 여러 채팅 서버 중 하나와 웹소켓 연결을 맺습니다.
  • 각 채팅 서버는 클라이언트의 연결 상태를 메모리에 유지해야 합니다.

1.2 이로 인한 확장의 어려움

  • 서버 간 세션 정보 공유 불가
  • 서버 장애/재시작 시 모든 연결 상태 손실
  • 메모리 관리의 어려움

1.3 실제 발생하는 문제 시나리오

  1. 다중 서버 환경에서의 메시지 전달
    • 서버 A에 연결된 사용자가 서버 B에 연결된 사용자에게 메시지를 보내야 하는 상황
    • 서버 간 직접적인 메시지 전달 불가능
  2. 서버 확장/축소 시의 연결 관리
    • 새로운 서버 추가 시 연결 분배 문제
    • 서버 제거 시 기존 연결 마이그레이션 문제

2. 해결 방안: 메시지 브로커 도입

2.1 메시지 브로커의 역할

  • 서버 간 메시지 전달 중계
  • 메시지 발신자와 수신자의 느슨한 결합(Loose Coupling) 지원
  • 메시지 라우팅 및 필터링

2.2 가능한 솔루션 비교

  1. Apache Kafka
    • 장점: 높은 처리량, 메시지 영속성
    • 단점: 복잡한 운영, 상대적으로 높은 지연시간
  2. RabbitMQ
    • 장점: 다양한 메시징 패턴, 신뢰성 있는 전달
    • 단점: 추가 인프라 구성 필요
  3. Redis Pub/Sub
    • 장점: 단순한 구성, 매우 낮은 지연시간
    • 단점: 메시지 영속성 없음

2.3 Redis Pub/Sub 선택 이유

  • 실시간성 최우선 고려
  • 기존 Redis 인프라 활용 가능
  • 운영 복잡도 최소화
  • 메시지 영속성은 별도 DB로 해결 가능

3. 아키텍처 설계

3.1 전체 시스템 구조

  1. 클라이언트 계층
    • WebSocket 연결 관리
    • 자동 재연결 처리
  2. 서버 계층
    • HTTP API 서버: 메시지 수신 및 저장
    • 채팅 서버: 실시간 메시지 전달
    • Redis: 채팅 서버 간 메시지 브로커
  3. 저장소 계층
    • 메시지 영구 저장소
    • 사용자/채팅방 메타데이터 관리

3.2 메시지 흐름

  1. 메시지 발신
    • 클라이언트 → HTTP API 서버
    • 메시지 저장 및 유효성 검증
    • Redis 채널에 메시지 발행
  2. 메시지 수신
    • Redis → 모든 WebSocket 서버
    • 각 서버는 자신에게 연결된 대상 클라이언트에만 전달

4. 구현 결과와 효과

4.1 달성한 목표

  • 수평 확장성 확보
    • Redis Pub/Sub을 통한 채팅 서버 간 메시지 동기화
    • 서버 추가/제거가 자유로운 구조
    • 각 서버는 독립적으로 동작 가능
  • 안정성 향상
    • 서버 장애 시에도 다른 서버들이 계속 서비스 제공

5. 결론

  • 메시지 브로커를 도입함으로써 WebSocket 서버의 stateful한 특성으로 인한 제약을 극복하고,수평 확장이 가능한 아키텍처를 구축할 수 있었습니다.
  • Redis Pub/Sub의 도입으로 서버 간 메시지 전달이 원활해졌고, 각 서버는 독립적으로 확장/축소될 수 있게 되었습니다.
  • 이를 통해 사용자 증가에 따른 유연한 대응이 가능해졌으며, 시스템의 안정성과 신뢰성도 크게 향상되었습니다.