멀티 IDC & Federation
서울 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 환경은 이야기가 완전히 달라집니다.
| 항목 | LAN | WAN |
|---|---|---|
| 지연(Latency) | < 1ms | 수십 ~ 수백ms |
| 패킷 유실률 | 거의 0% | 간헐적 발생 |
| 대역폭 | 1~10 Gbps | 수백 Mbps 이하 |
| 연결 안정성 | 매우 높음 | 변동 가능 |
WAN에서 클러스터링하면 생기는 문제
공부하다 보니 이 부분이 정말 중요했습니다. WAN에서 Erlang 클러스터를 구성하면 다음과 같은 문제가 연쇄적으로 발생합니다.
- ** 네트워크 파티션(Split-Brain)**: 노드 간 통신이 일시적으로 끊기면, 각 노드가 상대방이 죽었다고 판단합니다. 양쪽 모두 자신이 "살아 있는 클러스터"라고 생각하면서 데이터 불일치가 발생합니다.
- ** 성능 저하 **: 메타데이터 동기화에 수십ms가 걸리면, 큐 선언이나 바인딩 변경 같은 관리 작업이 느려집니다.
- ** 메시지 라우팅 지연 **: 클러스터 내에서 메시지가 다른 노드의 큐로 라우팅될 때, WAN 지연이 그대로 반영됩니다.
클러스터링은 "하나의 브로커"를 만드는 기술이고, Federation은 "독립적인 브로커 간 메시지를 연결"하는 기술입니다. WAN 환경에서는 하나의 브로커를 만들려고 하면 안 되고, 독립적인 브로커를 느슨하게 연결해야 합니다.
2. Federation Plugin
Federation은 RabbitMQ가 공식으로 제공하는 플러그인으로, ** 독립적인 브로커(또는 클러스터) 간에 메시지를 전달 **하는 역할을 합니다.
핵심 개념: Upstream과 Downstream
┌─────────────────┐ AMQP ┌─────────────────┐
│ Upstream │ ──────────────────▶ │ Downstream │
│ (서울 IDC) │ 메시지 전달 │ (부산 IDC) │
│ │ │ │
│ Exchange: orders│ │ Exchange: orders│
└─────────────────┘ └─────────────────┘
- Upstream: 메시지의 원본이 있는 브로커
- Downstream: 메시지를 받아오는 브로커
- 각 브로커는 ** 완전히 독립적 **으로 운영됩니다 (Erlang 쿠키 공유 없음)
Exchange Federation
Exchange Federation은 upstream의 Exchange로 들어온 메시지를 downstream의 동일한 이름의 Exchange로 전달합니다.
서울 IDC (Upstream) 부산 IDC (Downstream)
┌────────────────────┐ ┌────────────────────┐
│ │ │ │
│ [orders exchange] │ Federation │ [orders exchange] │
│ │ │ ─────────────▶ │ │ │
│ ▼ │ Link │ ▼ │
│ [orders.queue] │ │ [orders.queue] │
│ │ │ │ │ │
│ ▼ │ │ ▼ │
│ Consumer A │ │ Consumer B │
└────────────────────┘ └────────────────────┘
동작 방식을 정리하면:
- Downstream 브로커에서 Federation upstream을 설정합니다
- Federation 플러그인이 upstream에 ** 내부 큐 **를 자동 생성합니다
- Upstream Exchange에 도착한 메시지가 이 내부 큐에 복사됩니다
- Federation link가 AMQP 프로토콜로 메시지를 downstream으로 전달합니다
- Downstream의 동일 이름 Exchange에 메시지가 발행됩니다
Queue Federation
Queue Federation은 조금 다릅니다. downstream에서 소비자가 메시지를 요청할 때, upstream 큐에서 메시지를 가져오는 pull 방식 입니다.
서울 IDC (Upstream) 부산 IDC (Downstream)
┌────────────────────┐ ┌────────────────────┐
│ │ │ │
│ [task.queue] │ 소비 요청 시 │ [task.queue] │
│ ┌──┬──┬──┬──┐ │ ◀────────────── │ ┌──┐ │
│ │m4│m3│m2│m1│ │ 메시지 이동 │ │ │ │
│ └──┴──┴──┴──┘ │ ──────────────▶ │ └──┘ │
│ │ │ │ │
│ │ │ ▼ │
│ │ │ Consumer B │
└────────────────────┘ └────────────────────┘
Queue Federation은 로드 밸런싱 시나리오에 유용합니다. downstream에 소비자가 있을 때만 upstream에서 메시지를 가져오기 때문에, 한쪽 IDC의 소비자가 바쁠 때 다른 IDC에서 처리를 분담할 수 있습니다.
Federation 설정 예시
# 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로 옮겨라"입니다.
동작 방식
소스 브로커 대상 브로커
┌────────────────────┐ ┌────────────────────┐
│ │ │ │
│ [source.queue] │ Shovel │ [dest.exchange] │
│ ┌──┬──┬──┐ │ ──────────────▶ │ │ │
│ │m3│m2│m1│ │ 메시지를 꺼내서 │ ▼ │
│ └──┴──┴──┘ │ 다시 발행 │ [dest.queue] │
│ │ │ │
└────────────────────┘ └────────────────────┘
Shovel은 내부적으로 이런 동작을 합니다:
- 소스 큐에서 메시지를 ** 소비(consume)**합니다
- 대상 브로커의 Exchange에 메시지를 ** 재발행(publish)**합니다
- 대상에서 발행이 확인되면 소스에서 ACK 를 보냅니다
Shovel 설정 예시
# 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 비교
이 둘의 차이를 정리하면서 어떤 상황에 무엇을 써야 하는지 감이 잡혔습니다.
| 항목 | Federation | Shovel |
|---|---|---|
| 추상화 수준 | 높음 (Exchange/Queue 단위) | 낮음 (큐 → Exchange 단순 전달) |
| 설정 방식 | Policy 기반 (패턴 매칭) | 개별 Shovel 정의 |
| 토폴로지 | upstream/downstream 동일 구조 권장 | 소스/대상 구조 달라도 됨 |
| 자동 복구 | 연결 끊김 시 자동 재연결 | 자동 재연결 지원 |
| 메시지 루프 방지 | 내장 (max-hops 설정) | 수동 관리 필요 |
| 적합한 시나리오 | 멀티 IDC, DR | 마이그레이션, 프로토콜 변환 |
Federation은 "같은 이름의 Exchange끼리 논리적으로 연결하겠다"라는 의도가 명확한 반면, Shovel은 "이 메시지를 저기로 옮기겠다"라는 단순한 도구입니다. 멀티 IDC 구성에서는 대부분 Federation이 적합하고, Shovel은 특수한 상황에 보조적으로 사용합니다.
5. Split-Brain 방지 — Federation이 해결하는 이유
클러스터링 기초에서 네트워크 파티션 문제를 언급했었는데, WAN 환경에서는 이 문제가 훨씬 심각합니다.
클러스터링의 Split-Brain 문제
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로 전환합니다.
평상시:
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가 동시에 트래픽을 처리하는 구성입니다. 더 높은 가용성을 제공하지만, 복잡도도 올라갑니다.
서울 IDC 부산 IDC
┌────────────────┐ ┌────────────────┐
│ Producer A │ Federation │ Producer B │
│ ↓ │ ◀──────────────▶ │ ↓ │
│ orders exchange│ (양방향) │ orders exchange│
│ ↓ │ │ ↓ │
│ orders.queue │ │ orders.queue │
│ ↓ │ │ ↓ │
│ Consumer A │ │ Consumer B │
└────────────────┘ └────────────────┘
양방향 Federation 설정:
# 서울 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 설정으로 이를 방지합니다.
# 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 구성
실제 운영 환경을 가정한 구성도입니다.
전체 아키텍처
서울 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 재연결 |
페일오버 절차 (서울 전체 장애 시)
1. 모니터링 시스템이 서울 IDC 장애 감지
└→ Federation link 상태: DOWN
2. DNS 전환 (또는 GSLB 자동 전환)
└→ 도메인이 부산 IDC를 가리키도록 변경
3. 부산 App Servers 활성화
└→ 부산 RabbitMQ에 연결하여 메시지 처리 시작
4. 서울 복구 후
└→ Federation link 자동 재연결
└→ 서울 IDC의 밀린 메시지(expires 이내)가 부산으로 전달
└→ 트래픽을 점진적으로 서울로 복귀
8. 모니터링과 운영
Federation을 운영한다면, link 상태 모니터링이 필수적입니다.
Federation 상태 확인
# Federation link 상태 확인
rabbitmqctl eval 'rabbit_federation_status:status().'
# 또는 HTTP API로 확인
curl -u admin:password \
http://localhost:15672/api/federation-links
응답 예시:
[
{
"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_status | link 상태 | error 또는 starting이 지속 |
federation_queue_depth | 내부 큐 적체량 | 지속적으로 증가 |
federation_msg_rate | 메시지 전달 속도 | 급격한 감소 |
connection_state | AMQP 연결 상태 | blocked 또는 closed |
운영 팁
- **expires 설정을 충분히 **: WAN 장애 시 Federation 내부 큐의 메시지가 유실되지 않도록,
expires값을 넉넉하게 설정합니다 (예: 24시간 = 86400000ms) - **heartbeat 조정 **: WAN 환경에서는 기본 heartbeat(60초)가 너무 짧을 수 있습니다. 네트워크 상황에 맞게 조정하세요
- **TLS 적용 **: IDC 간 통신은 반드시
amqps://로 암호화합니다 - ** 대역폭 계산 **: 예상 메시지 처리량 x 평균 메시지 크기로 WAN 대역폭 요구량을 미리 계산합니다
# 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 이론에서 말하는 일관성과 가용성의 트레이드오프가 메시지 브로커에서도 그대로 적용된다는 걸 체감할 수 있었습니다.