읽음(“read receipt”) 상태를 WebSocket 이벤트로만 처리한다면,
클라이언트가 실시간 연결을 못 잡았을 때는 누락될 수 있다.
그래서 보통 이 둘을 함께 제공한다고 한다.
예를 들어, /api/group/{groupId}/chat/rooms/{roomId}/read-status
같은 엔드포인트를 추가해서
GET /api/group/8/chat/rooms/4/read-status
{
"roomId": 4,
"lastReadMessageId": 123, // 로그인한 내가 이 방에서 마지막으로 읽은 메시지 ID
"unreadCount": 5, // 읽지 않은 메시지 개수
"participants": [
{ "userId": 1, "lastReadMessageId": 123 },
{ "userId": 2, "lastReadMessageId": 120 },
…
]
}
이런 식으로 반환해 주면,
따라서 WebSocket 전용이 아니라, 보완용 HTTP API도 함께 구현하시는 걸 추천한다.
사용자가 채팅방에 입장하면, 클라이언트(JavaScript)가 WebSocket 서버에 연결(ws://… or wss://…).
클라이언트는 특정 채팅방(read-status) 토픽(또는 채널)으로 구독 요청을 보냄.
SUBSCRIBE /topic/chat/rooms/{roomId}/read-status
사용자가 방에 들어오면서 마지막 메시지를 읽으면, 클라이언트는 “읽음” 메시지(read-ack)를 WebSocket으로 전송
SEND /app/chat/rooms/{roomId}/read
Payload: { userId, lastReadMessageId }
서버에서는 해당 payload를 받아 DB에 ChatParticipant.lastReadMessageId
업데이트 후, 같은 방에 구독 중인 모든 클라이언트에 새 읽음 상태를 푸시(PUBLISH).
PUBLISH /topic/chat/rooms/{roomId}/read-status
Payload: {
roomId,
participantStatuses: [
{ userId: A, lastRead: 42 },
{ userId: B, lastRead: 40 },
…
]
}
각 클라이언트는 수신한 participantStatuses
를 바탕으로 “누가 몇 번째 메시지까지 읽었는지” UI(예: 아바타 옆 체크 표시) 를 실시간으로 갱신
클라이언트가 일정 간격(e.g. 5초마다) GET /api/group/{groupId}/chat/rooms/{roomId}/read-status
호출.
chatValidator
로 그룹·방 권한 검증ChatParticipantRepository
로 내 lastReadMessageId
와 언읽 개수 계산findActiveByRoom
로 모든 참가자의 lastReadMessageId
수집ReadStatusResponse
반환폴링 응답을 받아 WebSocket과 동일한 방식으로 UI 갱신.
구분 | WebSocket | HTTP(REST) |
---|---|---|
전송방식 | 서버가 클라이언트에 푸시 | 클라이언트가 서버에 풀(Poll) 또는 조회 |
실시간성 | 즉각적 (이벤트 발생 시 바로 전달) | 폴링 주기에 따라 지연 발생 가능 |
부하(Push vs Poll) | 사용자 수·이벤트 수만큼 메시지 전송 | 조회 빈도×사용자 수 만큼 HTTP 요청 발생 |
구현 복잡도 | WebSocket 연결·토픽 구독·브로드캐스트 로직 필요 | 단순 REST 컨트롤러 + 주기적 호출 스케줄링 |
사용 시나리오 | 1:1 대화·그룹 대화 같은 실시간 채팅 | 부하가 높거나 간헐적 확인이 필요할 때 |
즉, 페이지 진입 직후 GET /read-status로 초기 상태를 받아오고, 이후 WebSocket으로 실시간 변화를 처리하는 하이브리드 방식을 많이 씀
Tip: 폴링 주기를 너무 짧게 잡으면 서버에 부담이 커지고, 너무 길게 잡으면 사용자 경험이 떨어집니다. WebSocket이 여의치 않을 때만 5–10초 주기의 폴링을 고려해 보기