서울 IDC에 있는 RabbitMQ와 부산 IDC에 있는 RabbitMQ를 하나의 클러스터로 묶으면 안 될까? 같은 소프트웨어인데 왜 WAN으로 연결하면 문제가 생기는 걸까?

멀티 IDC & Federation

이전 글에서 RabbitMQ 클러스터링의 기본 구조를 살펴봤습니다. 같은 데이터센터 안에서 여러 노드를 묶어 하나의 논리적 브로커를 만드는 방식이었는데요. 이번 글에서는 한 발 더 나아가서, 서로 다른 데이터센터(IDC)에 있는 RabbitMQ를 어떻게 연결하는지 정리해보겠습니다.

시리즈의 마지막 글인 만큼, 실무에서 자주 나오는 DR(Disaster Recovery) 구성까지 다루겠습니다.


1. LAN vs WAN — 왜 클러스터링이 WAN에서 안 되는가

Erlang 분산 프로토콜의 한계

RabbitMQ 클러스터링은 내부적으로 Erlang 분산 프로토콜 을 사용합니다. 이 프로토콜은 노드 간에 메타데이터(큐 정보, Exchange 바인딩, 사용자 정보 등)를 실시간으로 동기화하는데, 여기에는 중요한 전제가 있습니다.

  • 낮은 네트워크 지연 (1ms 이하)
  • ** 안정적인 연결** (패킷 유실 거의 없음)
  • ** 높은 대역폭**

LAN 환경에서는 이 조건이 자연스럽게 충족됩니다. 하지만 WAN 환경은 이야기가 완전히 달라집니다.

항목LANWAN
지연(Latency)< 1ms수십 ~ 수백ms
패킷 유실률거의 0%간헐적 발생
대역폭1~10 Gbps수백 Mbps 이하
연결 안정성매우 높음변동 가능

WAN에서 클러스터링하면 생기는 문제

공부하다 보니 이 부분이 정말 중요했습니다. WAN에서 Erlang 클러스터를 구성하면 다음과 같은 문제가 연쇄적으로 발생합니다.

  1. ** 네트워크 파티션(Split-Brain)**: 노드 간 통신이 일시적으로 끊기면, 각 노드가 상대방이 죽었다고 판단합니다. 양쪽 모두 자신이 "살아 있는 클러스터"라고 생각하면서 데이터 불일치가 발생합니다.
  2. ** 성능 저하 **: 메타데이터 동기화에 수십ms가 걸리면, 큐 선언이나 바인딩 변경 같은 관리 작업이 느려집니다.
  3. ** 메시지 라우팅 지연 **: 클러스터 내에서 메시지가 다른 노드의 큐로 라우팅될 때, WAN 지연이 그대로 반영됩니다.

클러스터링은 "하나의 브로커"를 만드는 기술이고, Federation은 "독립적인 브로커 간 메시지를 연결"하는 기술입니다. WAN 환경에서는 하나의 브로커를 만들려고 하면 안 되고, 독립적인 브로커를 느슨하게 연결해야 합니다.


2. Federation Plugin

Federation은 RabbitMQ가 공식으로 제공하는 플러그인으로, ** 독립적인 브로커(또는 클러스터) 간에 메시지를 전달 **하는 역할을 합니다.

핵심 개념: Upstream과 Downstream

PLAINTEXT
┌─────────────────┐         AMQP          ┌─────────────────┐
│   Upstream       │ ──────────────────▶  │   Downstream     │
│   (서울 IDC)     │    메시지 전달        │   (부산 IDC)     │
│                  │                      │                  │
│  Exchange: orders│                      │  Exchange: orders│
└─────────────────┘                       └─────────────────┘
  • Upstream: 메시지의 원본이 있는 브로커
  • Downstream: 메시지를 받아오는 브로커
  • 각 브로커는 ** 완전히 독립적 **으로 운영됩니다 (Erlang 쿠키 공유 없음)

Exchange Federation

Exchange Federation은 upstream의 Exchange로 들어온 메시지를 downstream의 동일한 이름의 Exchange로 전달합니다.

PLAINTEXT
서울 IDC (Upstream)                     부산 IDC (Downstream)
┌────────────────────┐                 ┌────────────────────┐
│                    │                 │                    │
│  [orders exchange] │    Federation   │  [orders exchange] │
│       │            │ ─────────────▶  │       │            │
│       ▼            │    Link         │       ▼            │
│  [orders.queue]    │                 │  [orders.queue]    │
│       │            │                 │       │            │
│       ▼            │                 │       ▼            │
│  Consumer A        │                 │  Consumer B        │
└────────────────────┘                 └────────────────────┘

동작 방식을 정리하면:

  1. Downstream 브로커에서 Federation upstream을 설정합니다
  2. Federation 플러그인이 upstream에 ** 내부 큐 **를 자동 생성합니다
  3. Upstream Exchange에 도착한 메시지가 이 내부 큐에 복사됩니다
  4. Federation link가 AMQP 프로토콜로 메시지를 downstream으로 전달합니다
  5. Downstream의 동일 이름 Exchange에 메시지가 발행됩니다

Queue Federation

Queue Federation은 조금 다릅니다. downstream에서 소비자가 메시지를 요청할 때, upstream 큐에서 메시지를 가져오는 pull 방식 입니다.

PLAINTEXT
서울 IDC (Upstream)                     부산 IDC (Downstream)
┌────────────────────┐                 ┌────────────────────┐
│                    │                 │                    │
│  [task.queue]      │    소비 요청 시   │  [task.queue]      │
│  ┌──┬──┬──┬──┐     │ ◀────────────── │  ┌──┐              │
│  │m4│m3│m2│m1│     │    메시지 이동    │  │  │              │
│  └──┴──┴──┴──┘     │ ──────────────▶ │  └──┘              │
│                    │                 │       │            │
│                    │                 │       ▼            │
│                    │                 │  Consumer B        │
└────────────────────┘                 └────────────────────┘

Queue Federation은 로드 밸런싱 시나리오에 유용합니다. downstream에 소비자가 있을 때만 upstream에서 메시지를 가져오기 때문에, 한쪽 IDC의 소비자가 바쁠 때 다른 IDC에서 처리를 분담할 수 있습니다.

Federation 설정 예시

BASH
# 1. Federation 플러그인 활성화 (downstream 브로커에서)
rabbitmq-plugins enable rabbitmq_federation
rabbitmq-plugins enable rabbitmq_federation_management  # 관리 UI 지원

# 2. Upstream 정의
rabbitmqctl set_parameter federation-upstream seoul-idc \
  '{"uri":"amqp://user:pass@seoul-rabbitmq.internal:5672","expires":3600000}'

# 3. Policy로 Federation 적용
rabbitmqctl set_policy federate-orders \
  "^orders\." \
  '{"federation-upstream-set":"all"}' \
  --priority 1 \
  --apply-to exchanges

설정 포인트를 정리하면:

  • uri: upstream 브로커의 AMQP 접속 정보
  • expires: Federation 내부 큐의 만료 시간 (연결 끊김 시 메시지 보존 기간)
  • Policy의 패턴(^orders\.)으로 어떤 Exchange에 Federation을 적용할지 결정합니다
  • --apply-to로 Exchange Federation인지 Queue Federation인지 지정합니다

3. Shovel Plugin

Shovel은 Federation보다 ** 더 단순한 메시지 전달 도구 **입니다. 한마디로, "이 큐의 메시지를 저 브로커의 Exchange/Queue로 옮겨라"입니다.

동작 방식

PLAINTEXT
소스 브로커                              대상 브로커
┌────────────────────┐                 ┌────────────────────┐
│                    │                 │                    │
│  [source.queue]    │    Shovel       │  [dest.exchange]   │
│  ┌──┬──┬──┐        │ ──────────────▶ │       │            │
│  │m3│m2│m1│        │  메시지를 꺼내서  │       ▼            │
│  └──┴──┴──┘        │  다시 발행       │  [dest.queue]      │
│                    │                 │                    │
└────────────────────┘                 └────────────────────┘

Shovel은 내부적으로 이런 동작을 합니다:

  1. 소스 큐에서 메시지를 ** 소비(consume)**합니다
  2. 대상 브로커의 Exchange에 메시지를 ** 재발행(publish)**합니다
  3. 대상에서 발행이 확인되면 소스에서 ACK 를 보냅니다

Shovel 설정 예시

BASH
# Shovel 플러그인 활성화
rabbitmq-plugins enable rabbitmq_shovel
rabbitmq-plugins enable rabbitmq_shovel_management

# Dynamic Shovel 설정
rabbitmqctl set_parameter shovel migrate-orders \
  '{"src-protocol":"amqp091",
    "src-uri":"amqp://localhost",
    "src-queue":"orders.legacy",
    "dest-protocol":"amqp091",
    "dest-uri":"amqp://user:pass@busan-rabbitmq.internal:5672",
    "dest-exchange":"orders",
    "dest-exchange-key":"orders.new"}'

Shovel 사용이 적합한 경우

  • **일회성 마이그레이션 **: 기존 큐의 메시지를 새 브로커로 이동
  • ** 프로토콜 변환 **: AMQP 0.9.1 ↔ AMQP 1.0 간 메시지 전달
  • ** 단순한 큐 간 연결 **: 특정 큐의 메시지를 다른 큐로 그대로 전달
  • **Exchange 토폴로지가 다른 환경 **: 소스와 대상의 Exchange 구조가 완전히 다를 때

4. Federation vs Shovel 비교

이 둘의 차이를 정리하면서 어떤 상황에 무엇을 써야 하는지 감이 잡혔습니다.

항목FederationShovel
추상화 수준높음 (Exchange/Queue 단위)낮음 (큐 → Exchange 단순 전달)
설정 방식Policy 기반 (패턴 매칭)개별 Shovel 정의
토폴로지upstream/downstream 동일 구조 권장소스/대상 구조 달라도 됨
자동 복구연결 끊김 시 자동 재연결자동 재연결 지원
메시지 루프 방지내장 (max-hops 설정)수동 관리 필요
적합한 시나리오멀티 IDC, DR마이그레이션, 프로토콜 변환

Federation은 "같은 이름의 Exchange끼리 논리적으로 연결하겠다"라는 의도가 명확한 반면, Shovel은 "이 메시지를 저기로 옮기겠다"라는 단순한 도구입니다. 멀티 IDC 구성에서는 대부분 Federation이 적합하고, Shovel은 특수한 상황에 보조적으로 사용합니다.


5. Split-Brain 방지 — Federation이 해결하는 이유

클러스터링 기초에서 네트워크 파티션 문제를 언급했었는데, WAN 환경에서는 이 문제가 훨씬 심각합니다.

클러스터링의 Split-Brain 문제

PLAINTEXT
      WAN 연결 끊김!

서울 IDC         부산 IDC
┌─────────┐     ┌─────────┐
│ Node A  │     │ Node C  │
│ Node B  │     │ Node D  │
│         │     │         │
│ "우리가  │     │ "우리가  │
│ 진짜    │     │  진짜   │
│ 클러스터"│     │ 클러스터"│
└─────────┘     └─────────┘
→ 양쪽 다 자기가 정상이라고 판단
→ 데이터 불일치 발생!

Federation은 왜 이 문제가 없는가

Federation에서는 각 브로커가 처음부터 ** 독립적인 개체 **입니다. 연결이 끊기면 단순히 메시지 전달이 멈출 뿐, 어느 쪽이 "진짜 클러스터"인지 판단할 필요가 없습니다.

  • WAN 끊김 → Federation link 일시 중단
  • 각 IDC는 ** 로컬 메시지를 독립적으로 계속 처리**
  • WAN 복구 → Federation link 자동 재연결, 밀린 메시지 전달 재개

이 차이가 WAN 환경에서 Federation을 선택해야 하는 근본적인 이유입니다.


6. DR(Disaster Recovery) 구성 전략

Active-Passive 패턴

가장 단순한 DR 구성입니다. 평소에는 한쪽 IDC만 트래픽을 처리하고, 장애 시 다른 IDC로 전환합니다.

PLAINTEXT
평상시:
                    Federation (단방향)
서울 IDC (Active)  ──────────────────▶  부산 IDC (Passive)
┌────────────────┐                     ┌────────────────┐
│ Producer       │                     │                │
│     ↓          │                     │ orders exchange│
│ orders exchange│                     │     ↓          │
│     ↓          │                     │ orders.queue   │
│ orders.queue   │                     │ (메시지 대기중) │
│     ↓          │                     │                │
│ Consumer       │                     │ Consumer (대기) │
└────────────────┘                     └────────────────┘

장애 발생 시:
서울 IDC (장애)                         부산 IDC (Active 전환)
┌────────────────┐                     ┌────────────────┐
│     ✕ DOWN     │                     │ Producer (전환) │
│                │                     │     ↓          │
│                │                     │ orders exchange│
│                │                     │     ↓          │
│                │                     │ orders.queue   │
│                │                     │     ↓          │
│                │                     │ Consumer (활성) │
└────────────────┘                     └────────────────┘

** 장점:**

  • 구성이 단순합니다
  • 메시지 중복 처리 걱정이 없습니다
  • Federation은 단방향이라 설정이 직관적입니다

** 단점:**

  • 부산 IDC 자원이 평소에 유휴 상태입니다
  • 페일오버 시 Federation link가 끊긴 동안의 메시지 유실 가능성이 있습니다
  • 수동 페일오버가 필요할 수 있습니다

Active-Active 패턴

양쪽 IDC가 동시에 트래픽을 처리하는 구성입니다. 더 높은 가용성을 제공하지만, 복잡도도 올라갑니다.

PLAINTEXT
서울 IDC                                부산 IDC
┌────────────────┐                     ┌────────────────┐
│ Producer A     │   Federation        │ Producer B     │
│     ↓          │ ◀──────────────▶    │     ↓          │
│ orders exchange│   (양방향)          │ orders exchange│
│     ↓          │                     │     ↓          │
│ orders.queue   │                     │ orders.queue   │
│     ↓          │                     │     ↓          │
│ Consumer A     │                     │ Consumer B     │
└────────────────┘                     └────────────────┘

양방향 Federation 설정:

BASH
# 서울 IDC에서 (부산을 upstream으로 설정)
rabbitmqctl set_parameter federation-upstream busan-idc \
  '{"uri":"amqp://user:pass@busan-rabbitmq:5672","expires":3600000}'

# 부산 IDC에서 (서울을 upstream으로 설정)
rabbitmqctl set_parameter federation-upstream seoul-idc \
  '{"uri":"amqp://user:pass@seoul-rabbitmq:5672","expires":3600000}'

# 양쪽 모두 동일한 Policy 적용
rabbitmqctl set_policy federate-orders \
  "^orders\." \
  '{"federation-upstream-set":"all"}' \
  --priority 1 \
  --apply-to exchanges

Active-Active 구성에서는 양쪽 IDC에 동일한 메시지가 존재할 수 있습니다. 소비자 측에서 반드시 멱등성(idempotency)을 보장해야 합니다. 예를 들어 주문 처리라면, 주문 ID 기반으로 중복 처리를 방지하는 로직이 필수적입니다.

** 주의할 점 — 메시지 루프 방지:**

양방향 Federation에서는 메시지가 서울 → 부산 → 서울로 무한 순환할 수 있습니다. Federation 플러그인은 max-hops 설정으로 이를 방지합니다.

BASH
# max-hops를 1로 설정하면 한 번만 전달
rabbitmqctl set_parameter federation-upstream busan-idc \
  '{"uri":"amqp://user:pass@busan-rabbitmq:5672",
    "expires":3600000,
    "max-hops":1}'

max-hops: 1이면 메시지가 한 번 Federation으로 전달된 후에는 다시 전달되지 않습니다.


7. 실무 예시: 서울 ↔ 부산 DR 구성

실제 운영 환경을 가정한 구성도입니다.

전체 아키텍처

PLAINTEXT
서울 IDC (Primary)                        부산 IDC (Secondary)
┌──────────────────────────┐             ┌──────────────────────────┐
│  ┌─────────────────────┐ │             │  ┌─────────────────────┐ │
│  │   RabbitMQ Cluster  │ │  Federation │  │   RabbitMQ Cluster  │ │
│  │  ┌─────┐ ┌─────┐   │ │ ◀────────▶  │  │  ┌─────┐ ┌─────┐   │ │
│  │  │Node1│ │Node2│   │ │  (양방향)   │  │  │Node1│ │Node2│   │ │
│  │  └─────┘ └─────┘   │ │   AMQP     │  │  └─────┘ └─────┘   │ │
│  │       ┌─────┐       │ │             │  │       ┌─────┐       │ │
│  │       │Node3│       │ │             │  │       │Node3│       │ │
│  │       └─────┘       │ │             │  │       └─────┘       │ │
│  └─────────────────────┘ │             │  └─────────────────────┘ │
│                          │             │                          │
│  App Servers (Active)    │             │  App Servers (Standby)   │
│  ┌───────┐ ┌───────┐    │             │  ┌───────┐ ┌───────┐    │
│  │ App 1 │ │ App 2 │    │             │  │ App 3 │ │ App 4 │    │
│  └───────┘ └───────┘    │             │  └───────┘ └───────┘    │
└──────────────────────────┘             └──────────────────────────┘
         ▲                                          ▲
         │              DNS / L4 LB                 │
         └──────────────────────────────────────────┘

포인트는 ** 각 IDC 내부에서는 클러스터링 **, IDC 간에는 Federation 을 사용한다는 점입니다. LAN에서는 클러스터링의 장점(메타데이터 공유, 큐 미러링)을 누리고, WAN에서는 Federation의 장점(독립성, 네트워크 장애 내성)을 활용합니다.

장애 시나리오별 대응

시나리오영향대응
서울 노드 1대 장애클러스터 내 자동 복구Quorum Queue 자동 리더 선출
서울-부산 WAN 일시 끊김Federation 일시 중단각 IDC 독립 운영, WAN 복구 시 자동 재연결
서울 IDC 전체 장애서울 메시지 처리 불가DNS 전환 → 부산 IDC로 트래픽 이동
부산 IDC 전체 장애DR 사이트 손실서울 단독 운영, 부산 복구 후 Federation 재연결

페일오버 절차 (서울 전체 장애 시)

PLAINTEXT
1. 모니터링 시스템이 서울 IDC 장애 감지
   └→ Federation link 상태: DOWN

2. DNS 전환 (또는 GSLB 자동 전환)
   └→ 도메인이 부산 IDC를 가리키도록 변경

3. 부산 App Servers 활성화
   └→ 부산 RabbitMQ에 연결하여 메시지 처리 시작

4. 서울 복구 후
   └→ Federation link 자동 재연결
   └→ 서울 IDC의 밀린 메시지(expires 이내)가 부산으로 전달
   └→ 트래픽을 점진적으로 서울로 복귀

8. 모니터링과 운영

Federation을 운영한다면, link 상태 모니터링이 필수적입니다.

Federation 상태 확인

BASH
# Federation link 상태 확인
rabbitmqctl eval 'rabbit_federation_status:status().'

# 또는 HTTP API로 확인
curl -u admin:password \
  http://localhost:15672/api/federation-links

응답 예시:

JSON
[
  {
    "node": "rabbit@busan-node1",
    "queue": "federation: orders → seoul-idc",
    "upstream": "seoul-idc",
    "type": "exchange",
    "vhost": "/",
    "status": "running",      // running | starting | error
    "local_connection": {
      "state": "running"
    },
    "timestamp": "2026-03-27T10:30:00.000Z"
  }
]

핵심 모니터링 지표

지표설명경고 기준
federation_link_statuslink 상태error 또는 starting이 지속
federation_queue_depth내부 큐 적체량지속적으로 증가
federation_msg_rate메시지 전달 속도급격한 감소
connection_stateAMQP 연결 상태blocked 또는 closed

운영 팁

  • **expires 설정을 충분히 **: WAN 장애 시 Federation 내부 큐의 메시지가 유실되지 않도록, expires 값을 넉넉하게 설정합니다 (예: 24시간 = 86400000ms)
  • **heartbeat 조정 **: WAN 환경에서는 기본 heartbeat(60초)가 너무 짧을 수 있습니다. 네트워크 상황에 맞게 조정하세요
  • **TLS 적용 **: IDC 간 통신은 반드시 amqps://로 암호화합니다
  • ** 대역폭 계산 **: 예상 메시지 처리량 x 평균 메시지 크기로 WAN 대역폭 요구량을 미리 계산합니다
BASH
# TLS를 적용한 upstream 설정
rabbitmqctl set_parameter federation-upstream seoul-idc \
  '{"uri":"amqps://user:pass@seoul-rabbitmq.internal:5671",
    "expires":86400000,
    "message-ttl":86400000,
    "trust-user-id":false,
    "ack-mode":"on-confirm"}'

ack-mode: on-confirm은 대상 브로커가 메시지 수신을 확인한 후에만 소스에서 ACK를 보내는 설정입니다. 메시지 유실을 방지하는 가장 안전한 옵션입니다.


정리

멀티 IDC 환경에서 RabbitMQ를 운영하는 핵심을 정리합니다.

구분핵심
LAN 환경클러스터링으로 하나의 논리적 브로커 구성
WAN 환경Federation으로 독립 브로커 간 메시지 연결
단순 전달Shovel로 큐 간 메시지 이동
DR 구성Active-Passive(단순) 또는 Active-Active(고가용성+멱등성 필수)
모니터링Federation link 상태와 내부 큐 적체량 필수 감시

시리즈를 마치며

이 글로 RabbitMQ 시리즈 12편을 모두 마무리합니다. 메시징 큐의 기본 개념에서 시작해서, Exchange 타입, ACK/NACK, DLX, 발행 확인, Spring Boot 연동, 클러스터링, 그리고 멀티 IDC까지 살펴봤습니다.

공부하면서 느낀 건, RabbitMQ는 단순히 "메시지를 보내고 받는 도구"가 아니라 ** 분산 시스템에서 서비스 간 통신을 어떻게 신뢰성 있게 설계할 것인가 **에 대한 답을 담고 있다는 점이었습니다. 클러스터링과 Federation의 차이를 이해하면, 결국 CAP 이론에서 말하는 일관성과 가용성의 트레이드오프가 메시지 브로커에서도 그대로 적용된다는 걸 체감할 수 있었습니다.

댓글 로딩 중...