← all posts
DEV 2026.05.02 · 15 min read Intermediate

RabbitMQ 운영의 모든 실패는 어디서 오는가

처리량 병목부터 Flow Control, 모니터링 맹점, 운영 장애 패턴, 클러스터 복구까지 — RabbitMQ 실무 운영의 다섯 가지 핵심 축을 추적한다.


“RabbitMQ가 느리다”는 말은 대부분 “설정이 잘못됐다”는 뜻이다. 그런데 잘못된 설정은 한 곳에서 오지 않는다. Publisher 측, Consumer 측, 브로커 메모리, 모니터링 부재, 클러스터 구조 — 다섯 곳 중 어느 하나라도 어긋나면 전체가 흔들린다. 왜 RabbitMQ 장애는 항상 “갑자기” 발생하는 것처럼 느껴지는가?

처리량은 Publisher와 Consumer 양쪽의 문제다

Publisher 쪽 병목은 대부분 Confirm 처리 방식에서 온다. 매 메시지마다 waitForConfirmsOrDie()로 동기 대기하면 1,000개 메시지에 10ms RTT 기준으로 약 10초가 걸린다. 비동기 ConfirmCallback으로 바꾸면 같은 1,000개를 100ms 수준에서 처리할 수 있다 — 100배 차이다.

Consumer 쪽 병목은 concurrency와 prefetch 설정에서 온다. 처리 시간이 50ms인 Consumer를 단일 스레드(concurrency=1)로 운영하면 초당 최대 20개 처리가 한계다. 목표 처리량이 초당 1,000개라면 필요한 스레드 수는 1000 / (1/0.05) = 50개다. Prefetch는 처리시간 / RTT 비율로 설정하는 것이 기준점이다.

압축은 조건부다. 메시지가 1KB를 넘고 네트워크가 병목일 때만 이득이다. 1KB 미만 메시지에 GZIP을 걸면 압축 결과가 원본보다 커지고 CPU만 낭비한다.

복합 튜닝 순서는 Prefetch 증가 → Concurrency 증가 → 비동기 Confirm → 배치 소비다. 앞 단계의 효과를 확인하고 다음 단계로 넘어가야 한다.

Flow Control은 버그가 아니라 설계다

Publisher가 갑자기 차단된다. 연결은 살아있다. 팀은 RabbitMQ를 재시작한다. 재시작 후 5분이 지나면 또 차단된다. 이것이 Flow Control을 모를 때 생기는 패턴이다.

RabbitMQ는 메모리 사용량이 vm_memory_high_watermark(기본 40%)를 초과하면 모든 Publisher Connection에 Connection.Blocked 프레임을 보내 발행을 차단한다. Consumer는 계속 동작한다 — Consumer가 메시지를 소비해서 메모리를 회수해야 차단이 풀리기 때문이다.

Flow Control 오진 패턴

재시작은 해결책이 아니다. Consumer 처리 속도가 발행 속도보다 느린 구조적 문제가 남아있으면, 재시작 후 메모리가 다시 임계값에 도달하는 순간 똑같이 차단된다.

Lazy Queue는 이 문제의 예방책이다. 일반 Queue는 메시지를 메모리에 먼저 올리고 임계값 초과 시 디스크로 내리는(Paging) 반면, Lazy Queue는 메시지 도착 즉시 디스크에 저장하고 메타데이터만 메모리에 유지한다. 100만 개 메시지 기준으로 일반 Queue는 수백 MB에서 수 GB의 메모리를 쓰지만 Lazy Queue는 메타데이터 크기인 수백 MB 수준에 머문다.

권장 설정은 vm_memory_high_watermark=0.4, paging_ratio=0.5(20%에서 미리 Paging 시작), disk_free_limit=메모리×1.5다. 디스크 여유 공간이 임계값 아래로 내려가도 Publisher가 차단된다는 점을 간과하지 말 것.

모니터링 없이는 장애가 항상 “갑자기” 온다

핵심 지표는 세 가지다.

Queue Depth는 Ready와 Unacked를 구분해서 봐야 한다. Ready가 증가하면 Consumer 처리 속도 부족이다. Unacked가 증가하면 Consumer가 메시지를 받았지만 처리를 완료하지 못하는 상태다 — 보통 외부 I/O 블로킹이 원인이다.

Publish Rate vs Deliver Rate의 차이가 지속적으로 양수면 Queue가 쌓이고 있다는 신호다. ack/s가 실질적인 처리량 지표다.

Consumer Utilisation이 50% 미만이면 Consumer가 Prefetch 대기 시간 때문에 놀고 있다는 뜻이다. Prefetch를 늘리거나 Consumer 수를 줄일 수 있다. 100%에 가까우면 Scale Out을 검토해야 한다.

DLQ 메시지는 0이어야 정상이다. 0에서 1이 되는 순간이 가장 중요한 시점이다.

트레이드오프: Management UI vs Prometheus+Grafana

Management UI는 기본 내장이고 실시간 상태 확인에 충분하지만 알림 기능과 이력 저장이 없다. 프로덕션에서는 rabbitmq_prometheus 플러그인 + Grafana 조합이 필수다. 알림은 P1(즉각)/P2(30분)/P3(일일) 계층으로 나눠야 알림 피로를 피할 수 있다.

운영 장애는 세 가지 패턴으로 수렴한다

패턴 A — Queue 적체: Queue Depth Ready가 급증하고 Consumer는 있다. Consumer Utilisation이 100%이면 Scale Out이 답이다. Consumer가 특정 메시지에서 루프를 돌며 requeue=true로 반복 실패하는 경우에는 DLX 설정이 없으면 해당 메시지가 계속 순환한다.

패턴 B — Unacked 누적: Ready는 0인데 Unacked가 수만 건이다. Consumer는 살아있지만 Ack을 보내지 않는다. 외부 API 타임아웃이 없어서 스레드가 무한 대기 중인 경우가 가장 흔하다. Consumer를 재시작하면 Connection이 끊기며 Unacked가 모두 Ready로 복귀한다 — 임시 처치다. 근본 해결은 모든 외부 I/O에 타임아웃을 설정하는 것이다.

패턴 C — 연결 폭풍: RabbitMQ 재시작 후 서비스 100개가 동시에 재연결을 시도한다. 고정 간격 재연결(sleep(1000))은 모든 서비스가 1초 후 동시에 재시도해서 폭풍을 증폭시킨다. Exponential Backoff + Jitter가 답이다. 지수 증가(1, 2, 4, 8… 최대 30초)에 ±30% 랜덤 지터를 더하면 재연결 시도가 시간적으로 분산된다.

클러스터는 홀수 노드와 Quorum Queue가 기본이다

단일 노드 RabbitMQ는 SPOF다. 3노드 클러스터 + Quorum Queue를 구성하면 노드 1개 장애에서 자동으로 복구된다.

Quorum Queue는 Raft 합의 알고리즘 기반이다. Leader 노드가 다운되면 Election Timeout(기본 5초) 후 Follower들이 투표를 시작하고, 과반수가 새 Leader를 선출한다. 총 다운타임은 보통 5~15초다. 인간 개입 없이 자동 복구된다.

홀수 노드를 권장하는 이유는 Split-Brain 처리 방식에 있다. 4노드 클러스터가 2+2로 분리되면 어느 쪽도 과반수(>2)를 달성하지 못해 전체 쓰기가 불가능해진다. 3노드에서 2+1로 분리되면 2가 과반수를 유지해 부분 가용성이 보존된다.

Rolling Upgrade는 노드 하나씩 stop_app → 업그레이드 → start_app 순서로 진행한다. 메이저 버전 업그레이드는 한 단계씩만 허용된다 — 3.9에서 3.12로 직접 올리면 클러스터가 분열된다. 기능 플래그는 모든 노드가 신버전이 된 후에 활성화해야 한다.

정리

  • 처리량은 Publisher(비동기 Confirm, 배치)와 Consumer(concurrency, prefetch) 양쪽을 함께 튜닝해야 한다.
  • Flow Control은 메모리 임계값 초과 시 Publisher를 차단하는 설계다. 재시작이 아니라 Consumer 처리량 증가 또는 Lazy Queue가 해결책이다.
  • 핵심 지표는 Queue Depth(Ready/Unacked 구분), Publish-Deliver Rate 차이, Consumer Utilisation, DLQ 메시지 수다.
  • 운영 장애는 적체/Unacked 누적/연결 폭풍 세 패턴으로 수렴한다. 각각 임시 처치(Consumer 재시작, Scale Out, 순차 재시작)와 근본 해결(타임아웃 설정, Backoff+Jitter)이 다르다.
  • 3노드 이상 홀수 클러스터 + Quorum Queue는 선택이 아니라 프로덕션의 기본값이다.

다음 글에서는 RabbitMQ와 Kafka의 아키텍처 차이 — Push 기반 vs Pull 기반, 메시지 삭제 vs 로그 보존 — 를 구체적인 설계 결정으로 추적한다.