본문으로 건너뛰기
김신건의 로그

Pub/Sub Bus

· 수정 · 📖 약 2분 · 674자/단어 #architecture #messaging #scaling #network
Pub/Sub, pubsub, publish-subscribe, 메시지 버스, Pub Sub Bus

정의

Pub/Sub Bus (Publish-Subscribe bus)는 메시지 송신자(publisher)와 수신자(subscriber)가 서로를 모르고도 통신할 수 있게 해주는 중간 메시지 라우팅 계층이다.

  • Publisher: 특정 채널/토픽에 메시지를 발행
  • Subscriber: 관심 있는 채널을 구독, 발행된 메시지 수신
  • Bus: 둘 사이의 라우팅 담당 (Redis, Kafka, NATS, RabbitMQ 등)

왜 필요한가, 분산 Stateful 서버의 확장 문제

WebSocket 같은 지속 연결 서버를 여러 인스턴스로 확장하면 새로운 문제가 생긴다.

Client A → Server 1
Client B → Server 2 (다른 서버)

질문: A 가 B 에게 메시지를 보내려면?

서버 1 은 B 와 연결되어 있지 않으니 직접 send 할 수 없다.

해결책: Pub/Sub Bus

1. Server 1 이 A 의 메시지 수신
2. Server 1 → Redis publish "channel-B" with payload
3. Server 2 가 "channel-B" 를 구독 중
4. Redis → Server 2 에 메시지 전달
5. Server 2 → B 에게 send

서버는 자기 클라이언트만 관리하면 됨. 서버 간 라우팅은 bus 가 담당.

동작 패턴

Channel 기반

특정 이름의 채널에 발행/구독.

// Publisher
redis.publish('chat:room:123', JSON.stringify({ from: 'A', text: 'hello' }));

// Subscriber
redis.subscribe('chat:room:123', (message) => {
  const data = JSON.parse(message);
  forwardToClient(data);
});

Pattern 기반 (와일드카드)

redis.psubscribe('chat:room:*', (channel, message) => {
  // chat:room:123, chat:room:456 모두 매칭
});

Topic 기반 (계층화)

Kafka 등에서 토픽을 partition 단위로 분배.

topic: user-events
  partition 0: user_id % 3 == 0 인 이벤트
  partition 1: user_id % 3 == 1
  partition 2: ...

Socket.IO + Redis Adapter 패턴

가장 흔한 실무 패턴.

import { Server } from 'socket.io';
import { createAdapter } from '@socket.io/redis-adapter';
import { createClient } from 'redis';

const io = new Server(httpServer);

const pubClient = createClient({ url: 'redis://localhost:6379' });
const subClient = pubClient.duplicate();

await Promise.all([pubClient.connect(), subClient.connect()]);
io.adapter(createAdapter(pubClient, subClient));

// 이후엔 같은 코드로 서버 간 브로드캐스트가 동작
io.to('room-123').emit('msg', payload);
//   → Redis publish → 모든 서버 인스턴스 → 각자 자기 클라이언트에게 전달

서버 코드는 단일 인스턴스 때와 동일. Bus 가 multi-server 라우팅을 투명하게 처리한다.

Pub/Sub vs Message Queue (차이점)

Pub/SubMessage Queue
메시지 보관즉시 전달 (fire-and-forget)큐에 보관, 컨슈머가 pull
다중 수신자모든 subscriber 가 받음보통 1 consumer 가 받음
메시지 유실subscriber 가 offline 이면 잃을 수 있음persistent 가능
예시Redis Pub/Sub, NATSRabbitMQ, SQS, Kafka (consumer group 모드)

Redis Pub/Sub 은 단순 Pub/Sub. Kafka 는 두 모드 모두 가능 (broadcast + consumer group).

부하 특성

구성 요소부하
Publisher 서버메시지마다 publish 호출 (~0.1 ms)
Bus모든 publish 가 통과, 가장 부담 큰 단일 지점
Subscriber 서버수신 + 자기 클라이언트에게 forward

IMPORTANT

Bus 가 SPOF (단일 장애점) 가 될 수 있다. 고가용성 클러스터 (Redis Sentinel/Cluster, Kafka multi-broker) 가 필수. 또 다른 함정: 모든 메시지가 bus 를 통과하므로 bus 처리량이 시스템 전체의 상한이 된다.

사용 시나리오

1. WebSocket 클러스터

위에서 설명한 Socket.IO + Redis Adapter 패턴.

2. 마이크로서비스 간 이벤트 전파

주문 서비스 → publish "order.created"
  → 결제 서비스 (subscribe)
  → 재고 서비스 (subscribe)
  → 알림 서비스 (subscribe)

각 서비스가 독립적으로 진화 가능.

3. 실시간 알림 시스템

이벤트 발생 → publish "notify:user-{id}"
  → 사용자가 접속한 서버가 subscribe → 전달

4. Cache invalidation

DB 업데이트 → publish "cache-invalidate:user-{id}"
  → 모든 서버가 자기 로컬 캐시 무효화

대안과 trade-off

gRPC streaming + 서비스 디스커버리

서비스 간 직접 stream. Bus 없음. 더 빠르지만 N×M 연결 관리.

Service mesh (Istio 등)

L7 라우팅으로 메시지 전달. 더 강력하지만 복잡.

Database notification

PostgreSQL LISTEN/NOTIFY 같은 DB 내장 Pub/Sub. 외부 인프라 불필요.

관련 위키

💬 댓글

사이트 검색 / 명령어

검색

스크롤 = 확대/축소 · 드래그 = 이동 · 0 = 원래 크기 · ESC = 닫기