이 문서를 통해 아래 질문들에 답할 수 있게 됩니다.
- Redis Pub/Sub과 Redis Stream은 메시지 보관과 재처리 관점에서 무엇이 다른가?
- 어떤 이벤트는 유실을 허용할 수 있고, 어떤 이벤트는 Stream이나 Kafka가 필요한가?
- Java/Spring 서비스에서 Pub/Sub을 잘못 선택하면 어떤 downstream 누락이 생기는가?
- 개인 프로젝트에서 Redis Stream을 도입할 때 어떤 최소 운영 지표를 남겨야 하는가?
개요
Redis Pub/Sub은 실시간 fan-out에 가깝다.
subscriber가 접속해 있을 때 publish된 메시지를 받는다.
subscriber가 없거나 끊겨 있으면 메시지는 저장되지 않는다.
Redis Stream은 entry를 Stream에 저장하고 ID로 읽는다.
consumer group, pending entry, ack를 제공한다.
따라서 선택 기준은 “Redis를 쓸 것인가”가 아니라 “메시지를 잃어도 되는가”다.
Pub/Sub 모델
Pub/Sub은 단순하고 빠르다.
SUBSCRIBE order-notifications
PUBLISH order-notifications "order:10042:paid"publisher는 subscriber가 실제로 처리했는지 알지 못한다.
subscriber가 잠시 내려가 있으면 그 사이 메시지는 사라진다.
그래서 Pub/Sub은 다음에 잘 맞는다.
- 접속 중인 WebSocket 서버에 상태 변경을 알림
- 캐시 무효화 신호
- 잠깐 놓쳐도 다음 조회로 회복되는 UI 알림
- 운영상 “최신 상태만 다시 보면 되는” 이벤트
Pub/Sub은 감사, 정산, 배송 시작처럼 반드시 처리되어야 하는 이벤트에는 부적합하다.
Stream 모델
Redis Stream은 entry를 저장한다.
XADD order-events * eventType OrderPaid orderId 10042 amount 49000entry는 1719720000000-0 같은 ID를 가진다.
consumer는 이 ID를 기준으로 읽고 ack한다.
XGROUP CREATE order-events settlement-group $ MKSTREAM
XREADGROUP GROUP settlement-group consumer-1 COUNT 10 STREAMS order-events >
XACK order-events settlement-group 1719720000000-0Stream은 Pub/Sub보다 복구 가능성이 크다.
하지만 저장된다는 이유만으로 무한 보관이나 완전한 replay를 기대하면 안 된다.
trim 정책과 Redis 메모리 한계가 있다.
비교 기준
| 기준 | Pub/Sub | Stream |
|---|---|---|
| 보관 | 저장하지 않음 | Stream entry로 저장 |
| 재처리 | 기본 제공 없음 | ID와 pending으로 가능 |
| consumer 상태 | Redis가 처리 상태를 모름 | group과 pending entry를 관리 |
| ack | 없음 | XACK |
| 적합한 용도 | 휘발성 알림, cache invalidation | 짧은 내부 비동기 처리, 간단한 replay |
| 위험 | subscriber 장애 중 유실 | pending 방치, trim으로 replay 불가 |
이 표에서 가장 중요한 줄은 보관과 ack다.
처리 성공을 확인해야 하는 메시지는 Pub/Sub에 넣으면 안 된다.
Spring 관점
Spring에서 Pub/Sub은 listener container로 쉽게 붙일 수 있다.
코드가 쉽다는 이유로 business event를 Pub/Sub에 싣는 것이 문제다.
결제 완료 후 배송 시작 event를 Pub/Sub으로 보내면 배송 subscriber 재시작 중 event를 잃을 수 있다.
반면 재고 화면의 실시간 refresh 신호는 Pub/Sub으로 충분할 수 있다.
Redis Stream을 쓰면 consumer가 읽은 뒤 business 처리 완료 시점에 ack해야 한다.
Stream consumer도 Kafka consumer처럼 중복 입력을 가정해야 한다.
유실 허용 판단
다음 질문 중 하나라도 “아니오”라면 Pub/Sub은 위험하다.
- 메시지를 놓쳐도 다음 주기나 조회로 회복되는가?
- 처리 실패를 사람이 나중에 조사하지 않아도 되는가?
- 메시지별 감사 로그가 필요 없는가?
- 같은 메시지를 나중에 다시 읽을 필요가 없는가?
정산, 주문 상태 전이, 데이터 mart 적재 트리거는 보통 Pub/Sub보다 Stream 또는 Kafka에 가깝다.
캐시 무효화나 화면 refresh는 Pub/Sub으로 충분한 경우가 많다.
Stream도 만능은 아니다
Stream은 Pub/Sub보다 안전하지만 Kafka와 같지 않다.
Redis는 메모리 사용량과 eviction 정책의 영향을 받는다.
Stream trim을 걸면 과거 entry가 사라진다.
consumer group의 pending entry를 방치하면 메시지는 읽힌 상태로 묶인다.
장기 보관, 많은 consumer group, 큰 replay 범위, partition 확장이 필요하면 Kafka를 검토한다.
운영 지표
- publish count: Pub/Sub 또는 Stream으로 발행한 건수다.
- subscriber connected count: Pub/Sub subscriber가 살아 있는지 본다.
- stream length: Stream entry가 얼마나 쌓였는지 본다.
- pending entry count: 읽고 ack하지 않은 entry 수다.
- oldest pending idle time: 가장 오래 방치된 pending 시간이다.
- trim count: 보관 정책으로 제거된 entry 수다.
- memory usage: Redis 메모리 압박을 본다.
Pub/Sub은 놓친 메시지 수를 Redis만으로 알기 어렵다.
반드시 처리되어야 하는 흐름에는 이 사실만으로도 선택 제외 사유가 된다.
개인 프로젝트 기준
개인 프로젝트에서는 같은 이벤트를 Pub/Sub과 Stream으로 각각 처리해 차이를 보여주면 좋다.
subscriber를 끄고 PUBLISH한 메시지가 사라지는지 확인한다.
Stream consumer를 끄고 XADD한 메시지가 나중에 읽히는지 확인한다.
consumer가 읽고 ack하지 않은 entry가 XPENDING에 남는지도 확인한다.
이 실험이 있으면 두 기능의 차이를 API 이름이 아니라 신뢰성 관점으로 설명할 수 있다.
위험 신호!
- 반드시 처리되어야 하는 주문 event를 Pub/Sub으로 보낸다.
- Pub/Sub subscriber 장애 중 유실을 관측할 방법이 없다.
- Stream을 쓰면서 pending entry를 보지 않는다.
- trim 정책 없이 Stream이 계속 커진다.
- Redis Stream을 Kafka 대체재라고만 설명한다.
확인 질문
확인 질문
- 이 메시지는 subscriber가 내려가 있던 동안 사라져도 되는가?
- 아니라면 Pub/Sub은 부적합하다.
- 이 메시지를 나중에 다시 읽어야 하는가?
- 그렇다면 Stream ID, pending, trim 정책이 필요하다.
- Pub/Sub 유실과 Stream pending을 각각 어떤 지표로 볼 수 있는가?
- Pub/Sub은 subscriber 상태, Stream은 length와 pending을 봐야 한다.