들어가기 앞서
오늘은 RabbitMQ의 Work Queue에 대해서 알아볼 예정입니다.
추후 exchange에 대해 공부할 예정인데, 저는 exchange와 work queue가 같은 개념의 용어로, exchange모델 중 하나가 work queue인줄 알았습니다.
- exchange : 프로듀서에게 메시지를 받아 어떤 큐로 보낼지 결정 (프로듀서 → 큐)
- work queue : 큐에 있는 메시지를 어떤 컨슈머에게 보낼지 결정 (큐 → 컨슈머)

먼저 두 개념에 대해 한번 설명하고 가면 좋을것 같아, 간단하게 설명드리게 되었습니다.
Work Queue
작업큐(Work Queue)는 여러개의 메시지를 큐에 쌓아두고, 이를 여러 개의 컨슈머가 분산 처리하는 구조를 의미합니다.

작업큐를 통해 작업 부하를 효율적으로 분산시키고, 병렬 처리를 가능하게 만들어 처리 속도를 향상시킵니다.
- Round-Robin : default 설정이며, 메시지는 연결된 모든 컨슈머에게 균등하게 분배 됩니다. ex) 1 → 2 → 3 → 1
- Fair Dispatch : 기본 Roud-Robin은 작업시간은 고려하지 않고 분배한다는 특징이 있습니다. 반면 공정 분배 방식은 prefetch 설정을 통해 한 워커가 작업을 끝내기 전에는 다른 작업을 할당받지 않도록 설정할 수 있습니다. 즉, 더 느린 워커가 과부하되지 않도록 방지할 수 있습니다.
Round Robin

- RabbitMQ는 메시지를 받은 순서대로 소비자들에게 순차적으로 분배합니다.
- 별다른 설정이 없으면, 모든 Consumer는 메시지를 수신한 후 즉시 Ack가 도착하든 말든 다음 메시지를 계속 전달받습니다.
- 이는 모든 Consumer가 유사한 처리 속도를 가진 경우에는 부하가 균등하게 분산되기 때문에 적합합니다.
- 그러나 일부 작업이 무겁거나 특정 Consumer가 느린 경우에는 해당 Consumer에 메시지가 몰리게 되어 부하 불균형 문제가 발생합니다.
Fair Dispatch

- 라운드 로빈의 불균등한 작업시간을 해결하기 위해 사용하는 방법이 Prefetch Count 설정입니다.
- prefetch count는 Consumer가 동시에 처리할 수 있는 메시지 수를 제한합니다. 일반적으로 1로 설정하면 "하나 처리 완료되기 전에는 다른 메시지 받지 않기" 방식이 됩니다.
- prefetch count를 1로 설정하면, RabbitMQ는 Consumer로부터 ACK를 받을 때까지 추가 메시지를 전달하지 않음으로써 느린 Consumer에는 메시지를 덜 보내고, 빠른 Consumer에 더 많이 보낼 수 있게 됩니다.
- 이로 인해 Consumer 간 처리량 차이를 고려한 공정한 메시지 분배가 가능해집니다.
비교
| 구분 | Round Robin | Fair Dispatch (prefetch count=1) |
| 메시지 분배 방식 | 순서대로 분배 | 처리 능력에 따라 분배 |
| 설정 여부 | 기본값 (별도 설정 x) | basicQos(1) 등 설정 필요 |
| 처리 불균형 | 발생 가능 (느린 Consumer에 몰림) | 완화됨 (빠른 Consumer에게 메시지 몰림) |
| 적합한 경우 | 작업이 비슷하고 Consumer가 균등할 때 | 작업이 무겁고 처리 속도가 다른 경우 |
실제 동작 확인하기
1. 서버 여러대 띄우기

동일한 애플리케이션(컨슈머)을 서로 다른 포트로 설정하여 여러대를 실행 시킵니다. (Spring Boot에서 생성가능하며, 생성 방법은 생략하겠습니다.)
2. 프로듀서 메세지 생성
포스트맨 혹은 curl 명령어를 통해 3개의 메세지를 생성하여, 큐에 전달합니다.
3. durable 설정
제가 실습한 프로젝트는 Message Durability를 설정하고 있습니다. durability는 RabbitMQ가 종료 되더라도 메세지가 손실되지 않도록 하는 방법 입니다. durable을 선언 하여 RabbitMQ 노드가 재시작 후에도 큐가 유지되도록 설정 하였으니, 이점 참고해 주시면 감사하겠습니다.
3-1. Round Robin 방식의 분산 확인하기 (메세지 3개 생성)
처음 메세지를 3개 생성하면, 1번 서버가 2개를 처리하고, 2번 서버가 1개를 처리하는 것을 확인할 수 있습니다.


3-1. Round Robin 방식의 분산 확인하기 (메세지 6개 생성)
여기서 메세지를 또 3개더 생성하여 총 6개를 생성하면 어떻게 될까요?


새롭게 3개를 보내면서 다음과 같이 메세지가 전달 됩니다.
- 서버1 : 기존 2개 + 신규 1개 → 총 3개 / 받은 메시지 : 1번→3번→5번 (기존 메시지/신규 메시지)
- 서버2 : 기존 1개 + 신규 2개 → 총 3개 / 받은 메시지 : 2번→4번→6번 (기존 메시지/신규 메시지)
즉, 모든 컨슈머는 동일한 갯수의 메시지를 처리하게 됩니다.
3-2. Round Robin 방식의 분산 확인하기 - 컨슈머 1대가 고장이 난다면 어떻게 처리 될까?
운영 도중 컨슈머가 고장이 난다면 어떻게 처리가 될까요?
- 6개의 메시지를 발생하여 분산 처리가 어떻게 되는지 확인
- 처리되지 못한 메시지를 위해 컨슈머(Consumer)를 껐다 켰을때 어떻게 처리되는지 확인
과정을 직접 테스트 해보기 전에 제가 한번 결과를 추측해 보겠습니다.
[내가 예측한 동작]
1. 메시지 6개 발행
2. 컨슈머로 라운드 로빈 방식으로 메시지 전달
서버1 : 1번 → 3번 → 5번
서버2 : 2번 → 4번 → 6번 (서버 장애🔥)
3. 서버2 재시작
서버2에 있던 2, 4, 6번 메시지는
4. 다시 큐로 이동다시 컨슈머로 라운드 로빈 방식으로 메시지 전달
서버1: 1번 → 3번 → 5번 → 2번 → 6번
서버2: 4번
[실제 동작]
1. 메시지 6개 발행
2. 컨슈머로 라운드 로빈 방식으로 메시지 전달
서버1 : 1번 → 3번 → 5번
서버2 : 2번 → 4번 → 6번 (서버 장애🔥)
서버1 : 1번 → 3번 → 5번 → 2번 → 4번 → 6번 (비동기로 동작하기에 순서는 다를수 있습니다.)
실제 동작은 다음과 같았습니다. 1번 컨슈머 서버와 2번 컨슈머 서버에 예측한 대로 메시지가 전달 되었습니다. 하지만, 제 예상과 다르게 2번 컨슈머 서버를 재시작 하지 않아도 메시지가 큐로 자동으로 돌아 갔습니다.


조금더 쉽게 요약하자면, 저는 컨슈머 서버가 죽어야 지만, 메시지가 큐로 돌아가는 줄 알았습니다.
Spring AMQP에서 AcknowledgeMode.AUTO에서는
- 메시지 처리 성공 : RabbitMQ에게 ACK 전송
- 메시지 처리 실패 : 컨슈머 메서드 실행 중 예외가 발생하면 NACK 전송, default 설정으로 requeue=true가 되어 있어 자동으로 큐로 되돌아 갑니다.
즉, 메시지를 받은 뒤 Exception이 발생하면 Spring은 그 메시지를 처리 실패(NACK) 로 간주하고 자동으로 큐에 다시 넣어줌.
그렇다면 자동으로 큐에 넣지 않도록 하려면 어떻게 해야 될까요?
container.setDefaultRequeueRejected(false);
설정에 setDefaultRequeueRejected = false 로 설정하면 장애가 발생하더라도, 처리되지 않은 메시지가 큐로 돌아가지 않습니다.


그렇다면 "setDefaultRequeueRejected = false"를 설정하게 되면 제가 예측한대로 동작을 할까요?
[setDefaultRequeueRejected = false 설정시 실제 동작]
1. 메시지 6개 발행
2. 컨슈머로 라운드 로빈 방식으로 메시지 전달
서버1 : 1번 → 3번 → 5번 (끝!)
서버2 : 2번 → 4번 → 6번 (서버 장애🔥) : 메시지가 다시 큐로 돌아가지 않음.
3. 서버를 재시작해도 실패한 메시지는 큐로 돌아 가지 않음
아니요. Requeue를 false로 설정하면, 실패한 메시지를 큐에 다시 집어 넣지 않는다는 뜻입니다. 그래서 2번 컨슈머 서버에서 장애로 인해 실패한 메시지는 재큐잉 되지 않기 때문에 1번 컨슈머 서버가 처리하는 메시지는 1, 3, 5가 끝이게 됩니다.


4-1. Fair Dispatch 방식의 분산 확인하기
총 3대의 컨슈머의 서버를 설정하고 8개의 메시지를 생성하였습니다. 그리고 'container.setPrefetchCount(1)' 설정을 통해, 각 컨슈머의 서버는 1개의 메시지만 받을 수 있도록 설정해 주었습니다.
- 1번 컨슈머 서버 : 메시지 당 1초 sleep
- 2번 컨슈머 서버 : 메시지 당 5초 sleep
- 3번 컨슈머 서버 : 메시지 당 2.5초 sleep
[내가 예측한 동작]
Fair Dispatch는 서버의 처리성능에 맞춰 메시지를 분산 시키는 방식입니다.
1번 컨슈머 : 1번→4번→5번→7번→8번 5개 (1초x5개 = 5초)
2번 컨슈머 : 2번 1개 (5초x1개 = 5초)
1번 컨슈머 : 3번→6번 2개 (2.5초x2개 = 5초)



실제로 제가 예측한 과정으로 메시지가 처리된 것을 알 수 있습니다.
Reference
'Kafka, RabbitMQ' 카테고리의 다른 글
| RabbitMQ에 대해 알아보자 - (7) Pub-Sub 패턴 / Fanout Exchange 전략 (0) | 2025.05.06 |
|---|---|
| RabbitMQ에 대해 알아보자 - (6) Exchange 전략 (0) | 2025.05.05 |
| RabbitMQ에 대해 알아보자 - (4) 메시지 실패 처리 DLQ, DLX, PLQ (0) | 2025.04.30 |
| RabbitMQ에 대해 알아보자 - (3) 메시지 처리에 실패하게 된다면? (0) | 2025.04.30 |
| RabbitMQ에 대해 알아보자 - (2) RabbitMQ vs Kafka (0) | 2025.04.30 |