채팅 기능 구현하기 전 고려해야 할 사항들

김동욱·2023년 4월 27일
2
post-thumbnail

해당 글은 Spring Boot를 활용하여 채팅기능를 구현하는 과정에서 새롭게 알게된 내용과 공부한 내용을 정리하고 기록하기 위해 작성한 글입니다.

모놀리식 아키텍처(Monolithic Architecture) vs MSA(Microservices Architecture)

채팅기능을 구현할 때 크게 두 가지 형태로 구현할 수 있다. 모놀리식 아키텍처(이하 MA) 형태로 구현 시, 채팅과 관련된 모든 기능과 로직이 단일한 서버 내에서 구현되고 실행된다. MSA 형태로 구현 시, 채팅과 관련된 기능을 담당하는 서버를 따로 구축하여 운영한다.

사용자가 많은 서비스일 경우, 대량의 동시 접속자를 처리하고 실시간으로 메시지를 전달해야 하는 특성상 서버 부하가 매우 크다. 이러한 경우는 MSA 형태로 채팅 서버를 따로 구축하는 것이 좋다. 이 방식을 통해 서버 부하 문제로 인한 서버 다운이나 처리 속도 저하 등의 문제를 예방할 수 있다. 하지만 규모가 작고, 사용자 수가 적다면 MA 형태로 채팅 기능을 구현하는 것이 좋다.

서비스의 규모와 필요성에 따라 위 두 방식 중 하나를 정하여 구현하는 것이 좋다. 개인적으로 처음에 MA 방식으로 운영하다 서비스의 확장이 필요한 시점에 MSA 방법으로 변경하는 것이 좋아보인다.

채팅 내용 저장을 위한 데이터베이스 선택

채팅 내용을 저장하기 위해서는 데이터베이스가 필요하다. 채팅은 매우 빈번하게 발생하는 작은 데이터이므로, 성능과 확장성이 뛰어난 NoSQL 데이터베이스를 선택하는 것이 좋다. 물론 MYSQL, ORACLE과 같은 RDBMS를 사용하여 데이터베이스를 구축할 수 있으나, RDBMS의 정해진 스키마 구조로 인한 확장성의 한계와 데이터 삽입, 수정, 삭제 시 높은 처리 비용을 필요로 하기 때문에 좋은 선택은 아니다.

MongoDB, Cassandra, Redis 등이 채팅 내용을 저장하는데 사용하는 대표적인 NoSQL 데이터베이스이다. 이러한 데이터베이스는 채팅 내역을 빠르게 저장하고 조회할 수 있으며, 확장성이 높다.

MongoDB는 문서 지향적인 데이터 모델을 제공하여, 채팅 내용을 JSON 형태로 저장할 수 있다. Cassandra는 분산 환경에서 대량의 데이터를 처리하는 데 최적화되어 있으며, Redis는 캐시 기능을 지원하여 데이터 읽기 성능을 높일 수 있다.

각각의 데이터베이스는 서로 다른 장단점을 가지고 있으므로, 구현하는 서비스를 고려하여 적절한 데이터베이스를 선택하여야 한다.

채팅 내용을 저장하기 위한 데이터베이스로 MongoDB를 선택

필자는 진행한 프로젝트의 특성상 아래 포맷처럼 JSON 형태로 넘어온 데이터를 쉽게 처리할 수 있는 MongoDB를 사용했다. 모든 채팅방의 채팅 내역을 저장할 수 있는 db을 생성하고, 하나의 collection에 하나의 채팅방 대화 내용을 모두 저장하는 방식으로 구현했다.

{
  "roomId":"1",
  "sender":"1",
  "message":"example message",
  "sendingTime":"2023-04-12 09:24:32"
}

물론 채팅 내역이 많아짐에 따라 collection의 크기가 커져서 읽고 쓰기에 많은 시간이 소요될 수 있다. 이 같은 경우가 발생하면 MongoDB에서 제공하는 sharding과 기능을 사용하여 대용량 데이터를 분산하여 저장하는 것이 좋다.

Websocket 사용하기

채팅 기능을 구현할 때, SSE(Server Sent Events)와 HTTP 폴링 방식을 사용하여 구현할 수도 있다. 하지만 이러한 방식은 적합하지 않다. 채팅 기능은 내가 작성한 메세지를 채팅방에 속한 모두에게 전달해야 하고, 누군가 보낸 메세지를 실시간으로 확인할 수 있어야 하기 때문에 양방향 통신을 해야 한다. 따라서 Websocket을 사용하여 채팅 기능을 구현하기로 정했다.

HTTP 폴링
클라이언트가 일정한 주기로 서버에 요청을 보내는 방식이다. 실시간성을 제공하지 않으며, 주기적인 요청으로 인해 서버 부하와 네트워크 트래픽이 증가할 수 있다.

SSE(Server-Sent Events)
서버에서 클라이언트로의 단방향 실시간 데이터 전송을 위한 프로토콜이다.
클라이언트는 서버에 연결을 열어두고, 서버에서 클라이언트로 데이터를 지속적으로 전송한다.

Websocket이란

Websocket의 동작방식

클라이언트가 초기에 HTTP GET으로 Handshake 요청 시 upgrade 헤더를 사용하여 웹소켓을 사용할 것을 서버에 알린다. 서버는 클라이언트의 요청에 대해 HTTP 101 Switching Protocols 응답 코드로 응답하고 이후부터 웹소켓 프로토콜을 사용하여 통신한다. 웹소켓 프로토콜은 TCP/IP 기반으로 동작한다.

Websocket의 특징

  • 웹소켓은 클라이언트와 서버 간의 양방향 통신을 제공하는 실시간 통신 프로토콜이다.
  • 지속적인 연결을 통해 클라이언트와 서버 간에 데이터를 실시간으로 전송하고 수신할 수 있다.
  • 실시간 채팅, 게임, 주식 시세 업데이트 등에 많이 사용된다.
  • 웹소켓 API를 사용하여 클라이언트와 서버 간의 통신을 구현할 수 있으며, 다양한 언어와 프레임워크에서 지원된다.

STOMP(Simple Text Oriented Message Protocol) 사용하기

만약 채팅 기능을 WebSocket 프로토콜만을 사용하여 구현한다면, 메시지 포맷 형식이나 메시지 통신 과정, 세션 관리 등을 일일이 처리해야 하는 번거로움이 있다. 이를 해결하기 위해 STOMP를 서브 프로토콜로 사용하여 메시징 처리를 최적화할 수 있다.

STOMP는 WebSocket 프로토콜 위에서 동작하는 메시지 송수신을 효율적으로 처리하기 위해 개발된 프로토콜이다. STOMP는 pub/sub 구조로 되어 있어 메시지 송수신 처리 부분이 명확하게 정의되어 있어 개발자가 STOMP 스펙의 규칙을 잘 따른다면 메시징 처리를 간편하게 할 수 있다. 이를 통해 STOMP 를 사용하면 채팅 서버 개발에 용이하고 효율적인 메시징 처리를 할 수 있다.

STOMP의 특징

  • Spring이 기본적으로 제공하는 내장 메시지 브로커가 아닌 외부 메시지 큐(RabbitMQ, ActiveMQ, Kafka 등)를 연동해서 사용할 수 있다.
  • 연결 주소마다 새로운 Handler를 구현하고 설정해줄 필요 없다. 클라이언트에서 메시지를 보내면, 해당 메시지는 @MessageMapping에서 처리된다.
  • STOMP의 destination 및 message type을 기반으로 메세지를 보호하기 위해 스프링 시큐리티를 사용할 수 있다.

STOMP의 동작방식

STOMP는 pub/sub 패턴으로 동작한다. pub/sub 패턴은 메시지를 발행하는 곳과 해당 메시지를 구독하는 곳이 분리되어 있으며, 발행된 메시지는 여러 개의 구독자에게 동시에 전달된다.

STOMP에서 클라이언트는 특정 주제(topic)에 대해 구독(subscribe)을 요청하고, 해당 주제로 발행(publish)된 메시지를 수신(receive)할 수 있다. 클라이언트가 구독을 요청하면 해당 주제로 발행된 모든 메시지를 수신할 수 있게 된다. 이를 통해 다수의 클라이언트가 동일한 주제에 대한 메시지를 구독하고 처리할 수 있다.

1대1 채팅 기능에서는 한 명의 유저가 본인이 속한 채팅방(topic)을 구독(subscribe)하면서, 해당 채팅방에 메세지를 발행(publish)하는 역할을 수행해야 한다.






저도 공부하는 입장이라 부족할 수 있습니다. 따라서 조언과 피드백은 항상 열려있습니다!

profile
안녕하세요! 질문과 피드백은 언제든지 환영입니다:)

0개의 댓글