(CS Study) Redis를 메시지 브로커로 : Pub/Sub 가이드
- -
👀 들어가기 전에
Redis를 여태 한 번도 사용해본 적이 없는 형태, Pub/Sub에 대해 이해해보려 합니다.
항상 '채팅 기능 구현시 Redis의 Pub/Sub을 사용하면 고부하 환경에서도 실시간성을 보장할 수 있다. '라고만 들어왔지,
정확하게 왜 그런지, 어떤 부분이 장점이고 어떤 부분이 단점인지까지 고민해보는 시간이 없었습니다.
그렇기에 이 글에서는 Redis Pub/Sub에 집중하여 이해해보는 시간을 갖도록 하겠습니다.
기술 선택의 폭과 판단력을 기르기 위해 기술적 선택지를 넓혀가는 시간을 가져보도록 하겠습니다.
👀 본론
1. Pub/Sub이란 무엇인가 : Redis에서의 위치
Pub/Sub은 Publisher(발행자)와 Subscriber(구독자)의 약자로 발행, 구독 패턴을 의미합니다.
발행자가 특정 채널에 메시지를 발행하면, 해당 채널을 구독하고 있는 모든 구독자가 실시간으로 그 메시지를 수신하는 구조입니다.
이 패턴의 가장 큰 강점은 느슨한 결합(Loose Coupling)일 것 같습니다.
서비스 A는 서비스 B,C,D가 자신의 메시지를 구독하고 있다는 사실을 알 필요가 없습니닫.
반대로 구독자들은 누가 발행하는지 신경쓰지 않아도 됩니다. 채널 이름 하나만으로 발행자와 구독자가 연결됩니다.
Pub/Sub에는 두 가지 분리 특성이 존재합니다.
- 시간적 분리 : 일반적인 Pub/Sub에서는 발행자와 구독자가 동시에 존재할 필요가 없습니다.
하지만 Redis Pub/Sub은 중간에 메시지를 저장하지 않습니다.
메시지의 영속성이 없기 때문에 완전하게 활용하려면 발행자와 구독자 모두 동시에 연결되어 있어야 합니다. - 공간적 분리 : 발행자는 구독자의 위치를 모르고 구독자는 발행자의 위치를 모릅니다.
이 특성은 모든 메시지큐 시스템에서 공유하는 특징일 것 같습니다.
그렇다면 Redis생태계 안에서 Pub/Sub은 어디에 위치할까요?
Redis Pub/Sub은 가장 끝단에 있는 가장 단순한 형태의 메시징 시스템입니다.
소위 Fire & Forget 방식으로 메모리만 사용하는 초경량구조입니다.
스펙트럼으로 놓고 보면 아래와 같습니다.
Redis Pub/Sub → Redis Streams → RabbitMQ/Kafka
비영속 브로드캐스트 시스템 로그 기반 메시지 저장 영속성과 소비 보장을 제공하는 메시징 시스템
Redis Pub/Sub은 가볍고 유실이 허용되는 이벤트에만 적합합니다.
반드시 전달되어야하는 중요한 메시지라면 Redis Pub/Sub의 대상이 아닌 것입니다.
2. 핵심 명령어 - SUBSCRIBE, PSUBSCRIBE, PUBLISH
Redis Pub/Sub에는 세가지 핵심 명령어가 있습니다.
일반 구독 - SUBSCRIBE
구독자는 하나 이상의 채널을 구독할 수 있습니다.
SUBSCRIBE 명령을 실행하는 순간, 클라이언트는 구독 모드로 전환됩니다.
구독 모드에서는 Pub/Sub 관련 명령 외에는 다른 Redis 명령을 실행할 수 없습니다.
여러 채널을 동시에 구독하는 것도 가능하며 각 채널로부터 독립적으로 메시지를 수신합니다.
굳고 해제는 UNSUBSCRIBE 명령어로 처리됩니다.
SUBSCRIBE news sports weather
UNSUBSCRIBE news
패턴구독 - PSUBSCRIBE
와일드카드 패턴으로 여러 채널을 한 번에 구독하는 방식입니다.
와일드카드에는 *과 ? 두가지를 사용할 수 있습니다.
- * : 0개 이상의 모든 문자를 의미
- ? : 정확히 1개의 임의 문자를 의미
PSUBSCRIBE news.* # news.sports, news.weather 등 모두 매칭
PSUBSCRIBE user.??.log # 정확히 2글자 사이에 있는 패턴 매칭
여기서 반드시 주의해야할 점이 있습니다.
Redis 내부적으로 일반 구독과 패턴 구독은 독립적으로 관리됩니다.
같은 채널에 대해 SUBSCRIBE와 PSUBSCRIBE를 동시에 적용했다면,
해당 채널에 메시지가 발행될 때 메시지를 두 번 수신하게 됩니다.
중복 수신을 피하고자 한다면 구독 방식을 일관되게 유지해야 합니다.
메시지 발행 - PUBLISH
PUBLISH 명령어로 특정 채널에 메시지를 발행하면 해당 채널을 구독 중인 모든 클라이언트에게 메시지가 전송됩니다.
PUBLISH news.sports "오늘 경기 결과: 3-1 승리"
반환 값은 메시지를 수신한 구독자 수가 됩니다. 이 수는 일반 구독자 수와 패턴 매칭으로 수신한 구독자 수를 합산한 값이 됩니다.
3. 내부 구현 - 자료 구조와 시간 복잡도
Redis가 Pub/Sub을 내부적으로 어떻게 구현하는지 이해하면 성능 관점에서의 설계 판단이 조금 더 명확해집니다.
두가지 핵심 자료 구조
Redis는 Pub/Sub 구현을 위해 두 가지 자료구조를 사용합니다.
1) pubsub_channels : 해시테이블
키는 채널 이름, 값은 해당 채널을 구독하는 클라이언트들의 연결리스트 (LinkedList)입니다.
일반 SUBSCRIBE가 이 구조를 사용하게 됩니다.
2) pubsub_patterns : 연결 리스트
각 노드가 패턴 문자열과 해당 패턴을 구독하는 클라이언트 리스트를 갖는 연결리스트 구조입니다.
PSUBSCRIBE가 이 구조를 사용합니다.
PUBLISH 명령 실행 시 내부 동작
PUBLISH 명령이 실행되면 Redis는 아래 두 단계를 순서대로 수행합니다.
Step1 : 정확한 채널 매칭
pubsub_channels 해시테이블에서 채널 이름으로 조회합니다.
해시테이블 특성상 시간 복잡도는 O(1)입니다.
이후 해당 채널의 구독자 리스트를 순회하며 각 클라이언트에게 메시지를 복사해 전달합니다.
구독자가 N명이라면 이 단계는 O(N)이 됩니다.
Step2 : 패턴 매칭
pubsub_patterns 연결 리스트 전체를 처음부터 끝까지 순회하며 패턴 매칭을 수행합니다.
패턴이 P개이고 문자열 비교 비용이 평균 M이라면, 이 단계의 복잡도는 O(P*M)입니다.
패턴이 매칭되면 해당 구독자들에게 동일하게 메시지를 전달합니다.
→ 패턴 구독은 일반 구독보다 훨씬 비싼 연산입니다.
모든 패턴 리스트를 순회해야 하기 때문에 등록된 패턴이 많을수록
PUBLISH 비용이 선형으로 증가합니다. 패턴 구독은 정말 필요한 경우에만, 최소한으로 사용해야 합니다.
메모리 사용 관점에서의 핵심
Redis Pub/Sub에서 메모리와 관련해 가장 중요한 특성은 두 가지입니다.
첫째, 메시지를 저장하지 않습니다.
영속성이 없습니다. 메시지는 전송되는 순간 소멸됩니다.
둘째, 채널은 구독자가 있을 때만 메모리에 존재합니다.
구독자가 없는 채널은 pubsub_channels에서 제거됩니다.
채널이 1,000개 있더라도 아무도 구독하지 않으면 메모리 사용량은 0에 가깝습니다.
Pub/Sub이 가벼운 핵심적인 이유가 바로 여기있습니다.
왜 메시지를 저장하지 않을까?
여기까지 보신 분들은 아마 위 의문점으로 자연스럽게 이어지게 될 것이라고 생각합니다.
Pub/Sub이 추구하는 방향은 성능이 우선입니다.
메시지를 저장하게 되면 아래의 부하가 따라옵니다.
- Disk I/O 발생 → 병목 현상
- 메모리 할당과 해제 → GC 오버헤드
- ACK 메커니즘 구현의 필요성 → 비즈니스 로직 복잡도 증가
Pub/Sub은 이 모든 것을 생략해 빠른 메시지 전파에만 집중하게 됩니다.
따라서 구독자가 없는 상태에서 메시지를 발행하면, Redis는 전달을 시도조차 하지 않습니다.
전통적인 메시지 큐처럼 큐에 쌓아두고 나중에 전달한다는 개념 자체가 없습니다.
네트워크 파티션이 발생한 경우도 마찬가지입니다.
Redis는 전송 실패를 감지할 수 있지만, Pub/Sub에는 따로 Retry 정책이 없고 누락된 메시지를 복구하는 것도 불가합니다.
4. 분산환경에서의 Pub/Sub : 클러스터와 레플리카
Redis 클러스터에서의 동작
Redis 클러스터에서 일반 키는 해시슬롯에 따라 특정 노드에 저장됩니다.
하지만 Pub/Sub은 다릅니다. 구독자가 어느 노드에 연결되어 있는지 알 수 없기 때문에,
메시지를 모든 노드에 브로드캐스트해야 합니다.
발행자가 노드 A에 연결되어 있고 구독자가 노드 B에 연결되어 있어도 메시지가 전달되어야 하기 때문입니다.
이를 위해 Redis는 클러스터 버스(Cluster Bus)를 사용합니다.
클러스터 버스는 노드 간 통신을 위한 별도의 TCP 연결로, 일반적으로 기본 포트에 10,000을 더한 포트를 사용하게 됩니다.
Gossip 프로토콜 기반의 바이너리 프로토콜로 동작합니다.
PUBLISH가 실행되면 아래의 순서로 동작합니다.
- 발행 노드가 자신에게 연결된 로컬 구독자들에게 즉시 메시지를 전달합니다.
- 이후 클러스터 버스를 통해 다른 모든 노드로 메시지를 전파합니다.
- 각 노드는 수신한 메시지를 자신의 로컬 구독자들에게 전달합니다.
이 구조의 문제가 바로 네트워크 대역폭 증폭입니다.
노드가 N개인 클러스터에서 1KB 메시지를 초당 10,000개 발행하면
단일 노드 기준으로는 10MB/s의 트래픽이 발생합니다.
하지만 10개 노드 클러스터라면 모든 노드 합산 기준으로 100MB/s가 됩니다.
클러스터 규모가 커질수록 Pub/Sub 트래픽은 N배로 증폭됩니다.
Redis 7.0의 해답 : Sharded Pub/Sub
이 문제를 해결하기 위해 Redis 7.0에서 Sharded Pub/Sub이 도입되었습니다.
SPUBLISH와 SSUBSCRIBE 명령을 사용하며 채널이 해시 슬롯에 할당됩니다.
SSUBSCRIBE news.sports # 특정 샤드의 구독자만 수신
SPUBLISH news.sports "경기 시작"
채널이 담당하는 샤드의 노드에만 메시지가 전달되므로 네트워크 트래픽이 N배에서 상수 수준으로 대폭 줄어들게 됩니다.
다만 TradeOff가 존재합니다.
채널이 특정 슬롯에 매핑되며, 해당 슬롯을 담당하는 노드에서만 메시지가 처리됩니다.
그렇기에 클라이언트가 올바른 노드에 연결해야하며, 클러스터 리샤딩이 발생하면 구독이 끊어질 수도 있습니다.
마스터-레플리카 환경에서의 동작
마스터-레플리카 구조에서 일반 키는 마스터에서 레플리카로 복제됩니다.
하지만 Pub/Sub 메시지는 복제되지 않습니다.
마스터에서 발행된 메시지는 마스터에 연결된 구독자들에게만 전파됩니다.
레플리카로는 전달되지 않습니다. 이유는 단순합니다.
복제가 의미 있으려면 과거 메시지 기록과 안정성이 전제되어야합니다.
하지만 Pub/Sub의 설계 철학은 바로 지금 이 순간에만 집중하는 것입니다.
영속성도 신뢰성도 추구하지 않기 때문에 복제 자체가 불필요합니다.
따라서 실무에서 Pub/Sub을 활용할 때는 모든 Pub/Sub 클라이언트를 마스터에 연결해야합니다.
레플리카에 구독자를 연결하면 메시지를 수신하지 못하게 됩니다.
👀 결론
Redis Pub/Sub을 공부해보며 느낀 점은 Redis는 정말 뚜렷한 철학을 가지고 설계되었다는 것입니다.
"빠르게 전파하되 책임은 지지 않는다." 메시지를 저장하지 않고 Retry도 없고 복제도 하지 않는다.
이 선택들이 Redis의 설계 철학을 더 뚜렷하게 보여주는 것 같습니다.
Pub/Sub을 처음 접했을 때는 "왜 메시지를 저장 안하지?"라는 의문이 들었습니다.
하지만 내부 구조를 이해하고, Redis의 철학에 대해 공부해보고 나면서 납득이 되었습니다.
결론적으로, Redis Pub/Sub은 유실을 허용할 수 있는 실시간 이벤트 전파에 최적화된 도구입니다.
실제 프로젝트에서 생각해보면 알림, 실시간 피드, 캐시 무효화 이벤트처럼
일부 메시지가 누락되어도 서비스에 치명적이지 않은 곳에 어울립니다.
반대로 결제, 재고 변경, 주문 처리처럼 단 하나의 메시지 유실도 허용할 수 없는 도메인이라면,
Redis Streams나 Kafka처럼 영속성을 보장하는 시스템을 선택해야 할 것 같습니다.
'Study' 카테고리의 다른 글
| [CS Study] Kafka, 들어는 봤지만 잘 모르는 그거... (1) | 2026.04.25 |
|---|---|
| (CS Study) Docker 컨테이너 부터 Kubernetes까지 (feat. namespace가 바꾼 세상) (0) | 2026.04.18 |
| (CS Study) Redis의 핵심 - Key 관리 기법과 영속성 (1) | 2026.04.10 |
| (CS Study) Redis 기본 자료구조 깊게 파헤치기 - Set & Hash (마지막) (0) | 2026.04.10 |
| (CS Study) Redis List 정복 : 내부 구조부터 Queue / Stack으로의 활용까지 (0) | 2026.04.06 |
소중한 공감 감사합니다