실시간 채팅을 구현해보자 (1) - 개요

이원석·2025년 2월 18일
1

사이드 프로젝트

목록 보기
1/10
post-thumbnail

오타/잘못된 내용에 대한 지적은 언제나 환영입니다!


안녕하시렵니까.
길어지는 취업 준비 과정 가운데, 개발의 감을 잃지 않고자 실시간 채팅을 구현해봤습니다.



🤔 왜 실시간 채팅을 구현했는가?

기존 프로젝트들은 HTTP 기반의 단방향 통신을 활용한 RESTful API 방식이었습니다. 또 똑같은 방식의 프로젝트를 하자니 손도 잘 안가고 뭔가 새로운 것을 하고 싶다는 생각이 들었습니다.

네트워크를 공부하면서 HTTP의 무상태성(Stateless)과 통신 후 연결 종료(Connectionless)가 서버의 부담을 줄이는 중요한 원리이지만, 실시간 채팅과 같이 연결을 유지해야 하는 서비스는 어떻게 동작할까? 라는 궁금증이 생겼습니다.

그렇다면 실시간 통신이 필수적인 환경에서 Session 유지를 위한 서버의 부담이 크다면, 이를 효과적으로 줄이는 방법은 무엇일까? 또한, 기존 HTTP 기반 폴링 방식과 비교했을 때 WebSocket이 어떤 장점을 가지며, 어떤 구조로 동작하는지 간단한 서비스를 구현하며 직접 탐구하고 싶었습니다.


💡 어떻게 구현할까?

  1. WebSocket을 활용해 기본적인 실시간 통신 구조를 만들었습니다.
  2. 단순 WebSocket만 사용하면 메시지 라우팅과 확장이 어렵기 때문에, STOMP (Simple Text Oriented Messaging Protocol) 를 도입했습니다.
  3. 로그인이 필요없고, 사용자들의 최소한의 신원 인증을 보장하기 위해 JWT와 SpringSecurity를 사용했습니다.
  4. STOMP는 pub/sub 구조를 지원하기 때문에, 추후 RabbitMQ나 Kafka 같은 메시지 큐와 연동하여 확장할 수도 있습니다.
  5. 현재는 Spring 기본 MessageBroker를 사용해 구현했으며, 이후 외부 메시지 큐로 전환할 예정입니다.
  6. 채팅에 어울리는 NoSQL(MongoDB)를 사용함, RDBMS는 채팅방 관리에만 사용함 ㅋ

이번 포스팅에서는 실시간 채팅 서비스 기능을 설계하기 위해 어떤 기술들을 왜 사용했는지 작성해보겠습니다.



-1: 웹 소켓이란?
-2: STOMP(Simple Text Oriented Messaging Protocol) 란?
-3: JWT란?
-4: NoSQL (Mongo DB)




🔌 1. 웹 소켓이란?

웹 소켓 통신은 HTTP와 다르게 전이중통신(Half-Duplex Communication) 이라고 합니다. 클라이언트와 서버는 양방향 통신을 지원하며, 능동적으로 요청과 응답을 주고받을 수 있는 능동적인 구조입니다. 이 덕분에 채팅, 게임 등 실시간 데이터 전송이 필요한 서비스에 적합합니다.

HTTP 통신은 요청과 응답이 존재하며, 요청은 클라이언트가 응답은 서버가 전송합니다. 이러한 구조는 서버에서 능동적으로 클라이언트에 ‘먼저’ 데이터를 전송할 수 없고, 클라이언트의 요청에 따라 서버는 수동적으로 응답하는 구조입니다. 이와 같은 통신 구조를 반이중통신(Half-Duplex Communication) 이라고 합니다.

각각의 Protocol은 상황에 따라 장/단점이 있으며 프로젝트의 목표에 맞춰 적절하게 사용하면 됩니다. 저와 같이 실시간 채팅이 주 목적인 경우 웹 소켓을 활용하면 좋겠죠?


❓ HTTP는 양방향 통신이 불가능한가?

사실 HTTP도 실시간성을 보장하는 기법이 존재합니다. 다만, 웹 소켓에 비해 빈번한 Connection의 연결과 해제가 반복되어 효율성이 떨어집니다.


  • Polling:
    클라이언트가 일정한 간격으로 서버에 요청하여 새 데이터(이벤트)가 있는지 확인하는 방식입니다. 데이터 발생 시점과 요청 시점 사이에 지연(latency)가 발생할 수 있습니다. (특히 실시간성이 중요할 경우 더 Critical)


  • Long Polling:
    Polling을 보완한 방식으로, 클라이언트가 서버에 요청을 보내면, 서버는 새 데이터(이벤트)가 발생할 때까지 연결을 유지하다가 응답을 보냅니다. 응답 이후 클라이언트는 다시 요청을 보냅니다.


Long Polling 방식으로 불필요한 네트워크 트래픽이 발생을 줄인다 해도, 여전히 HTTP 요청-응답 사이클이 반복됩니다.
하지만, 실시간 채팅이 아닌 뉴스/메일과 같이 변경사항의 빈도가 적은 경우, 위의 방식이 웹소켓 보다 효과적일 수 있습니다.


  • Server-Sent-Events (SSE):
    서버 → 클라이언트로 실시간 이벤트를 전달하는 웹 기술입니다. (클라이언트의 요청 X) 클라이언트는 서버에게 특정 이벤트 구독 요청을 전송하고, 서버는 해당 이벤트가 발생하면 이벤트를 전송합니다. 하지만, 클라이언트가 서버에게 데이터를 보낼 수 없다는 단점이 있습니다.


다음으로는 웹 소켓의 특징에 대해 알아보겠습니다.


🔗 웹 소켓의 특징

1. Stateful Protocol

웹 소켓은 HTTP(Stateless Protocol)과 다른 Stateful Protocol입니다. 따라서, 최초 접속시에는 HTTP 프로토콜로 WS HandShaking 과정을 수행하여 WebSocket으로 Upgrade합니다.

2. 데이터 양이 적음

HTTP는 요청 시 Request URL, Status Code, Header/Body 등의 데이터가 함께 전달되며 kbyte 크기의 헤더를 갖지만, 웹 소켓은 byte 단위내로 압축이 가능하며 훨씬 적은 양의 데이터를 서버와 주고받습니다.

3. 세션 유지

서버 - 클라이언트간의 연결을 유지해야 하기 때문에 세션 유지 비용으로 인한 서버 부하가 발생할 수 있습니다. 따라서, 트래픽이 많은 서비스에서는 웹 소켓용 서버를 분리하는 것이 좋은 선택일 수 있습니다.

현재 프로젝트는 도메인을 기준으로 패키징 해, 추후 효율적으로 서버를 분리할 수 있도록 했습니다. 상황에 따라 Kafka/RabitMQ/Redis 메시징 큐를 통해 모든 서버에 이벤트를 전파할 수 있도록 고도화 예정입니다.




📩 2. STOMP(Simple Text Oriented Messaging Protocol) 란?

STOMP는 클라이언트/서버 간의 전송할 메시지의 유형, 형식, 내용들을 정의한 규칙입니다. TCP 또는 WebSocket과 같은 “양방향 네트워크 프로토콜 기반”으로 동작하며, 메시지 전송을 효율적으로 하기 위한 다양한 기능을 제공하는 프로토콜입니다.

- PUB: 메시지를 전송
- SUB: 전송한 메시지를 받아서 처리



☺️ STOMP를 사용하면?

  • 메시지의 형식이 표준화됨
    STOMP 프레임(CONNECT, CONNECTED, SUBSCRIBE, SEND, UNSUBSCRIBE, MESSAGE, DISCONNECT, ERROR)을 사용하여 메시지를 주고 받을 수 있습니다. 또한, 메시지에 헤더 값을 설정할 수 있기 때문에 (JWT) 인증 처리(Spring Seucirty)의 도입도 가능합니다.

  • Topic 기반 Pub/Sub 지원
    특정 채널(예: /topic/chatroom)을 구독하면, 서버가 자동으로 해당 채널의 메시지를 구독자들에게 전달합니다. 메시지 목적지 또한 쉽게 정의가 가능합니다. (/queue/, /topic/ 등..) 추후에 Kafka, RabbitMQ 와 같은 메시징 큐를 활용해 고도화 또한 가능합니다.

  • ACK/NACK 지원
    클라리언트가 메시지를 정산적으로 받았는지, 문제는 없는지, 괜찮은지, 잘 지내는지 확인할 수 있습니다.


WebSocket만 사용한다면, 이 모든 혜택들을 직접 구현해야 합니다. 한 번 구현해 보는것도 좋은 경험이겠지만, 기왕이면 똑똑한 선도 개발자들이 구현한 STOMP 프로토콜을 사용합시다!




🔐 3. JWT 란?

JWT 개념

해당 포스팅을 참고하자!

✏️ 네 줄 요약

JWT는 클라이언트가 Token을 관리하며 서버가 관리하는 세션과는 다르게 무상태(Stateless)를 유지합니다.

  • 무상태성
    서버는 클라이언트로부터 전달받은 토큰만 검증하면 되므로, 세션 저장소나 추가적인 상태 관리가 필요 없습니다.

  • 플랫폼 독립성
    토큰 기반 인증이기 때문에 모바일, 웹, 데스크탑 등 모든 플랫폼에서 동일하게 활용할 수 있습니다.

  • 확장성
    서버에 세션 정보를 저장하지 않으므로, 서버 간의 세션 동기화 문제 없이 수평적 확장이 용이합니다.

  • 보안
    토큰 내에 필요한 클레임을 담아 암호화 또는 서명할 수 있어, 데이터 무결성과 인증을 보장할 수 있습니다.

STOMP 프로토콜은 헤더값을 설정할 수 있기 때문에 JWT와 Spring Security를 결합한 사용자 인증 절차를 구현했습니다.




🗄️ 4. NoSQL (Mongo DB)

첫 서비스 구현시에는 RDBMS (MySQL)을 사용해 채팅 기능을 구현했습니다. 여러 레퍼런스들을 참고해보니 실시간 채팅에는 RDBMS 방식 보다는 NoSQL이 더 적절하다는 점을 알게되었습니다.

  1. 트랜잭션 처리 및 ACID
    RDBMS는 트랜잭션 처리와 ACID를 보장하여 데이터 무결성을 유지합니다. 그러나, 실시간 채팅과 같이 빠른 응답과 대용량 데이터 처리가 필요한 경우, 이러한 무결성 보장이 오히려 성능에 부담을 줄 수 있습니다. (실시간 채팅은 금융 거래와 같은 트랜잭션의 보장이 그렇게 중요하지 않다)
  1. 성능 최적화
    NoSQL은 빠른 읽기 및 쓰기 처리를 위해 최적화되어 있어, 대량의 메시지를 신속하게 저장하고 검색할 수 있습니다. 즉, 낮은 지연 시간과 높은 처리량이 요구되는 실시간 서비스 환경에서 MongoDB가 효율적입니다.
  1. 수평적 확장성
    NoSQL 데이터베이스는 클러스터링 및 샤딩(sharding)이 용이해, 데이터 양과 사용자가 급증하는 상황에서도 수평적 확장이 쉽게 가능합니다. (RDBMS는 ACID 보장과 복잡한 조인(join) 연산 등으로 인해 수평적 확장이 더 복잡하고 어려운 경우가 많습니다)

이러한 이유로.. 현재 프로젝트는 사실 RDB를 사용해도 성능상 큰 차이가 없을 것 같지만 추후 개선 작업시에 1 순위가 데이터 마이그레이션 작업이 될 것같아서, 기존의 RDB를 갈아 엎고 NoSQL로 마이그레이션 작업을 진행했습니다.



다음으로는 구체적으로 어떻게 채팅 기능을 구현했는지 Back-end/Front-end 관점에서 포스팅을 작성해보겠습니다!





참고 문헌
https://velog.io/@black_han26/SSE-Server-Sent-Events
https://nebulaisme.tistory.com/147
https://hyuk0309.tistory.com/24

2개의 댓글

comment-user-thumbnail
2025년 2월 19일

좋은 글 감사합니다

1개의 답글