신경계: Redis와 RabbitMQ를 활용한 분산 신호 설계

발행: (2026년 2월 2일 오전 07:00 GMT+9)
15 분 소요
원문: Dev.to

Source: Dev.to

해당 텍스트를 번역하려면 실제 기사 내용(본문, 코드 블록을 제외한 텍스트)을 제공해 주셔야 합니다.
본문을 복사해서 보내 주시면, 원본 포맷과 마크다운을 그대로 유지하면서 한국어로 번역해 드리겠습니다.

분할‑브레인 신호 위기

실시간 애플리케이션이 성공적으로 운영되는 생애 주기에서, 아키텍처가 깨지는 특정한 날이 있습니다 — 보통 두 번째 신호 서버를 배포할 때입니다.

Day 1 – Single process

Python 프로세스 하나(또는 서버 하나)만 있으면 WebRTC 시그널링은 매우 간단합니다.
user_idwebsocket_connection 를 매핑하는 간단한 메모리 내 사전을 유지합니다.

User AUser B에게 전화를 걸고 싶을 때, 코드는 사전에서 User B를 찾아 SDP 오퍼를 소켓으로 전송합니다. 이는 빠르고, 원자적이며, 간단합니다.

Day 100 – 확장

  • User ANode 1에 연결합니다.
  • User BNode 3에 연결합니다.

User A가 User B에게 오퍼를 보낼 때, Node 1은 로컬 메모리를 확인하고 User B에 대한 연결이 없음을 발견하여 메시지를 버리거나—더 나쁘게는—User B가 다른 서버에서 활발히 대기하고 있는 동안 “User Offline” 오류를 반환합니다. 사용자들은 각각의 프로세스 사일로에 격리되어 있어 미디어 협상이 불가능합니다.

이것은 WebRTC에서 근본적인 분산 상태 문제입니다.
표준 HTTP REST API(무상태, 공유 DB 기반)와 달리 시그널링은 상태가 있으며 일시적입니다. 모든 SDP 패킷을 Postgres에 기록하면 호출 설정 지연이 파괴됩니다. 여러분은 신경계가 필요합니다 — 격리된 프로세스를 연결하는 고속 분산 메시지 버스가 필요합니다.

두 가지 패러다임: 속도 vs. 메모리

이 레이어를 설계할 때 엔지니어들은 보통 두 가지 주요 기술 중 하나를 선택합니다:

패러다임기술철학
EphemeralRedis Pub/Sub“지금 듣고 있지 않다면 알 필요가 없습니다.”
DurableRabbitMQ“처리했음을 확인할 때까지 이 메시지를 보관합니다.”

실제 WebRTC 시스템에서는 두 가지가 필요하며, 트래픽 종류에 따라 적용됩니다.

Redis Pub/Sub – 속도 레이어

Redis는 지연 시간이라는 한 가지 지표 때문에 WebRTC 시그널링의 업계 표준입니다.

  • Pub/Sub 모델 – 발행자가 채널에 메시지를 보내면 Redis가 즉시 모든 활성 구독자에게 전달합니다.
  • 스토리지, 큐잉, 되돌아보기 없음.

내부 구조 및 성능

PUBLISH는 채널의 구독자 연결 리스트를 순회하면서 데이터를 각 구독자의 출력 버퍼에 씁니다. 이 덕분에 단일 Redis 인스턴스가 초당 수백만 건의 메시지를 밀리초 이하 지연으로 처리할 수 있습니다.

WebRTC에서는 ICE 후보 교환 시 이 속도가 매우 중요합니다. 일반적인 클라이언트는 한 번에 10‑20개의 후보를 생성하는데, 이 후보들은 Client A → Server → Client B 순으로 즉시 전달되어야 합니다. 각 후보에 50 ms의 큐잉 지연이 추가되면 첫 미디어 도달 시간 (TTFM) 이 지연되어 사용자는 검은 화면을 보게 됩니다.

“최대 한 번” 전달의 트레이드‑오프

이 속도의 대가로 최대 한 번 전달 보장이 제공됩니다. 시그널링 노드가 충돌하거나 재시작하면 Redis와의 연결이 끊기고, 그 다운타임 동안 구독자에게 보낸 모든 메시지는 영원히 사라집니다.

  • ICE 후보의 경우 이는 보통 허용됩니다 – WebRTC는 견고하므로 놓친 후보는 ICE 에이전트가 다음 페어를 시도하게 합니다.
  • 중요한 상태 전환(예: “통화 종료”)에서 메시지를 잃으면 데이터베이스에 방이 영원히 “활성” 상태로 남아 자원을 누수시킬 수 있습니다.

사기 위한 밈

RabbitMQ – 신뢰성 레이어

RabbitMQ는 AMQP (Advanced Message Queuing Protocol) 를 구현하며 브로커 역할을 합니다. 단순 라우터가 아닙니다.

내부 구조 및 신뢰성

  • 메시지는 exchange를 통해 queue로 흐릅니다.
  • ACK(확인 응답)영속성 덕분에 소비자가 확인할 때까지 메시지가 큐에서 제거되지 않습니다.
  • 소비자가 충돌하면 TCP 연결이 끊어지고, RabbitMQ는 메시지를 다시 큐에 넣어 다른 소비자가 처리하도록 합니다.

최소 한 번 전달 보장은 제어 플레인 이벤트에 있어 절대 양보할 수 없습니다.

예시: Room CreatedStart Cloud Recording.
이 과정을 Redis로 보내고 녹화 서비스가 일시 중단되면 녹화가 시작되지 않아 통화는 진행되지만 컴플라이언스 파일이 누락돼 HIPAA 위반 위험이 발생합니다.
RabbitMQ를 사용하면 Start Recording 작업이 영구 큐에 남아 있다가 녹화 서비스가 복구되면 처리됩니다.

지연 시간 비용

신뢰성을 확보하려면 비용이 듭니다. RabbitMQ는 영속 메시지를 디스크(또는 영구 저장소)에 기록하고 ACK를 위한 추가 핸드셰이크를 수행하므로 Redis Pub/Sub에 비해 지연 시간이 늘어납니다. 실제로는 수십 밀리초 정도의 추가 지연이 발생하는데, 이는 제어 플레인 트래픽에는 허용되지만 고빈도 ICE 후보 스트림에는 적합하지 않습니다.

모두 합쳐서

트래픽 유형권장 버스보장일반 지연시간
ICE 후보, SDP 오퍼/답변 (고주파, 지연 민감)Redis Pub/Sub최대 한 번 (손실 허용)< 1 ms
콜 제어 이벤트 (룸 생성, 녹음 시작/중지, 콜 종료)RabbitMQ최소 한 번 (내구성)10‑30 ms
하이브리드 시나리오 (예: Redis 사용 불가 시 대체)Both (Redis 기본, RabbitMQ 대체)대체 로직에 따라 다름

signaling plane을 빠르고 휘발성인 레이어(Redis)와 신뢰성 있고 내구성 있는 레이어(RabbitMQ)로 분리함으로써, split‑brain 격리를 방지하고 콜 설정 지연 시간을 낮게 유지하면서 중요한 상태 변화가 절대 손실되지 않도록 보장합니다.

행복한 시그널링!

# The Hybrid Architecture: A Dual‑Bus Approach

The most robust production systems utilize a **Hybrid Architecture**.  
We classify traffic into two lanes:

* **Hot Path** (Ephemeral) – low‑latency, fire‑and‑forget signals.  
* **Cold Path** (Durable) – transactional events that must be persisted.

---

이중 버스 아키텍처의 중앙 다이어그램


Lane 1: 핫 경로 (Redis)

Traffic: SDP offer/answer, ICE 후보, 커서 움직임, 입력 표시.
Goal: 최소 지연 시간.

Implementation

  1. 각 사용자는 신호 노드에 연결합니다.
  2. 노드는 고유한 Redis 채널 user:{uuid} 를 구독합니다.
  3. 다른 노드가 해당 사용자에게 데이터를 보내야 할 때, 그 채널에 퍼블리시합니다.

Library: redis.asyncio (이전 aioredis).
Pattern: Fire‑and‑forget. 메시지가 손실되면 UI가 재시도하거나 단순히 무시합니다 (예: 100 ms 뒤에 사라진 커서 업데이트는 무관합니다).

Lane 2: 콜드 경로 (RabbitMQ)

Traffic: 방 라이프사이클 이벤트(생성/소멸), 웹훅 트리거, 청구 메터링, 녹화 작업.
Goal: 트랜잭션 무결성.

Implementation

회의가 종료될 때, 신호 노드는 RabbitMQ의 topic 교환에 room.ended 이벤트를 퍼블리시합니다. 이 이벤트는 여러 큐로 라우팅됩니다:

QueuePurpose
billing_queue지속 시간을 계산하고 고객에게 요금을 청구
cleanup_queue미디어‑서버(SFU) 리소스를 종료
analytics_queue품질 통계를 집계

Library: aio_pika.
Pattern: Publisher confirms + consumer acks – RabbitMQ를 이용해 모든 청구 이벤트가 정확히 한 번(또는 최소 한 번, 멱등성 검증 포함) 처리되도록 보장합니다.

Python에서 비동기 아키텍처 구현

asyncio 기반 프레임워크(Quart, FastAPI 등)를 사용할 때는 연결 풀을 신중히 관리해야 합니다. WebSocket당 새로운 Redis 연결을 열면 파일 디스크립터가 즉시 소진됩니다.

다중화된 Redis 리스너

프로세스당 하나의 전역 Redis 연결을 발행용으로, 또 하나를 구독용으로 유지합니다. subscribe()는 블로킹 연산이므로, 전용 백그라운드 작업에서 실행하여 메시지를 적절한 WebSocket 인스턴스로 전달하도록 합니다.

# Conceptual architecture for multiplexed Redis → WebSocket
active_websockets = {}          # Map user_id → websocket

async def redis_reader(channel):
    async for message in channel.listen():
        target_user = extract_target(message)
        if ws := active_websockets.get(target_user):
            await ws.send_json(message["data"])

# On startup
asyncio.create_task(redis_reader(global_pubsub_channel))

비동기 AMQP 컨슈머

aio_pika는 견고한 채널 상태 관리를 제공합니다. 중요한 운영 패턴은 백프레셔입니다: 시그널링 서버가 들어오는 WebSocket 프레임에 압도당하면 RabbitMQ에서 더 많은 메시지를 가져오지 않아야 합니다. prefetch_count를 설정하여 서버가 처리할 수 있는 만큼만 소비하도록 하고, 초과 메시지는 다른 노드에 남겨 자동 로드 밸런싱이 이루어지게 합니다.

의사결정 매트릭스: 언제 무엇을 사용할까

FeatureRedis Pub/SubRabbitMQ
Primary MetricLatency (< 1 ms) → 지연시간 (< 1 ms)Reliability (durability) → 신뢰성 (내구성)
Delivery GuaranteeAt‑most‑once (lossy) → 최대 한 번 (손실 가능)At‑least‑once (persistent) → 최소 한 번 (지속성)
ThroughputHigh (millions /sec) → 높음 (수백만 /초)Moderate (thousands /sec) → 보통 (수천 /초)
ComplexityLow (simple commands) → 낮음 (간단한 명령)High (exchanges, bindings) → 높음 (교환, 바인딩)
Ideal PayloadICE candidates, mouse positions → ICE 후보, 마우스 위치Billing events, start/stop recording → 청구 이벤트, 녹화 시작/중지
Python Libraryredis.asyncioaio_pika

결론: 규모의 신경계

  • 단일 시그널링 서버는 프로토타입입니다.
  • 분산 클러스터는 제품입니다.

메시지 버스를 도입하면 소켓 연결을 애플리케이션 로직과 분리할 수 있습니다. 시그널링 노드는 클라이언트와 “신경계” 사이에 데이터를 단순히 전달하는 무상태 “덤 파이프”가 됩니다.

Redis와 RabbitMQ 중 선택하는 것이 이분법은 아닙니다. 가장 탄력적인 WebRTC 아키텍처는 시그널(물처럼 흐르는)과 이벤트(돌처럼 기록되어야 하는)를 구분합니다. 이러한 기술들을 혼합함으로써 사용자에게는 즉각적인 느낌을 주면서도 비즈니스에 대해서는 감시 증거가 가능한 플랫폼을 구축할 수 있습니다.

저자를 팔로우하기

YouTube channel thumbnail

채널: The Lalit Official

Back to Blog

관련 글

더 보기 »