Redis pub/sub을 이용한 채팅 다시 돌아보기 (2)

wellbeing-dough·2025년 4월 22일
post-thumbnail

배경

이전 에는 구독이 끊겼을 경우에 대해서 해결책을 알아봤다
(https://velog.io/@wellbeing-dough/Redis-pubsub%EC%9D%84-%EC%9D%B4%EC%9A%A9%ED%95%9C-%EC%B1%84%ED%8C%85-%EB%8B%A4%EC%8B%9C-%EB%8F%8C%EC%95%84%EB%B3%B4%EA%B8%B0)

이번엔 구독이 안끊겼는데, 중간 메시지가 유실되는 case에 대해서 해결책을 알아보자

이걸 왜 이제서...?

근데 왜 이걸 이제서야 해결할까? 채팅 시스템 구축은 3개월 전에 끝냈는데? 이유는 이렇다

  1. 구독이 끊기는 문제는 너무 치명적임. 유저가 직접 새로고침을 눌러서 재 구독을 하지 않는 한, 메시지 자체가 전송이 안됨 하지만, 구독이 끊기지 않았는데, 한두개의 메시지가 전송되지 않는다는 것이 그렇게 치명적일까? 보통의 유저는 채팅을 한번 더 보낼 것이다. 이게 과연 유저에게 우리 서비스에 이탈 사유가 될까?
  2. 해결 방법은 생각했었다. 하지만 빠르게 MVP를 내야 하는 상황에서 이 해결 방법을 적용하는 시간 대비 효율이 나올까?

어느정도 정신없이 여러 프로젝트를 끝내고 기술 부채를 해결할 수 있는 시간이 생겼다.
또한 중간 메시지 유실이 얼마나 발생하는지 확실하게 트래킹 해볼 수 있다.
이제 해결해보자

문제

구독이 끊기지 않았는데, 중간에 메시지가 하나가 유실됨.

이게 정상적인 흐름이라고 가정해 보자

하지만 "산본역에서" 라는 메시지가 유실된 것이다

구독이 끊기지 않았기 때문에 6, 7번 메시지가 나간 상황이다.

문제 해결


출처: https://www.cspsprotocol.com/tcp-sequence-number/

TCP는 데이터 전송 시 시퀀스 넘버(Sequence Number)를 사용한다.
보내는 데이터 바이트마다 번호를 붙이고, 수신 측에서는 이 번호가 순차적으로 잘 도착했는지 확인한다.

만약 중간에 누락되거나 순서가 꼬이면?

ACK를 멈추거나

재전송 요청하거나

타임아웃 후 자동 재전송이 일어난다

즉, "데이터 유실 → 감지 → 보완" 흐름을 아주 단단하게 갖춘 프로토콜이다.

여기서 해결책을 얻었다.

  1. 위 사진 처럼 메시지에는 채팅방마다 중복되지 않는 고유한 번호를 넣고, 그 번호는 auto increment처럼 천천히 올라간다.
  2. 클라이언트는 메시지 고유 번호가 누락됬을 경우, 채팅 내역 재 조회, 재 구독 요청을 한다.

그림으로 보면

이게 정상적인 상황인데 만약에 "산본역에서" 가 누락됬다고 쳐보자

그렇다.

숫자의 증가 원자성을 어떻게 보장할 수 있을까?

유저 두명이 정말 동시에 메시지를 보내면 숫자가 1씩 증가하지 않고 같은 시퀀스 숫자가 두번 나올 수 있다.
어플리케이션 코드 내에서 AtomicInteger로 하나씩 늘릴 수 있지만 스케일 아웃이 되는 순간 그 또한 보장할 수 없다

출처: https://redis.io/docs/latest/commands/incr/

해당 redis 명령어로 원자성을 보장할 수 있다. 왜? redis는 싱글 스레드니까. 그리고 심지어 시간복잡도도 1이고 ACL categories에 무서운 단어도 없다.

하지만 이미, redis에 너무 많은 의존을 하는 채팅 시스템이다.... 물론 redis에 시간 복잡도가 log n 이상인 명령어를 쓰지 않아서 cpu bound는 없지만, 메모리적인 측면에서 좀 생각을 해봐야 한다.

우리가 redis 에서 메모리로 올라가 있는게
1. 채널 목록 + 구독 목록
2. 채팅방 세션
3. 채팅 시퀀스 번호

여기에 저장된 데이터을 최대한 간결하게 하고
아직 간단하게 추론하여 보수적으로 계산 해 보았을 때, 천명정도가 수용 된다.
물론 redis memory 80퍼 찼을 때, 알림도 있지만 어쨌든 redis에 대한 의존도가 너무 높다

간단한 그림

0개의 댓글