채팅 서버 구현 방법
채팅 구현 방법에는 크게 4가지가 있습니다.
- Polling 방식
- Long Polling 방식
- Streaming 방식
- WebSocket 방식
1~3번 방법은 일반적인 RESTFUL API를 이용한 구현 방법입니다. 그래서 비교적 구현이 쉽습니다. 하지만, 이들의 특성상 클라이언트 → 서버로 데이터 전송이 가능하지만, 서버 → 클라이언트로 전송은 불가능합니다. 이를 극복하기 위해서 나온 방법이 바로 4번 WebSocket 방식입니다.
1~3번은 100% 실시간성을 보장하지 않습니다. 당연히 일정시간 간격으로 채팅을 서버로부터 끌어오는 방식이기 때문에 그렇습니다. 그렇지만, 채팅이 주력 기능이 아니라서 100% 실시간성을 보장 안해도 된다던지, 아니면 유튜브 Live 채팅같이 동시다발적으로 여러명이 채팅을 보내서 문맥이 별로 중요하지 않은 순간에는 적합합니다.
각각에 대해서 자세히 알고싶다면 아래 블로그를 참고해주세요.
Note
일반적으로 채팅은 많이들 websocket으로 구현한다고 생각합니다.
하지만, websocket은 리소스를 굉장히 많이 잡아먹고, 여러 단점이 있기 때문에 현업에서 사용하는 경우는 드뭄니다.
또한, Websocket은 구현이 매우 까다롭다는 단점이 있습니다. 따라서, 리소스를 비교적 덜 잡아먹고, 마감 기한이 얼마 안남은 지금으로써는 Long Polling을 통한 채팅서버 구현이 적합하다고 생각했습니다. 자세한 내용은 아래 블로그를 참고해주세요.
우리는 채팅을 왜 Long Polling으로 개발했는가?
카카오톡 일부 기능과 페이스북은 Long polling 방식으로 구현한다고 알려져 있습니다.
당근 마켓은 WebSocket으로 구현했다고 합니다. (18분 정도부터 보시면 됩니다.)
왜 채팅 서버를 분리했는가?
우리 메인 Spring 비즈니스 서버는 다른거 처리하기도 바쁩니다. 거기에 Long Polling으로 여러 사용자가 동시 다발적으로 요청을 보낸다? 그럼 메인 비즈니스 서버의 성능을 매우 떨어질 것이며, 응답시간은 길어질 것입니다. Software Quality가 안좋아지죠. 그래서 분리했습니다.
채팅 서버로 Ruby on Rails를 채택한 이유는?
몇가지 이유를 들겠습니다.
- Ruby라는 언어는 매우 쉽습니다. 파이썬처럼 프로그래밍 해보지 않은 사람들에게 입문용으로 추천이 가능할 정도로 굉장히 간단합니다. 게다가 저는 어릴 때 RPG Vx ACE라는 게임 만들기 툴에서, 스크립트 언어로 Ruby를 사용하면서 어느정도 조금은 아는 상태였습니다.
- 테스트 코드가 작성하기가 매우매우 쉽습니다. Spring은 Test Code를 작성하려면 굉장히 여러과정을 거쳐야하며 복잡합니다. 반면에 Ruby On Rails은 굉장히 간단하게 구성되있습니다.
- Ruby On Rails는 빠른 개발을 지향합니다. COC원칙을 중요시해서, 설정이 거의 없습니다. Spring으로 서버 하나 만들려면 하나부터 열까지 설정할게 많아서 매우 복잡합니다. 프로젝트 마감 기한이 얼마 안남은 지금, Ruby On Rails만큼 좋을게 없다고 생각했습니다. (Express도 있긴 하지만... 개인적으로 JavaScript를 혐오합니다.) 특히, 언어의 특성이 애자일에 초점이 맞춰져 있기 때문에 생산성이 높다고 합니다.
- Ruby On Rails는 DRY 원칙을 중요시합니다. 즉, 내부에서 같은 처리 또는 정의를 반복해서 사용하는 것을 극도로 싫어합니다. 따라서, Rails는 데이터 베이스 스키마 정의를 설정 파일로 별도 작성하지 않아도 알아서 다 처리해줍니다. Spring은 하나하나 다 처리해야 했는데...
추가적으로 현업 Spring 개발자가 바라보는 Ruby on Rails라는 글 참고해주시면 더 좋을 것 같습니다.
Ruby on Rails을 바라보는 시각
하지만 제가 이번에 해보면서 느낀 몇가지 단점도 있는데...
- 한국에선 너무 비주류라서 한국어 자료가 거의 없다. 저도 그래서 다 영어로 공부했습니다....
- Window 환경과 잘 맞지 않는다. Window에서 WSL 환경 구축으로 바꾸니깐 바로 해결....
- 디버깅이 어렵다. (Type Free하기 때문임)
- Django만큼 느리다. 근데 이건 Spring도 개느려서 뭐... 빠르게 하고싶으면 C++로 서버짜야죠.
그래도... 저는 당근마켓에서 메인 프레임워크로 사용하길래 궁금해서 써봤습니다. Express로는 개발하고 싶지 않았기 때문에...
Message Queue는 왜 안썼나요?
원래는 아래 아키택처를 고민했습니다.
jwt 토큰 통과 - 최초 채팅 요청시, chat_room을 하나 생성해서 DB에 저장. (PK는 chat_room, 칼럼에는 두 유저의 id) - chat_room_id를 통해서 채팅방 입장, 지속적인 long polling 실시. 이때, message queue로 부터 받아오는 것임. - 채팅을 보낸다? message queue에 올라감
하지만, 위 방식에는 치명적인 단점이 있었습니다. Message Queue로 RabbitMQ를 사용하려고 했으나... 이런 메시지큐의 최대 장점은 이벤트가 발생했을 때 즉시 반응하도록 설계되었다는 것입니다. 비동기적으로 메시지를 처리하고, 이를 큐에 넣어놨다가 즉시 처리하도록 하는 것입니다. 즉, Redis Stream이나 RabbitMQ같은 것들은 이것들이 추구하는 아키택처와 맞지 않으며, 잠재력을 제한하는 꼴입니다. Message Queue방식은 WebSocket에서 좋습니다.
따라서, Message Queue는 버리고, DB에 채팅 내역을 저장하고, 이를 지속적으로 polling 해오는 방식으로 구현했습니다.
DB에 부담이 커지지 않을까요?
지속적으로 DB에 Polling해서 읽는 것은 DB 성능에 치명적입니다. 하지만, 이를 보완하기 위해서 Indexing 해놨기 때문에, 그렇게까지 성능에 치명적이지 않을 것입니다. 다만, DB 용량은 많이 차지할 것입니다...
채팅 시나리오 소개
채팅 시나리오는 아래와 같습니다.
- 채팅 리스트에 들어오면, 내가 속해있는 채팅방 리스트들을 모두 받아옵니다.
- 만약에, 한번도 채팅 해본적 없던 사람과 채팅을 시작하면, 채팅방을 생성해서 연결합니다.
- 채팅방에 입장하면, 쌓여있던 채팅들을 즉시 받아옵니다.
- 채팅방에 입장한 후로, 지속적으로 서버에 long polling을 날립니다. 서버 내부적으로 (확인)->(5초대기)->(확인)->(5초대기)... 를 반복하고 20초가 되는 시점에 timeout이 걸립니다. 그러면 클라이언트는 또다시 long polling 요청을 날리면 됩니다.
- 채팅방 목록에서 최신 메시지 알림이 발생하는 것도 long polling으로 받아옵니다. 위와 방식이 동일합니다. 채팅오면 푸쉬알림 보내는 것도, 백그라운드로 long polling을 계속 보내도록 하면, FCM 쓰지 않아도 충분히 구현 가능합니다.
- 채팅방에 채팅을 올리면, 즉시 채팅이 DB에 기록됩니다. 이때, DB에 Index이 되어있기 때문에, 검색하는데 성능 문제는 없습니다.
'BackEnd > 개발' 카테고리의 다른 글
[개발] NGINX 기초 (0) | 2024.11.12 |
---|---|
[개발] Docker Compose로 검색용 OpenSearch 띄우기 (0) | 2024.11.06 |
[개발] Nginx 환경변수 사용 방법 (0) | 2024.05.08 |