메세지별 읽음 안 읽음 처리 구현의 어려움


나만의 카카오톡 프로젝트에서 각 메세지의 읽지 않은 유저의수(unread count)를 추적하는 두 가지 방법과 그 구현의 어려움을 정리.

다른 기능들은 다 구현했으나 아무리 생각해도 이 프로젝트에서 메세지별 몇 명의 유저가 읽었는지 표시가 머리에 구현이 안되어 일단 정리해보았다.

채팅목록에서 로그인한 유저 본인이 해당 채팅방의 마지막 메시지를 읽었는지 안 읽었는지 표시는 그 유저의 해당방의 마지막 active한 시간을 따져보면 가능했다.

하지만 메세지별 이 유저가 읽었는지 안 읽었는지 처리는 정말 큰 데이터베이스에 부하를 주지 않고 하는 방법이 도저히 잘 정리되지 않아서 일단 떠오른 2가지 방법을 정리해봤다.


첫번째 방법. 모든 메시지에 대한 읽지 않음 수(Unread Count) 추적

목표:
  • 채팅방에서 모든 메시지마다 몇 명이 그 메시지를 읽지 않았는지(unread count)를 계산하고 저장해서, 각 메시지와 함께 이 정보를 사용자에게 보여주는 것. 예를 들어, 메시지 1은 2명 unread, 메시지 2는 3명 unread 이런 식.
구현 방법:
  • activeUsers:chatRoomId라는 Redis 셋(set)을 사용해 현재 활성 사용자를 추적.
  • 각 메시지를 보낼 때마다 totalParticipants - activeUsersCount로 unread count를 계산하고, 이 값을 Redis 에 메시지 데이터(예: "messages:chatRoomId" 리스트)에 저장.

왜 어려운가?

데이터 관리 복잡성:
  • 모든 메시지에 대해 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에 주기적으로 동기화하거나, 메시지 보낼 때마다 새로 계산하도록 해야 할 수도 있는데, 이것 또한 서버에 부하를 이를킬 가능성이 있다.

결론

  • 모든 메시지 추적은 너무 복잡하고 부하가 커서 피해야 할 것 같다.
  • 최신 메시지만 추적하는 두 키 방식(activeUsers:chatRoomId, unreadCount:chatRoomId)은 훨씬 간단하지만, 정확성, 동시성, 영속성 같은 문제가 있다. 그래도 구현하려면 이게 목표에 맞고 구현하기 훨씬 쉬운 해결책이다.


Posted on 2025.03.05