백엔드 면접 용어 사전
메시지 큐quorum queue · delivery-limit

쿼럼 큐

Raft 합의로 복제되는 RabbitMQ의 고가용성 큐. 4.0에서 classic mirrored queue가 제거되며 유일한 선택지가 됐다.

Raft 합의 알고리즘으로 복제되는 RabbitMQ의 큐 종류. 고가용성이 필요하면 이제 이것뿐이다.

무엇을 대체했나

예전에는 classic mirrored queue(클래식 큐를 여러 노드에 미러링)로 고가용성을 구성했다. 이 방식은 복제 일관성이 약해서 네트워크 분단 뒤 데이터가 어긋나는 문제가 있었다.

RabbitMQ 4.0에서 classic queue mirroring이 제거됐다. 복제 큐가 필요하면 쿼럼 큐(또는 스트림)를 써야 한다.

Raft — 과반이 동의해야 확정된다

쿼럼 큐 (3노드)
  리더 노드  ← 모든 쓰기/읽기가 여기로
     │ 로그 복제
     ├→ 팔로워 1
     └→ 팔로워 2

과반(2/3)이 로그에 기록해야 "확정"으로 인정
→ 노드 1대가 죽어도 계속 동작, 2대가 죽으면 멈춘다

과반이라는 조건 때문에 노드 수는 홀수(3, 5)로 잡는다. 4대는 3대와 내성이 같으면서(둘 다 1대까지) 비용만 더 든다.

이 구조는 앞서 본 Kafka의 KRaft 컨트롤러 쿼럼과 같은 원리다 — 합의 알고리즘으로 로그를 복제한다는 발상이 분산 시스템 전반의 표준이 된 셈이다.

트레이드오프

쿼럼 큐클래식 큐
복제Raft·강한 일관성없음(미러링은 제거됨)
내구성항상 durable(비durable 불가)선택 가능
처리량미러링 큐 대비 약 3배
지연디스크 I/O가 필수라 조금 더 김더 낮을 수 있음
미지원exclusive 큐·global QoS

"항상 durable"이라는 건 모든 메시지가 디스크를 거친다는 뜻이다. 지연에 극도로 민감하고 유실을 감수할 수 있는 워크로드라면 복제 없는 클래식 큐가 여전히 답일 수 있다.

실무에서 가장 유용한 기능 — delivery-limit

독약 메시지(처리하면 반드시 실패하는 메시지)가 무한 재큐되며 큐를 막는 문제를, 쿼럼 큐는 구조적으로 막는다.

메시지 헤더에 delivery-count 를 자동으로 센다 전달 → 실패 → 재큐 → delivery-count++ ... delivery-limit(4.0 기본 20) 초과

  • 버리거나 DLX(Dead Letter Exchange)로 보낸다

클래식 큐에서는 이 카운트를 애플리케이션이 직접 헤더에 세야 했다. Kafka 쪽의 "재시도 N회 후 DLT"와 같은 역할을 브로커가 대신 해 주는 셈이다.

면접 함정

  • "미러링 큐로 HA를 구성한다" → 4.0에서 제거됐다.
  • "쿼럼 큐가 모든 면에서 낫다" → 항상 durable이라 지연이 더 들고, exclusive·global QoS를 못 쓴다.

선언과 설정

// 큐 타입을 인자로 지정한다. 나중에 바꿀 수 없으므로 만들 때 정해야 한다
Map<String, Object> args = Map.of(
    "x-queue-type", "quorum",
    "x-delivery-limit", 5,                      // 5회 실패하면 DLX 로
    "x-dead-letter-exchange", "orders.dlx"
);
channel.queueDeclare("orders", true, false, false, args);

클래식 큐 → 쿼럼 큐로 "변경"은 불가능하다

  • 새 큐를 만들고 → 소비자를 옮기고 → 남은 메시지를 이관하고 → 옛 큐 제거

노드 수는 홀수로

  • 3노드: 과반 2 — → 1대 장애까지 견딤
  • 4노드: 과반 3 — → 1대 장애까지 견딤 (3노드와 같은데 비용만 증가)
  • 5노드: 과반 3 — → 2대 장애까지 견딤

짝수는 내성을 늘리지 못한다. Raft를 쓰는 시스템이 대체로 3 또는 5인 이유이고, 앞서 본 Kafka의 KRaft 컨트롤러도 같다.

함께 보면 좋은 용어

노트에서 맥락과 함께 보기 — 메시지 큐·비동기 — Kafka/RabbitMQ·EOS·순서·outbox