해당 글은 Spring Boot를 활용하여 채팅기능를 구현하는 과정에서 새롭게 알게된 내용과 공부한 내용을 정리하고 기록하기 위해 작성한 글입니다.
채팅기능을 구현할 때 크게 두 가지 형태로 구현할 수 있다. 모놀리식 아키텍처(이하 MA) 형태로 구현 시, 채팅과 관련된 모든 기능과 로직이 단일한 서버 내에서 구현되고 실행된다. MSA 형태로 구현 시, 채팅과 관련된 기능을 담당하는 서버를 따로 구축하여 운영한다.
사용자가 많은 서비스일 경우, 대량의 동시 접속자를 처리하고 실시간으로 메시지를 전달해야 하는 특성상 서버 부하가 매우 크다. 이러한 경우는 MSA 형태로 채팅 서버를 따로 구축하는 것이 좋다. 이 방식을 통해 서버 부하 문제로 인한 서버 다운이나 처리 속도 저하 등의 문제를 예방할 수 있다. 하지만 규모가 작고, 사용자 수가 적다면 MA 형태로 채팅 기능을 구현하는 것이 좋다.
서비스의 규모와 필요성에 따라 위 두 방식 중 하나를 정하여 구현하는 것이 좋다. 개인적으로 처음에 MA 방식으로 운영하다 서비스의 확장이 필요한 시점에 MSA 방법으로 변경하는 것이 좋아보인다.
채팅 내용을 저장하기 위해서는 데이터베이스가 필요하다. 채팅은 매우 빈번하게 발생하는 작은 데이터이므로, 성능과 확장성이 뛰어난 NoSQL 데이터베이스를 선택하는 것이 좋다. 물론 MYSQL, ORACLE과 같은 RDBMS를 사용하여 데이터베이스를 구축할 수 있으나, RDBMS의 정해진 스키마 구조로 인한 확장성의 한계와 데이터 삽입, 수정, 삭제 시 높은 처리 비용을 필요로 하기 때문에 좋은 선택은 아니다.
MongoDB, Cassandra, Redis 등이 채팅 내용을 저장하는데 사용하는 대표적인 NoSQL 데이터베이스이다. 이러한 데이터베이스는 채팅 내역을 빠르게 저장하고 조회할 수 있으며, 확장성이 높다.
MongoDB는 문서 지향적인 데이터 모델을 제공하여, 채팅 내용을 JSON 형태로 저장할 수 있다. Cassandra는 분산 환경에서 대량의 데이터를 처리하는 데 최적화되어 있으며, Redis는 캐시 기능을 지원하여 데이터 읽기 성능을 높일 수 있다.
각각의 데이터베이스는 서로 다른 장단점을 가지고 있으므로, 구현하는 서비스를 고려하여 적절한 데이터베이스를 선택하여야 한다.
필자는 진행한 프로젝트의 특성상 아래 포맷처럼 JSON 형태로 넘어온 데이터를 쉽게 처리할 수 있는 MongoDB를 사용했다. 모든 채팅방의 채팅 내역을 저장할 수 있는 db을 생성하고, 하나의 collection에 하나의 채팅방 대화 내용을 모두 저장하는 방식으로 구현했다.
{
"roomId":"1",
"sender":"1",
"message":"example message",
"sendingTime":"2023-04-12 09:24:32"
}
물론 채팅 내역이 많아짐에 따라 collection의 크기가 커져서 읽고 쓰기에 많은 시간이 소요될 수 있다. 이 같은 경우가 발생하면 MongoDB에서 제공하는 sharding과 기능을 사용하여 대용량 데이터를 분산하여 저장하는 것이 좋다.
채팅 기능을 구현할 때, SSE(Server Sent Events)와 HTTP 폴링 방식을 사용하여 구현할 수도 있다. 하지만 이러한 방식은 적합하지 않다. 채팅 기능은 내가 작성한 메세지를 채팅방에 속한 모두에게 전달해야 하고, 누군가 보낸 메세지를 실시간으로 확인할 수 있어야 하기 때문에 양방향 통신을 해야 한다. 따라서 Websocket을 사용하여 채팅 기능을 구현하기로 정했다.
HTTP 폴링
클라이언트가 일정한 주기로 서버에 요청을 보내는 방식이다. 실시간성을 제공하지 않으며, 주기적인 요청으로 인해 서버 부하와 네트워크 트래픽이 증가할 수 있다.
SSE(Server-Sent Events)
서버에서 클라이언트로의 단방향 실시간 데이터 전송을 위한 프로토콜이다.
클라이언트는 서버에 연결을 열어두고, 서버에서 클라이언트로 데이터를 지속적으로 전송한다.
클라이언트가 초기에 HTTP GET으로 Handshake 요청 시 upgrade 헤더를 사용하여 웹소켓을 사용할 것을 서버에 알린다. 서버는 클라이언트의 요청에 대해 HTTP 101 Switching Protocols 응답 코드로 응답하고 이후부터 웹소켓 프로토콜을 사용하여 통신한다. 웹소켓 프로토콜은 TCP/IP 기반으로 동작한다.
만약 채팅 기능을 WebSocket 프로토콜만을 사용하여 구현한다면, 메시지 포맷 형식이나 메시지 통신 과정, 세션 관리 등을 일일이 처리해야 하는 번거로움이 있다. 이를 해결하기 위해 STOMP를 서브 프로토콜로 사용하여 메시징 처리를 최적화할 수 있다.
STOMP는 WebSocket 프로토콜 위에서 동작하는 메시지 송수신을 효율적으로 처리하기 위해 개발된 프로토콜이다. STOMP는 pub/sub 구조로 되어 있어 메시지 송수신 처리 부분이 명확하게 정의되어 있어 개발자가 STOMP 스펙의 규칙을 잘 따른다면 메시징 처리를 간편하게 할 수 있다. 이를 통해 STOMP 를 사용하면 채팅 서버 개발에 용이하고 효율적인 메시징 처리를 할 수 있다.
STOMP는 pub/sub 패턴으로 동작한다. pub/sub 패턴은 메시지를 발행하는 곳과 해당 메시지를 구독하는 곳이 분리되어 있으며, 발행된 메시지는 여러 개의 구독자에게 동시에 전달된다.
STOMP에서 클라이언트는 특정 주제(topic)에 대해 구독(subscribe)을 요청하고, 해당 주제로 발행(publish)된 메시지를 수신(receive)할 수 있다. 클라이언트가 구독을 요청하면 해당 주제로 발행된 모든 메시지를 수신할 수 있게 된다. 이를 통해 다수의 클라이언트가 동일한 주제에 대한 메시지를 구독하고 처리할 수 있다.
1대1 채팅 기능에서는 한 명의 유저가 본인이 속한 채팅방(topic)을 구독(subscribe)하면서, 해당 채팅방에 메세지를 발행(publish)하는 역할을 수행해야 한다.
저도 공부하는 입장이라 부족할 수 있습니다. 따라서 조언과 피드백은 항상 열려있습니다!