메세지별 읽음 안 읽음 처리 구현의 어려움
나만의 카카오톡 프로젝트에서 각 메세지의 읽지 않은 유저의수(unread count)를 추적하는 두 가지 방법과 그 구현의 어려움을 정리.
다른 기능들은 다 구현했으나 아무리 생각해도 이 프로젝트에서 메세지별 몇 명의 유저가 읽었는지 표시가 머리에 구현이 안되어 일단 정리해보았다.
채팅목록에서 로그인한 유저 본인이 해당 채팅방의 마지막 메시지를 읽었는지 안 읽었는지 표시는 그 유저의 해당방의 마지막 active한 시간을 따져보면 가능했다.
하지만 메세지별 이 유저가 읽었는지 안 읽었는지 처리는 정말 큰 데이터베이스에 부하를 주지 않고 하는 방법이 도저히 잘 정리되지 않아서 일단 떠오른 2가지 방법을 정리해봤다.
ㅔ>- 목표:
-
- 채팅방에서 모든 메시지마다 몇 명이 그 메시지를 읽지 않았는지(unread count)를 계산하고 저장해서, 각 메시지와 함께 이 정보를 사용자에게 보여주는 것. 예를 들어, 메시지 1은 2명 unread, 메시지 2는 3명 unread 이런 식.
- 구현 방법:
-
activeUsers:chatRoomId
라는 Redis 셋(set)을 사용해 현재 활성 사용자를 추적.- 각 메시지를 보낼 때마다
totalParticipants - activeUsersCount
로 unread count를 계산하고, 이 값을 Redis 에 메시지 데이터(예:"messages:chatRoomId"
리스트)에 저장.
첫번째 방법. 모든 메시지에 대한 읽지 않음 수(Unread Count) 추적
- 데이터 관리 복잡성:
-
- 모든 메시지에 대해 unread count를 저장하려면 메시지마다 데이터를 유지해야 한다. Redis 리스트나 데이터베이스에 메시지가 쌓일수록 저장 공간과 관리가 복잡해집니다.
- 그리고 결국 이 Redis에 들어있는 데이터들도 메인 데이터베이스 MySQL과 연계를 해야한다.
- 실시간 업데이트 문제:
-
- 사용자가 나중에 채팅방에 들어와 메시지를 읽으면 unread count를 갱신해야 할 수도 있는데, 이를 실시간으로 반영하려면 각 사용자별로 읽음 상태를 추적하는 추가 시스템(예: 사용자별 읽음 마커)이 필요. 이건 엄청난 부하를 초래할 수 있다.
- 확장성 한계:
-
- 채팅방이 크거나 메시지가 많아지면, 모든 메시지의 unread count를 계산하고 저장하는 작업이 느려지고 리소스를 많이 잡아먹을 가능성이 있다. 예를 들어, 1000개의 메시지가 있는 채팅방에서 과거 메시지까지 추적하려면 성능 저하가 심각해질 수 있다.
- 결론:
-
- 모든 메시지에 unread count를 붙이는 건 너무 복잡하고 리소스를 많이 써서, 간단한 채팅 서비스에는 비효율적.
왜 어려운가?
- 목표:
-
- 가장 최근에 보낸 메시지에 대해서만 unread count를 계산하고, 이 값을 채팅방 전체에 대한 단일 값으로 저장해서 메시지와 함께 보내는 것.
- 과거 메시지는 신경 쓰지 않고 해당 채팅방에 마지막 메세지만 몇명이 읽었는지 표시. 인스타 DM이랑 같음
- 구현 방법:
-
activeUsers:chatRoomId
: Redis 셋으로 현재 활성 사용자를 추적 (예: 2명 활성).unreadCount:chatRoomId
: Redis 키로 최신 메시지의 unread count를 저장 (예: 2, 총 4명 - 2명 = 2명 unread).- 메시지를 보낼 때마다
totalParticipants - activeUsersCount
를 계산해unreadCount:chatRoomId
를 갱신하고, 이 값을 메시지와 함께 보냄.
두번째 방법. 최신 메시지에 대한 읽지 않음 수만 추적 (두 개의 키 사용)
- 간단한 관리:
-
- 단일 키(
unreadCount:chatRoomId
)만 관리하면 되니까 저장 공간과 로직이 훨씬 간단해짐.
- 단일 키(
- 과거 데이터 불필요:
-
- 과거 메시지를 추적할 필요가 없어서 데이터가 쌓이지 않고, 항상 최신 상태만 유지하면 된다.
왜 이게 더 쉬운가?
- 정확성 문제:
-
activeUsers:chatRoomId
가 정확히 현재 활성 사용자를 반영해야 하는데, 사용자가 접속/비접속 상태를 빠르게 바꾸면(예: 네트워크 문제로), unread count가 틀릴 수 있다. 예를 들어, 메시지 보내는 순간 사용자가 나갔다가 바로 들어오면 계산이 어긋날 수 있다.
- 동시성 문제:
-
- 여러 사용자가 동시에 메시지를 보내면
unreadCount:chatRoomId
를 덮어쓰는 경쟁 조건(race condition)이 생길 수 있어요. 이를 막으려면 Redis 락(lock)을 추가해야 할 수도 있는데, 그럼 코드가 복잡해진다.
- 여러 사용자가 동시에 메시지를 보내면
- 영속성 한계:
-
- Redis가 재시작되면
unreadCount:chatRoomId
값이 날아갈 수 있다. MySQL에 주기적으로 동기화하거나, 메시지 보낼 때마다 새로 계산하도록 해야 할 수도 있는데, 이것 또한 서버에 부하를 이를킬 가능성이 있다.
- Redis가 재시작되면
하지만 왜 이것도 구현이 어려울까?
-
- 모든 메시지 추적은 너무 복잡하고 부하가 커서 피해야 할 것 같다.
- 최신 메시지만 추적하는 두 키 방식(
activeUsers:chatRoomId
,unreadCount:chatRoomId
)은 훨씬 간단하지만, 정확성, 동시성, 영속성 같은 문제가 있다. 그래도 구현하려면 이게 목표에 맞고 구현하기 훨씬 쉬운 해결책이다.
결론
Posted on 2025.03.05