컨슈머 인정과 퍼블리셔 확인

컨슈머 인정과 퍼블리셔 확인 (Acknowledgements & Confirms)

메시지가 정말 전달·처리됐는지 보장하는 두 축이 있어요. 컨슈머가 브로커에게 "받아서 처리했어요"라고 알리는 **컨슈머 인정(ack)**과, 브로커가 퍼블리셔에게 "메시지 책임졌어요"라고 알리는 **퍼블리셔 확인(confirm)**이죠. 이번엔 수동·자동 인정 모드, 재큐잉, prefetch, 그리고 confirm 모드의 동작까지 잡아볼게요.

출처: Consumer Acknowledgements and Publisher Confirms | RabbitMQ

본문

이 가이드는 데이터 안전성과 관련된 두 가지 밀접한 기능, 컨슈머 인정과 퍼블리셔 확인을 다뤄요.

  • 인정이 존재하는 이유
  • 수동·자동 인정 모드
  • 멀티 ack·재큐잉을 포함한 인정 API
  • 연결 손실·채널 폐쇄 시 자동 재큐잉
  • 채널 prefetch와 처리량에 미치는 영향
  • 가장 흔한 클라이언트 오류
  • 퍼블리셔 확인과 관련 데이터 안전성 주제

메시징을 쓰는 애플리케이션의 데이터 안전성에는 컨슈머·퍼블리셔 쪽의 인정이 모두 중요해요. 관련 내용은 Publisher·Consumer 가이드에도 더 있어요.

인정은 왜 중요한가

RabbitMQ 같은 메시징 브로커를 쓰는 시스템은 정의상 분산돼요. 보내는 프로토콜 메서드(메시지)가 상대에게 도달하거나 성공적으로 처리된다는 보장이 없으므로, 퍼블리셔와 컨슈머 모두 전달·처리 확인 메커니즘이 필요해요. RabbitMQ가 지원하는 여러 메시징 프로토콜이 그런 기능을 제공해요. 이 가이드는 AMQP 0-9-1의 기능을 다루지만, 다른 지원 프로토콜에서도 아이디어는 대체로 같아요.

컨슈머에서 RabbitMQ로 가는 전달 처리 인정은 메시징 프로토콜에서 인정(acknowledgements)이라 불리고, 브로커에서 퍼블리셔로 가는 브로커 인정은 퍼블리셔 확인(publisher confirms)이라 불리는 프로토콜 확장이에요. 두 기능 모두 같은 아이디어에 기반하며 TCP에서 영감을 받았어요. 퍼블리셔에서 RabbitMQ 노드로, 그리고 RabbitMQ 노드에서 컨슈머로의 신뢰할 수 있는 전달에 필수적이에요. 즉 데이터 안전성에 필수이며, RabbitMQ 노드만큼 애플리케이션에도 책임이 있어요.

퍼블리셔 확인과 컨슈머 전달 인정은 서로 다른 맥락에서 비슷한 문제를 푸는 매우 유사한 기능이에요.

  1. 컨슈머 인정은 이름대로 RabbitMQ와 컨슈머 간의 통신을 다뤄요
  2. 퍼블리셔 확인은 퍼블리셔와 RabbitMQ 간의 통신을 다뤄요

하지만 두 기능은 완전히 직교하고 서로를 모르는 관계예요. 퍼블리셔 확인은 컨슈머를 모르고, 퍼블리셔의 연결 대상 노드와 큐(또는 스트림) 리더 복제본과의 상호작용만 다뤄요. 컨슈머 인정은 퍼블리셔를 모르며, 주어진 전달이 성공적으로 수신·처리돼 미래 삭제로 표시될 수 있음을 RabbitMQ 노드에 확인하는 게 목적이에요.

때로 게시·소비 애플리케이션은 상대에게 명시적 인정이 필요한 요청·응답으로 통신해야 해요. RabbitMQ 튜토리얼 #6이 그 기초를 보여주고, Direct Reply-to가 응답 큐를 만들지 않고 그렇게 하는 방법을 제공해요. 다만 이 통신 유형은 이 가이드의 범위가 아니고, 훨씬 집중된 프로토콜 기능과 대비하기 위해 언급한 것뿐이에요.

전달 식별: delivery tag

RabbitMQ가 컨슈머에게 메시지를 전달할 때, 메시지를 언제 성공적으로 보냈다고 볼지 알아야 해요. 어떤 로직이 최적인지는 시스템에 달려 있어, 주로 애플리케이션 결정이에요. AMQP 0-9-1에서는 컨슈머를 basic.consume 메서드로 등록하거나 basic.get 메서드로 메시지를 요청 시 가져올 때 결정돼요. 예시 중심의 단계별 자료를 선호한다면, 컨슈머 인정은 RabbitMQ 튜토리얼 #2에도 있어요.

전달이 어떻게 식별되는지(그리고 인정이 어떤 전달을 가리키는지)부터 짚고 가야 해요. 컨슈머(구독)가 등록되면 RabbitMQ는 basic.deliver 메서드로 메시지를 전달(푸시)해요. 이 메서드는 채널에서 전달을 고유하게 식별하는 delivery tag를 운반해요. delivery tag는 채널 범위(scoped)로 정해져요. delivery tag는 단조 증가하는 양의 정수이고 클라이언트 라이브러리가 그대로 제시해요. 전달을 인정하는 클라이언트 라이브러리 메서드는 delivery tag를 인자로 받아요.

delivery tag가 채널 범위이므로, 전달은 받은 것과 같은 채널에서 인정해야 해요. 다른 채널에서 인정하면 "unknown delivery tag" 프로토콜 예외가 발생하고 채널이 닫혀요.

노드가 컨슈머에게 메시지를 전달할 때 메시지가 컨슈머에 의해 처리된(또는 적어도 수신된) 것으로 볼지 결정해야 해요. 클라이언트 연결·컨슈머 앱 등 여러 것이 실패할 수 있으므로, 이 결정은 데이터 안전성 문제예요. 메시징 프로토콜은 보통 컨슈머가 전달을 연결된 노드에 인정하게 하는 확인 메커니즘을 제공하고, 그 메커니즘을 쓸지는 컨슈머가 구독할 때 정해요.

인정 모드와 메서드

인정 모드에 따라 RabbitMQ는 메시지를 성공적으로 전달된 것으로 보는 시점이 달라져요. 보내는 즉시(TCP 소켓에 쓰인 뒤)일 수도, 명시적("수동") 클라이언트 인정을 받았을 때일 수도 있어요. 수동으로 보내는 인정은 긍정·부정일 수 있고 다음 프로토콜 메서드 중 하나를 써요.

  • basic.ack — 긍정 인정용
  • basic.nack — 부정 인정용(주의: AMQP 0-9-1에 대한 RabbitMQ 확장)
  • basic.reject — 부정 인정용이지만 basic.nack에 비해 제한이 하나 있어요

이 메서드들이 클라이언트 라이브러리 API에서 어떻게 노출되는지는 아래에서 다뤄요. 긍정 인정은 단순히 RabbitMQ에 메시지를 전달된 것으로 기록하고 폐기해도 된다고 지시해요. basic.reject로 하는 부정 인정도 같은 효과를 가져요. 차이는 주로 의미론이에요. 긍정 인정은 메시지가 성공적으로 처리됐다고 가정하는 반면, 부정 인정은 전달이 처리되진 않았지만 그래도 삭제되어야 한다고 시사해요.

자동 인정 모드에서는 메시지가 보내진 직후 성공적으로 전달된 것으로 간주돼요. 이 모드는 (컨슈머가 따라갈 수 있다면) 더 높은 처리량과 교환으로, 전달·컨슈머 처리의 안전성을 낮춰요. 이 모드는 흔히 "fire-and-forget"라고 불려요. 수동 인정 모델과 달리, 전달이 성공하기 전에 컨슈머의 TCP 연결이나 채널이 닫히면 서버가 보낸 메시지가 유실돼요. 따라서 자동 메시지 인정은 안전하지 않은 것으로 간주하고 모든 워크로드에 적합하지 않아요.

자동 인정 모드를 쓸 때 또 고려할 중요한 점은 컨슈머 과부하예요. 수동 인정 모드는 보통 경계 있는 채널 prefetch로 채널의 미해결("진행 중") 전달 수를 제한해요. 하지만 자동 인정에는 정의상 그런 제한이 없어요. 컨슈머는 전달 속도에 압도돼, 메모리에 백로그가 쌓여 힙을 소진하거나 OS가 프로세스를 종료시킬 수 있어요. 일부 클라이언트 라이브러리는 TCP 백프레셔(처리되지 않은 전달 백로그가 특정 한도 아래로 내려갈 때까지 소켓 읽기 중단)를 적용해요. 자동 인정 모드는 효율적·일정 속도로 전달을 처리할 수 있는 컨슈머에게만 권장돼요.

전달 인정용 API 메서드는 보통 클라이언트 라이브러리에서 채널의 연산으로 노출돼요. Java 클라이언트 사용자는 Channel#basicAckChannel#basicNack로 각각 basic.ackbasic.nack를 수행해요. 다음은 긍정 인정을 보여 주는 Java 클라이언트 예시예요.

// this example assumes an existing channel instance

boolean autoAck = false;
channel.basicConsume(queueName, autoAck, "a-consumer-tag",
     new DefaultConsumer(channel) {
         @Override
         public void handleDelivery(String consumerTag,
                                    Envelope envelope,
                                    AMQP.BasicProperties properties,
                                    byte[] body)
             throws IOException
         {
             long deliveryTag = envelope.getDeliveryTag();
             // positively acknowledge a single delivery, the message will
             // be discarded
             channel.basicAck(deliveryTag, false);
         }
     });

.NET 클라이언트에서는 메서드가 IModel#BasicAckIModel#BasicNack예요.

멀티 ack와 부정 인정·재큐잉

수동 인정은 multiple 필드를 true로 설정해 배치로 네트워크 트래픽을 줄일 수 있어요. basic.reject에는 역사적으로 그 필드가 없어서 RabbitMQ가 basic.nack를 프로토콜 확장으로 도입한 거예요. multipletrue면 RabbitMQ는 인정에 지정된 태그까지 모든 미해결 delivery tag를 인정해요. 인정과 관련된 모든 것처럼 이 역시 채널 범위예요. 예를 들어 채널 Ch에 delivery tag 5, 6, 7, 8이 미인정으로 있고, 그 채널에 delivery_tag8이고 multipletrue인 인정 프레임이 도착하면 5부터 8까지 모두 인정돼요. multiplefalse면 5, 6, 7은 여전히 미인정이에요. Java 클라이언트에서 여러 전달을 인정하려면 Channel#basicAckmultiple 파라미터에 true를 넘겨요.

// this example assumes an existing channel instance

boolean autoAck = false;
channel.basicConsume(queueName, autoAck, "a-consumer-tag",
     new DefaultConsumer(channel) {
         @Override
         public void handleDelivery(String consumerTag,
                                    Envelope envelope,
                                    AMQP.BasicProperties properties,
                                    byte[] body)
             throws IOException
         {
             long deliveryTag = envelope.getDeliveryTag();
             // positively acknowledge all deliveries up to
             // this delivery tag
             channel.basicAck(deliveryTag, true);
         }
     });

.NET 클라이언트도 아이디어는 거의 같아요.

컨슈머가 전달을 당장 처리할 수 없는데 다른 인스턴스가 처리할 수 있을 때는 재큐잉해서 다른 컨슈머가 받아 처리하게 하는 게 바람직할 수 있어요. basic.rejectbasic.nack가 그에 쓰이는 두 프로토콜 메서드예요. 이 메서드는 보통 전달을 부정 인정하는 데 쓰이고, 그런 전달은 브로커가 폐기하거나 데드 레터링하거나 재큐잉할 수 있어요. 이 동작은 requeue 필드가 제어해요. 필드가 true면 브로커는 지정된 delivery tag의 전달(곧 설명하겠지만 여러 전달)을 재큐잉해요. false면 메시지는 Dead Letter Exchange가 설정돼 있으면 그쪽으로, 아니면 폐기돼요.

두 메서드는 보통 클라이언트 라이브러리에서 채널 연산으로 노출돼요. Java 클라이언트 사용자는 Channel#basicRejectChannel#basicNack로 각각 basic.rejectbasic.nack를 수행해요.

// this example assumes an existing channel instance

boolean autoAck = false;
channel.basicConsume(queueName, autoAck, "a-consumer-tag",
     new DefaultConsumer(channel) {
         @Override
         public void handleDelivery(String consumerTag,
                                    Envelope envelope,
                                    AMQP.BasicProperties properties,
                                    byte[] body)
             throws IOException
         {
             long deliveryTag = envelope.getDeliveryTag();
             // negatively acknowledge, the message will
             // be discarded
             channel.basicReject(deliveryTag, false);
         }
     });
// this example assumes an existing channel instance

boolean autoAck = false;
channel.basicConsume(queueName, autoAck, "a-consumer-tag",
     new DefaultConsumer(channel) {
         @Override
         public void handleDelivery(String consumerTag,
                                    Envelope envelope,
                                    AMQP.BasicProperties properties,
                                    byte[] body)
             throws IOException
         {
             long deliveryTag = envelope.getDeliveryTag();
             // requeue the delivery
             channel.basicReject(deliveryTag, true);
         }
     });

.NET 클라이언트에서는 메서드가 IModel#BasicRejectIModel#BasicNack예요.

메시지가 재큐잉되면 가능하면 큐의 원래 위치에 놓여요. 불가능하면(여러 컨슈머가 큐를 공유하고 다른 컨슈머의 동시 전달·인정 때문에) 큐 머리에 더 가까운 위치로 재큐잉돼요. 재큐잉된 메시지는 큐에서의 위치와 활성 컨슈머 채널의 prefetch 값에 따라 즉시 재전달 준비가 될 수 있어요. 즉, 일시적 상태 때문에 전달을 처리할 수 없어 모든 컨슈머가 재큐잉하면, 재큐잉/재전달 루프가 생겨요. 그런 루프는 네트워크 대역폭과 CPU 리소스 측면에서 비용이 커요. 컨슈머 구현은 재전송 횟수를 추적하고 메시지를 영구히 거부(폐기)하거나, 지연 후 재큐잉을 예약할 수 있어요.

basic.nack 메서드로 여러 메시지를 한 번에 거부·재큐잉할 수 있어요. 이것이 basic.reject와 다른 점이에요. basic.nack는 추가 파라미터 multiple을 받아요. Java 클라이언트 예시:

// this example assumes an existing channel instance

boolean autoAck = false;
channel.basicConsume(queueName, autoAck, "a-consumer-tag",
     new DefaultConsumer(channel) {
         @Override
         public void handleDelivery(String consumerTag,
                                    Envelope envelope,
                                    AMQP.BasicProperties properties,
                                    byte[] body)
             throws IOException
         {
             long deliveryTag = envelope.getDeliveryTag();
             // requeue all unacknowledged deliveries up to
             // this delivery tag
             channel.basicNack(deliveryTag, true, true);
         }
     });

.NET 클라이언트도 매우 비슷하게 동작해요.

채널 prefetch (QoS)

메시지는 클라이언트에 비동기로 전달(발송)되고, 채널에 한 번에 하나 이상의 메시지가 "in flight"일 수 있어요. 클라이언트의 수동 인정도 본질적으로 비동기지만 반대 방향으로 흘러요. 이는 미인정 전달의 슬라이딩 윈도우를 의미해요. 대부분의 컨슈머에게는 컨슈머 쪽의 무한 버퍼(힙) 성장 문제를 피하려고 이 윈도우 크기를 제한하는 게 말이 돼요. 이는 basic.qos 메서드로 "prefetch count" 값을 설정해 해요. 값은 채널에서 허용되는 최대 미인정 전달 수를 정의해요. 수가 설정된 값에 도달하면 RabbitMQ는 미해결 전달 중 하나라도 인정될 때까지 채널에 더 이상 메시지를 전달하지 않아요. 값 0은 "제한 없음"을 뜻해 채널에 미인정 메시지를 무제한 허용해요.

예를 들어 채널 Ch에 delivery tag 5, 6, 7, 8의 전달 4개가 미인정이고 채널 Ch의 prefetch count가 4로 설정돼 있으면, RabbitMQ는 미해결 전달 중 하나가 인정되기 전에는 Ch에 더 이상 전달을 푸시하지 않아요. 그 채널에 delivery_tag5(또는 6, 7, 8)인 인정 프레임이 도착하면 RabbitMQ가 알아차리고 메시지 하나를 더 전달해요. 여러 메시지를 한 번에 인정하면 그만큼 더 많은 메시지가 전달 가능해져요.

전달과 수동 클라이언트 인정의 흐름이 완전히 비동기라는 점을 다시 강조할게요. 따라서 in-flight 전달이 있는 동안 prefetch 값을 바꾸면 자연스러운 경쟁 조건이 생겨, 채널에 일시적으로 prefetch count보다 많은 미인정 메시지가 있을 수 있어요. AMQP 0-9-1 프로토콜에서 값 0은 "제한 없음"을 뜻해요. QoS 설정은 특정 채널이나 특정 컨슈머에 설정할 수 있어요. Consumer Prefetch 가이드가 이 범위 지정의 효과를 설명해요. QoS prefetch 설정은 수동 확인 모드에서도 basic.get("pull API")으로 가져온 메시지에는 효과가 없어요.

인정 모드·prefetch와 처리량

인정 모드와 QoS prefetch 값은 컨슈머 처리량에 큰 영향을 줘요. 일반적으로 prefetch를 늘리면 컨슈머로의 메시지 전달 속도가 개선돼요. 자동 인정 모드는 가능한 최고 전달 속도를 냅니다. 하지만 두 경우 모두 전달됐지만 아직 처리되지 않은 메시지 수가 늘어 컨슈머 RAM 소비가 증가해요.

자동 인정 모드나 무제한 prefetch의 수동 인정 모드는 주의해서 써야 해요. 인정 없이 메시지를 많이 소비하는 컨슈머는 연결된 노드의 메모리 소비 증가로 이어져요. 적절한 prefetch 값을 찾는 건 시행착오 문제이고 워크로드마다 달라요. 100~300 범위의 값이 보통 최적 처리량을 주면서 컨슈머를 압도할 심각한 위험이 없어요. 더 높은 값은 점점 더 수확 체감에 부딪히는 경우가 많아요. prefetch 값 1이 가장 보수적이에요. 특히 컨슈머 연결 지연이 높은 환경에서 처리량을 크게 줄여요. 많은 애플리케이션에 더 높은 값이 적절하고 최적이에요.

자동 재큐잉

수동 인정을 쓸 때, 인정되지 않은 전달(메시지)은 전달이 일어난 채널(또는 연결)이 닫히면 자동으로 재큐잉돼요. 여기에는 클라이언트의 TCP 연결 손실, 컨슈머 애플리케이션(프로세스) 실패, 채널 레벨 프로토콜 예외(아래에서 다룸)가 포함돼요. 사용 불가 클라이언트를 감지하는 데 시간이 걸린다는 점에 유의하세요. 이 동작 때문에 컨슈머는 재전송을 처리할 준비가 되어 있어야 하고, 그 외에도 멱등성(idempotence)을 염두에 두고 구현해야 해요. 재전송에는 RabbitMQ가 특별한 불리언 속성 redelivertrue로 설정해요. 첫 전달에는 false로 설정돼요. 컨슈머는 이전에 다른 컨슈머에게 전달됐던 메시지를 받을 수 있음에 유의하세요.

클라이언트가 같은 delivery tag를 두 번 이상 인정하면 RabbitMQ는 PRECONDITION_FAILED - unknown delivery tag 100 같은 채널 오류를 내요. 알 수 없는 delivery tag를 써도 같은 채널 예외가 발생해요. 브로커가 "unknown delivery tag"를 불평하는 또 다른 시나리오는, 전달을 받은 채널과 다른 채널에서 긍정·부정 인정을 시도할 때예요. 전달은 반드시 같은 채널에서 인정해야 해요.

퍼블리셔 확인 (Publisher Confirms)

네트워크는 예상치 못한 방식으로 실패하고 일부 실패 감지는 시간이 걸려요. 따라서 소켓에 프로토콜 프레임(예: 게시된 메시지)을 쓴 클라이언트는 그 메시지가 서버에 도달해 성공적으로 처리됐다고 가정할 수 없어요. 도중에 유실되거나 전달이 크게 지연될 수 있거든요. 표준 AMQP 0-9-1을 쓰는 한, 메시지가 유실되지 않음을 보장하는 유일한 방법은 트랜잭션을 쓰는 거예요. 채널을 트랜잭션으로 만들고, 각 메시지 또는 메시지 집합마다 게시하고 커밋하는 방식이에요. 하지만 이 경우 트랜잭션은 불필요하게 무겁고 처리량을 250배까지 떨어뜨려요. 이를 해결하기 위해 확인(confirmation) 메커니즘이 도입됐어요. 이는 프로토콜에 이미 있는 컨슈머 인정 메커니즘을 흉내 냅니다.

확인을 활성화하려면 클라이언트가 confirm.select 메서드를 보내요. no-wait 설정 여부에 따라 브로커가 confirm.select-ok로 응답할 수 있어요. 채널에 confirm.select 메서드가 쓰인 뒤부터 그 채널은 confirm 모드라고 불러요. 트랜잭션 채널은 confirm 모드로 넣을 수 없고, confirm 모드의 채널은 트랜잭션으로 만들 수 없어요.

채널이 confirm 모드가 되면 브로커와 클라이언트 모두 메시지를 세어요(첫 confirm.select에서 1부터 시작). 브로커는 메시지를 처리하면서 같은 채널에 basic.ack를 보내 메시지를 확인해요. delivery-tag 필드는 확인된 메시지의 시퀀스 번호를 담아요. 브로커는 basic.ackmultiple 필드를 설정해서 시퀀스 번호까지의 모든 메시지가 처리됐음을 나타낼 수 있어요.

예외적으로 브로커가 메시지를 성공적으로 처리할 수 없으면 basic.ack 대신 basic.nack를 보내요. 이 맥락에서 basic.nack의 필드는 해당 basic.ack의 필드와 같은 의미이고, requeue 필드는 무시해야 해요. 하나 이상의 메시지에 대해 nack 함으로써 브로커는 메시지를 처리할 수 없었고 책임을 거부한다고 나타내요. 그 시점에 클라이언트는 메시지를 재게시할 수 있어요. 채널이 confirm 모드가 된 후에 게시된 모든 메시지는 한 번 확인되거나 nack돼요. 메시지가 얼마나 빨리 확인되는지는 보장되지 않아요. 확인과 nack를 동시에 받는 메시지는 없어요. basic.nack는 큐를 담당하는 Erlang 프로세스에서 내부 오류가 발생할 때만 전달돼요.

라우팅 불가 메시지의 경우, 익스체인지가 메시지가 어떤 큐로도 라우팅되지 않음을(빈 큐 목록 반환) 확인하면 브로커가 확인을 발행해요. 메시지가 mandatory로도 게시됐으면 basic.returnbasic.ack보다 먼저 클라이언트에 보내져요. 부정 인정(basic.nack)도 마찬가지예요. 라우팅 가능 메시지의 경우 basic.ack는 메시지가 모든 큐에 수락되었을 때 보내져요. 내구성 큐로 라우팅되는 영속 메시지라면 이는 디스크에 기록함을 의미해요. quorum 큐에서는 쿼럼 복제본이 메시지를 수락해 선출된 리더에게 확인했다는 뜻이에요.

내구성 큐로 라우팅되는 영속 메시지의 basic.ack는 메시지가 디스크에 기록된 뒤에 보내져요. RabbitMQ 메시지 스토어는 fsync(2) 호출 수를 최소화하려고 일정 간격(수백 밀리초) 후 배치로 메시지를 디스크에 기록하거나, 큐가 유휴 상태일 때 기록해요. 이는 상시 부하에서 basic.ack의 지연이 수백 밀리초에 달할 수 있음을 뜻해요. 처리량을 높이려면 애플리케이션이 인정을 비동기로(스트림으로) 처리하거나 메시지 배치를 게시한 뒤 미해결 확인을 기다리는 걸 강력히 권장해요. 정확한 API는 클라이언트 라이브러리마다 달라요.

대부분의 경우 RabbitMQ는 퍼블리셔에게 메시지를 게시된 순서대로 확인해요(단일 채널에 게시된 메시지에 적용). 하지만 퍼블리셔 인정은 비동기로 발행되고 단일·그룹 메시지를 확인할 수 있어요. 확인이 발행되는 정확한 시점은 메시지의 전달 모드(영속 vs 일시)와 메시지가 라우팅된 큐들의 속성(위 참고)에 달려 있어요. 즉, 다른 메시지가 인정 준비가 되는 시점이 달라질 수 있고, 인정이 해당 메시지와 다른 순서로 도착할 수 있어요. 애플리케이션은 가능하면 인정의 순서에 의존하지 말아야 해요.

확인 미사용 시의 데이터 손실 시나리오

RabbitMQ 노드는 메시지가 디스크에 기록되기 전에 실패하면 영속 메시지를 잃을 수 있어요. 예를 들어 다음 시나리오를 생각해 보세요.

  1. 클라이언트가 영속 메시지를 내구성 큐에 게시해요
  2. 클라이언트가 큐에서 메시지를 소비하지만(메시지가 영속이고 큐가 내구성임을 알고 있음), 확인은 활성화되지 않았어요
  3. 브로커 노드가 실패하고 재시작돼요
  4. 클라이언트가 재연결하고 메시지 소비를 시작해요

이 시점에 클라이언트는 메시지가 다시 전달될 거라고 합리적으로 가정할 수 있어요. 하지만 그렇지 않아요. 재시작이 브로커가 메시지를 잃게 만들었거든요. 영속성을 보장하려면 클라이언트가 확인(confirms)을 사용해야 해요. 퍼블리셔의 채널이 confirm 모드였다면, 퍼블리셔는 유실된 메시지에 대한 ack를 받지 못했을 거예요(메시지가 아직 디스크에 기록되지 않았으므로).

delivery tag는 64비트 long 값이라 최댓값이 9223372036854775807이에요. delivery tag가 채널 범위이므로 실제로 퍼블리셔나 컨슈머가 이 값을 넘을 가능성은 매우 낮아요.

더 알아보기