![article thumbnail image](https://blog.kakaocdn.net/dn/xD24A/btrXK8yUwZ0/7KWsnIgA7g8LCTeGPKuDKk/img.png)
0. 들어가며 🏃🏻♂️
진행중인 팀 프로젝트에서 웹소켓을 통해 실시간 채팅 기능을 구현했습니다. 채팅기능을 구현한 이후 상대방이 채팅을 읽었는지 여부를 확인할 수 있으면 좋겠다는 의견이 나와 이를 구현하게 되었고, 해당 요구사항을 반영하기 위해 고민했던 과정들을 블로그에 정리해보려 합니다.
1. 요구사항📕
변경된 요구사항은 "아래 사진과 같은 기능을 추가하자" 였습니다.
위 사진처럼 빨간 박스로 쳐진 부분과 같이 읽지 않은 메시지를 표시하는 기능이 있다면 좀 더 서비스 경험이 좋아질 것이라는 의견이 있었고, 이 부분을 새롭게 구현해야 했습니다.
2. 고민해야했던 점 과 구현한 방식 🤔
먼저 고민했던 부분은 기존 구조로는 채팅을 읽었는지 여부를 알기가 어려워 어떤 정보를 추가해야할지를 결정하는 것이었습니다. 채팅을 읽었는지 여부를 알기 위해서 채팅정보를 담는 Entity에 readCount 라는 필드를 만들고 기본 값을 2로 저장했습니다. 저희 서비스에서는 채팅에 두명만 참여할 수 있었기 때문입니다. 채팅을 한명이 읽을때마다 readCount 값을 1씩 감소시키는 방식을 사용했죠.
채팅 Entity의 readCount를 감소시키는 상황을 좀 더 구체적으로 살펴보면 아래 두 가지 경우가 있습니다.
- 채팅을 하는 두 명 모두가 채팅방에 있는 경우
- Stomp를 기반으로한 pub/sub 구조이므로 프론트엔드에서 subscribe 하자마자 서버에게 메시지를 읽었음을 알린다.
- 이 때 /chat/{chat-id} 라는 엔드포인트로 요청을 보내 서버에게 메시지 읽었음을 알린다.
- 위 요청을 받으면 서버는 chat-id에 해당하는 채팅의 readCount를 감소시킨다.
- 이런 방식을 사용하면 subscriber가 두명이므로 readCount가 2만큼 감소하게 되고, readCount가 0인 것을 통해 해당 채팅은 두 명 모두가 읽었음을 확인할 수 있다.
- 한명만 채팅방에 있는 경우
- 위 방식대로 하면 readCount는 1이 감소하게 된다. (채팅방의 subscriber가 한명이므로)
- 남은 readCount 1은 나머지 한명이 채팅방을 불러올 때 readCount를 1 감소시킨다.
- 주의) 채팅방을 불러올 때 내가 발신자가 아닌 경우 && readCount == 1 인 경우만 readCount를 1 감소시킨다.
위와 같은 방식을 사용하면 readCount 라는 값을 통해 채팅을 상대방이 읽었는지 여부를 알 수 있게 되고, 이를 통해 안읽은 메시지가 몇개 인지도 파악할 수 있게 됩니다.
다음으로 고민해야했던 점은 채팅방에 없는 유저가 실시간으로 안읽은 메시지가 있는지 확인하는 기능을 어떻게 구현해야하는가 였습니다. 내 프로필창 혹은 내 채팅 목록들을 보면서 안읽은 채팅이 있는지 여부를 알 수 있어야했는데 웹소켓은 채팅방에 들어가서야 연결이 되므로 실시간으로 클라이언트에게 안읽은 채팅이 있는지 여부를 알리기가 어려웠습니다. 또한 내 프로필창, 내 채팅 목록에 들어온 순간부터 웹소켓을 연결시키는 방식을 사용한다면 큰 규모의 서비스로 변했을 때 웹소켓 연결 한계로 문제가 생기지 않을까 라는 걱정이 생겼고, 서버에서 알림을 보낼 수 있다는 SSE 방식 역시 비슷한 고민을 하게끔 만들었습니다.
이러한 고민끝에 클라이언트에서 Polling 방식을 사용해서 안읽은 채팅이 있는지 여부를 서버에게 물어보도록 구현하게 되었습니다. 채팅방에 없는 사람에게는 안읽은 채팅 숫자가 늘어나는 것이 실시간으로 변해야할만큼 중요한 것이 아니라고 판단했습니다. 이를 토대로 안읽은 채팅 숫자가 변경되야할 속도는 어느정도일지 테스트해보았고 저희 프로젝트에서는 Polling 주기를 2초로 결정했습니다.
아래는 구현을 완료한 모습입니다!
3. 한계 및 개선해야할 점❗️
위 구조로 요구사항에 맞는 기능을 구현했지만 아래와 같은 한계 및 개선점이 생각나서 정리해두고 추후 좀 더 실력이 쌓였을 때 보완해보고자 합니다.
- 채팅방이 2명이 아닌 여러명인 경우 readCount만 가지고는 특정 유저가 읽었는지 아닌지 여부를 판단할 수 없습니다. 따라서 많은 채팅 서비스에서 제공하는 단체 채팅방에서는 적용할 수가 없습니다.
- 채팅을 전부 불러와서 (readCount == 1 && 발신자가 내가 아닌 경우)를 카운팅하고 이를 토대로 안읽은 채팅의 개수를 세는 방식입니다. 채팅의 경우 DB에 엄청나게 많이 쌓이는 데이터인데 이를 전부 애플리케이션에 부른다면 부담이 너무 클 것 같습니다. 이를 해결하고자 채팅을 페이징하면 페이징 수보다 더 많은 숫자의 채팅을 읽지 않은 경우가 문제가 됩니다.. 카카오톡을 생각해보면 최대 읽지 않은 개수가 채팅방 당 300개로 고정되어있는데 이 부분이 페이징 수이지 않을까 싶어서 이 부분을 아이디어 삼아 로직을 개선해볼 수 있을 것 같습니다.
- 유저가 안읽은 채팅 개수를 내 프로필과 채팅 목록에서 보여주는데 이 부분을 Polling으로 구현했다보니, Polling의 단점, 즉 업데이트가 없어도 서버에 요청을 계속 보낸다라는 아쉬움이 생깁니다. 웹소켓과 SSE 연결이 대규모 트래픽에서 어떤 영향을 미치는지 아직 알기가 어려워 해당 부분은 조금 더 경험이 쌓이면 다시 판단해봐야할 것 같습니다.
- 웹소켓을 통해 채팅을 구현했음에도 불구하고 Subscriber에게 메시지가 도착시 서버에 HTTP 요청을 보내 읽음을 확인하는 구조입니다. HTTP보다 웹소켓을 통한 메시지가 훨씬 가볍다는 이점을 가지기도하는데 저의 구현 방식에서는 매 채팅마다 HTTP 요청이 생겨 웹소켓의 장점이 퇴색됩니다.
4. 나가며💨
이번 글에서는 채팅 읽었음 여부 확인과 안읽은 메시지 개수를 유저에게 보여주는 기능을 구현하며 고민했던 점, 구현한 방식, 개선해야할 점 등에 대해 정리해보았습니다.
현재는 데이터도 그리 많지 않고, 대규모의 유저가 사용하지 않는 상황이라서 잘 작동하는게 아닐까라는 걱정이 드는 것 같습니다. 내가 한 구현에 근거를 가지고 분석할 수 있는 실력을 갖추기까지 열심히 배워야겠다는 생각이 듭니다.
'Spring > Project' 카테고리의 다른 글
[Spring & Project] 소셜로그인 기능에 전략 패턴을 적용하기 (0) | 2023.02.18 |
---|