해당글은 제가 다니고 있는 사내 기술공유 게시판에 올린 글입니다.
현재 RabbitMQ 4.x 버전이 나와있으며, 이번 Major 버전에서 어떤 점들이 바뀌었는지 궁금해졌습니다.
여러가지 변경점이 있지만, 그 중 가장 주요한 점은 클러스터링과 고가용성을 위한 Quorum Queue에 대한 많은 초점이 맞춰져있습니다.
역사
기존 RabbitMQ는 Classic Queue를 사용하였는데, 4.0버전으로 가면서 클래식 큐에서 제공하던 미러링(Mirroring) 기능이 제거 되었습니다. 미러링 기능이란 클래식 큐에서 고가용성을 위해 여러 노드에 큐를 복사하여 리더 노드에 문제가 생겼을 경우 팔로워노드가 리더 노드가 될 수 있게끔 하는 방식을 말합니다.
이 방식의 문제점은 "스플릿 브레인(Split-Brain)"시나리오에 매우 취약했다는 점입니다. 노드 장애시 수동으로 개발자가 개입할 수 밖에 없었습니다. 때문에 4.0버전에서는 클래식큐의 미러링 기능을 제거하고 고가용성을 유지하기 위해서는 Quorum Queue나 Streams을 사용하도록 권장하고 있습니다.

RabbitMQ 4.0이상부터 클래식 큐는 더이상 고가용성을 제공하지 않는다는 점을 파악하고 있으면 될 것 같습니다.
Quorum Queue
쿼럼큐는 RabbitMQ 3.8버전 이상에서 도입되었습니다. Raft 합의 알고리즘을 통해 데이터 안정성과 고가용성을 보장하고 있습니다. 클래식 큐의 미러링, 스플릿 브레인 문제를 극복하기 위해 설계되었습니다.
스플릿 브레인
Raft 합의 알고리즘을 통해 스플릿 브레인 문제를 해결하였다고 했는데, 스플릿 브레인 문제가 어떤 문제인지 간단히 확인해보겠습니다.

이렇게 3대의 노드가 서로 연결되어 있다고 가정합니다.

위의 상황에서 네트워크가 일시적
단절(네트워크 케이블 뽑힘, 스위치 문제 등) 되었다고 하면

이렇게 자기 자신을 Master로 승격하며, 네트워크가 복구되었을 때에는

이런 식으로 Master가 2개 생기는 스플릿 브레인 문제가 발생합니다.
네트워크가 복구되었을 때, RabbitMQ는 설정된 파티션 처리 전략(예: pause_minority)에 따라 소수 파티션(위 경우 A노드)에 있던 노드를 멈추게 합니다. 이때, 네트워크 단절 전에 A노드에서 처리하던 미확정(uncommitted) 데이터가 있다면 이는 유실될 수 있습니다.
그리고 새로운 마스터를 다시 레플리카로 두는 등의 작업은 개발자가 수동으로 진행해줘야하는 운영상의 부담이 있습니다.
그 외 문제는 또 있지만 범위 밖을 벗어나기에.. 넘어가겠습니다!
Raft 합의 알고리즘
그러면 위 문제를 Raft 합의 알고리즘으로 어떻게 해결했을까 살펴보겠습니다.
Raft합의 알고리즘은 어떤 동작을 취할때 과반수 이상의 미러 노드로부터 복제 승인을 받아야합니다. RabbitMQ에서의 어떤 동작이란 데이터 처리라고 가정하겠습니다.

네트워크가 단절된 상황에서 데이터가 들어왔을 경우입니다.
Raft 합의 알고리즘에 의하면 A노드가 Data X를 처리하기 위해서는 과반수 이상의 복제 노드로부터 동의를 받아야합니다.(3대의 노드중 2대)
하지만 네트워크가 단절되어, 동의를 받지 못해 자기 자신만 동의하게 되었으므로 (3대의 노드중 1대) 이 데이터는 처리할 수 없게 됩니다. 다만 , 로그성으로 데이터를 남기게 됩니다.
이때 단절된 B, C 노드는 새로운 마스터를 선출하고 데이터를 받을 준비를 합니다.
만약 B가 새로운 마스터이고, 데이터를 받았다면 C와의 통신을 통해 과반수 이상의 동의를받고 데이터를 처리하게 됩니다.
이후, 네트워크가 복구되면 A노드는 B노드가 새로운 마스터가 되었음을 알고 서로의 로그를 일치시키는 작업을 합니다. 과반수의 그룹을 가진 마스터를 기준으로 로그가 일치화됩니다. 이 과정에서 서로의 로그를 비교하고 불일치 지점부터 강제로 덮어쓰도록 합니다.
이러면 데이터 X에 대한 기록은 유실됩니다.
어, 그럼 클래식큐에서도 데이터 유실되는데 똑같은거 아닌가요? 할 수 있습니다.
맞습니다. Raft 합의 알고리즘의 핵심은 일관성입니다. 클래식큐에서는 네트워크가 재연결되어도 어떤 노드의 데이터가 진실된지 모릅니다. 반면 Raft 합의 알고리즘을 사용하는 쿼럼큐는 과반수동의를 통해 진실 데이터를 갖게 됩니다. 따라서 Raft 알고리즘은 일관성 유지에 더 큰 이점을 가져다주는 트레이드 오프라고 생각하시면 됩니다.
성능
위의 시나리오를 확인해보시면 과반수 동의를 얻기 위해 네트워크 트래픽이 많아질 것을 예상할 수 있습니다. 로그 복제, 하트비트, 새 리더 선출과정에서는 네트워크 통신이 있을 수 밖에 없기 때문입니다. 따라서 Raft 합의 과정을 거치는 쿼럼 큐는 클래식 큐(특히 비미러링 비영속 클래식 큐) 대비 기본적인 네트워크 오버헤드가 발생할 수 있습니다. 하지만 데이터 일관성 보장과 자동 복구, 스플릿 브레인 방지의 이점은 무시할 수 없습니다. 메모리도 많이 차지하게 되는데 버전업을 통해 이러한 메모리 효율성도 지속적으로 개선되고 있습니다.
실제로 공식문서에서 기존 클래식큐와 쿼럼큐를 비교한 글이 있어서 이를 기준으로 분석해보겠습니다.
테스트 시나리오
- 3.12버전을 기준으로 비교합니다.
- e2-standard-16 노드GKE 클러스터(Google)
테스트는 총 14개로, 5분마다 실행됩니다. 서로 다른 환경에서 서로 다른 메시지 크기로 동일한 테스트를 동시에 진행하게됩니다.
- 1개의 생산자는 가능한한 빨리 메시지를 생산하고, 1개의 소비자는 가능한한 빨리 메시지를 소비함.
- 2개의 생산자가 소비자는 없는 환경에서 계속해서 메시지를 생산함. (대기열이 길어질수록 성능 떨어짐)
- 한 소비자가 2번 테스트에서 쌓인 긴 대기열을 소비함.(소비자 성능 확인)
- 5개의 큐에는 각각 10k 메시지를 초마다 생산하고, 1개의 소비자가 이를 처리함.(총 예상 처리량 50k/초)
- 1개의 생산자가 Fan-out 방식으로 메시지를 10개의 큐에 보냄.
- 생산자 1개, 소비자 1개인 상황에서 확인되지 않은 메시지가 1개 존재.(이전 메시지가 확인될 때까지 생산자는 대기함)
- 7000개의 생산자가 초당 1개의 메시지를 Fan-in 방식으로 보냄.
- 1000개의 생산자가 각각 다른 큐에 10개의 메시지를 전달. 각 대기열에는 소비자가 존재. (예상 처리량 10k/s)
- 1개의 생산자, 소비자 없음. 백로그 메시지를 생산하고 10개의 소비자가 백로그 메시지를 소비하기 위해 참여하여 제거
=> 소비자가 없기에, 메시지가 큐에 쌓여 백로그를 형성하는데 메시지 유실 없이 얼마나 잘 버텨내는지 확인, 메시지를 얼마나 효율적이고 빠르고 분배하여 큐를 소진하는지확인 - 9번 시나리오에서 50명의 소비자가 참여하지만 1개의 소비자만 큐를 비우도록 함.
=> SAC(single-active-consumer)설정에 따른 큐의 동작, 성능 확인, 메서지 처리 순서 보장 확인 - 1번 시나리오에서 소비자는 다중 ack이 사용됨.
=> 다중 ack 설정했을 때의 성능 변화 측정 - TTL이 존재하는 메시지 발송(빠르게 사라질 것을 기대)
- 재처리 메커니즘 확인을 위한 nack메시지를 생성
- 13번 시나리오에 쌓인 메시지들 모두확인
3.11 vs 3.12 클래식큐
이는 쿼럼큐와의 비교는 아니므로 넘어가겠습니다. 전체적으로 성능은 개선되었습니다.
그럼에도 이 챕터를 넣은 이유는 점진적으로 개선됨을 나타내기 위함입니다.
3.12 클래식큐v1(CQV1) vs 클래식큐v2(CQV2)
클래식큐v2도 있습니다.
참고로 클래식큐v2는 RabbitMQ 3.13의 기본(default) 큐입니다.

- cqv2가 지연시간이 더 낮고, 처리량도 높아짐을 확인할 수 있습니다.
- ** 두 번째 테스트에서 지연시간이 50ms를 넘지 않는 반면 cqv1은 점점 길어짐을 알 수 있습니다.**
CQV1-mirror vs CQV2-mirror

글에서 cqv1-lazy-mirrored는 메모리 사용량을 줄이기위해 Lazy모드를 추가한 큐입니다.
- cqv1은 미러링될 때의 동작을 개선하기 위해 여러 최적화 기능이 포함되어있습니다. 현재는 미러링 기능을 제거하고 있는 상황에서 cqv2는 미러링 기능이 있음에도 효율적으로 동작하여 성능이 더 나은 부분이 많음이 알 수 있습니다.
Quorum Queues(QQ)

글에서는 3.12버전과 3.11버전에 대한 QQ를 비교하고 있는데, 이것보다 중요한건 ClassicQueue와의 비교입니다.
CQV1-mirror vs CQV2-mirror 해당 카테고리의 그래프와 비교해야하는데,
결과적으로 특정 시나리오에서는 클래식 큐(특히 비미러링, 비영속 설정)가 더 높은 처리량을 보일 수도 있습니다. 하지만 데이터 일관성과 안정성을 보장하는 상태에서는 쿼럼 큐가 월등히 우수하며, 이러한 맥락에서 성능적인 우위를 판단하는 것이 중요합니다.
QQ가 아직까지는 클래식큐보다 성능적인 우위를 가지고 있다고는 딱 정할 수 없지만
QQ는 데이터 일관성과 안정성을 보장하면서 높은 성능을 목표로설계되었고, CQV는 데이터 일관성과 안정성 대신에 최대 처리량에 초점이 맞춰져있습니다. 따라서 둘이 성능적인 면에서만 비교하는 것은 의미가 없고, 여러가지 관점에서 봐야합니다. 실제 엔터프라이즈 환경에서는 성능이 QQ가 CQV보다 처리량이 낮을지라도 QQ가 가지고 있는 데이터 안정성, 고가용성, 백로그 처리등이 우세한 QQ가 대세로 사용되고 있습니다.
현재는?(4.0 ~ 4.1)
RabbitMQ 4.0부터는 클래식큐의 미러링이 제거되었기에 쿼럼큐 위주의 개선사항이 많았습니다. 밑의 내용은 모두 쿼럼큐에 대한 내용입니다.
- 4.0: 메시지 우선순위 지원, SAC 결합, 체크포인트 파일을 이용한 큐 복구 속도 향상, 기본 재전송 한도 제한, 등
- 4.1: 메모리 사용 패턴 개선으로 낮고 안정적인 메모리 사용량, Long Queue 소비 시 성능 향상, 메시지 크기에 따른 성능 최적화(500byte 미만 메시지에 대한 처리량 두 배 증가, 지연시간 절감
즉, 4.0은 안정성, 복구 속도 향상에 초점이 있고 4.1은 성능 최적화에 초점을 둔 것을 확인할 수 있습니다.
결론
제가 느꼈을 때, 쿼럼큐는 특정 시나리오에 대해서는 나은 성능을 보이지만 아직 가야할 길이 있는 것으로 보입니다. 그럼에도 데이터 일관성이 보장된다는 것은 큰 이점입니다.
또한, 대놓고 쿼럼큐나 스트림을 쓰라는 등, 계속해서 개선하고 있다고 언급하거나 아예 4버전대에서는 클래식큐의 고가용성 방식을 제거하고 쿼럼큐에 대한 개선이 많은 것을 토대로 쿼럼큐를 계속 밀어주고 있음을 알 수 있습니다.
부록) 복구력 테스트
개인적으로 쿼럼큐와 클래식큐 복구력이 얼마나 차이가 나는지 궁금하여 간단한 테스트를 진행하였습니다.
환경
- RabbitMQ 3.13버전
- RabbitMQ Cluster(Node 3대)
- docker container
- Java24(Virtual Thread)
- RabbitMQ Client - Spring Boot
가정
- Producer는 10만개
- 2초마다 1000개의 메시지를 마다 보냄.
- 메시지 연결 통로인 RabbitMQ Channel이 너무 커져서 에러나는 것 방지
AtomicLong을 사용하여 원자성 보장(가상 쓰레드 동시성 문제)- 해당 변수를 통해 메시지를 소비하면 카운트 1증가
- 메시지 소비를 기다려야하므로 10만개 메시지보내고 30초 대기
- Producer하는 도중에 특정 Container down
시나리오 및 기대효과
- 미러링 없는 클래식 큐 사용했을 때: 특정 노드 down시 메시지 소비 불가
- 미러링 있는 클래식 큐 사용했을 때: 특정 노드 down시 특정 큐가 다른 큐로 복제되어 유실이 없거나
- 쿼럼 큐 사용시
미러링 없는 클래식 큐 사용했을 때(4.0 버전 이상 default)
rabbitmq2서버를 down

▶️ 테스트 결과


- 특정 노드로 가는 메시지는 처리 못하고 있습니다.
미러링 있는 클래식 큐 사용했을 때(4.0 버전 미만)

이전에 쌓인 메시지가 존재하여 한 번 큐를 비워주었음, rabbitmq1서버를 down

▶️ 테스트 결과


클래식큐는 메시지 중복이 발생. 어떤 메시지가 중복되었는지 간단한 설정으로는 모릅니다.
쿼럼큐 사용했을 때
이미 위 두 개의 결과창에서 보셨겠지만 메시지 유실 하나 없이 10만개 모두 처리함을 볼 수 있습니다.
출처
'작업 목록' 카테고리의 다른 글
| Java21에 나온 ZGC-Generation적용해보기 (0) | 2025.06.18 |
|---|---|
| [grpc] grpcurl에서 Failed to list services: server does not support the reflection API 라는 에러가 뜰 때 (0) | 2025.04.03 |
| JPA, Spring MVC를 이용한 동적 스키마 변경 (0) | 2025.02.21 |
| Git Hook을 이용한 CD구축(With Dooray Messenger) 작업 후기 (0) | 2025.02.12 |