소비자가 큐에서 메시지를 가져간 직후에 죽으면, 아직 처리하지 못한 그 메시지는 어떻게 되는 걸까?

ACK와 NACK — 메시지 신뢰성 보장의 핵심

왜 메시지 확인이 필요한가

RabbitMQ에서 소비자가 큐로부터 메시지를 가져왔다고 해서 처리가 완료된 것은 아닙니다. 메시지를 가져온 직후 소비자에 장애가 발생하면, 아직 처리되지 않은 메시지가 사라질 수 있습니다.

이러한 메시지 손실을 방지 하기 위해 RabbitMQ는 ACK/NACK 메커니즘을 제공합니다. 소비자가 메시지 처리 결과를 브로커에 명시적으로 알려줌으로써, 브로커는 메시지를 안전하게 삭제할지 또는 재전달할지를 판단할 수 있습니다.

**핵심 원리 **: 브로커는 소비자의 확인 신호를 받기 전까지 메시지를 큐에서 삭제하지 않습니다. 이를 통해 장애 상황에서도 메시지가 유실되지 않도록 보장합니다.


ACK와 NACK란

신호의미용도
ACK (Acknowledgement)정상 처리 확인소비자가 메시지를 성공적으로 처리했음을 브로커에 알림
NACK (Negative Acknowledgement)처리 실패 알림메시지 처리에 실패했음을 브로커에 알리고, 재전달 또는 폐기를 요청
  • ACK 를 수신한 브로커는 해당 메시지를 큐에서 안전하게 삭제합니다.
  • NACK 를 수신한 브로커는 설정에 따라 메시지를 큐에 재적재(requeue=true)하거나 폐기(requeue=false)합니다.

Acknowledgement Mode 비교

RabbitMQ에서는 소비자가 메시지 처리 결과를 브로커에 알리는 방식을 두 가지로 나눕니다.

비교 항목Auto-ackManual-ack
ACK 전송 시점메시지 수신 즉시 자동 전송비즈니스 로직 처리 후 명시적 전송
** 신뢰성**낮음 (처리 중 오류 시 메시지 손실 가능)높음 (처리 완료 후에만 삭제)
** 성능**높음 (별도 확인 작업 없음)상대적으로 낮음 (확인 신호 전송 오버헤드)
** 적합한 시나리오**로그 수집 등 손실 허용 데이터결제, 주문 등 손실 불가 데이터
** 장애 시 동작**메시지 유실 가능메시지 재전달 가능

Auto-ack 모드

메시지를 수신하는 즉시 자동으로 ACK가 전송됩니다. 별도의 확인 작업이 없어 처리 속도는 빠르지만, ** 비즈니스 로직 처리 중 오류가 발생해도 브로커는 이미 메시지를 처리 완료로 간주 **합니다.

Auto-ack는 로그 메시지, 통계 데이터 등 ** 재처리가 필요 없는 비중요 데이터 **에 적합합니다.

Manual-ack 모드

소비자가 비즈니스 로직을 성공적으로 처리한 후에만 명시적으로 ACK를 전송합니다. ** 메시지 처리가 확실히 끝난 뒤에만 큐에서 삭제 **되므로 신뢰성이 높고, 장애 발생 시 메시지를 재처리하여 데이터 손실을 방지할 수 있습니다.

Manual-ack는 결제, 주문 등 ** 데이터 손실이 허용되지 않는 비즈니스 **에 적합합니다.


코드 예시: Manual-ack 모드

JAVA
import com.rabbitmq.client.*;

public class AckNackExample {
    public static void main(String[] args) throws Exception {
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost("localhost");

        try (Connection connection = factory.newConnection();
             Channel channel = connection.createChannel()) {

            // Manual-ack 모드 설정: autoAck = false
            channel.basicConsume("queueName", false, (consumerTag, delivery) -> {
                String message = new String(delivery.getBody(), "UTF-8");
                long deliveryTag = delivery.getEnvelope().getDeliveryTag();

                boolean isProcessed = doSomething(message);
                if (isProcessed) {
                    // 처리 성공: ACK 전송 → 브로커가 메시지를 큐에서 삭제
                    channel.basicAck(deliveryTag, false);
                } else {
                    // 처리 실패: NACK 전송 → requeue=true로 큐에 재적재
                    channel.basicNack(deliveryTag, false, true);
                }
            }, consumerTag -> {});
        }
    }
}

위 코드의 핵심은 basicConsume의 두 번째 파라미터 autoAckfalse로 설정하여 Manual-ack 모드를 활성화하는 것입니다. 이후 처리 결과에 따라 basicAck 또는 basicNack를 명시적으로 호출합니다.


basicAck / basicNack 파라미터 상세

basicAck

JAVA
channel.basicAck(long deliveryTag, boolean multiple);
파라미터타입설명
deliveryTaglong메시지의 고유 식별자. 브로커가 어떤 메시지에 대한 ACK인지 식별
multiplebooleantrue: 해당 deliveryTag 이하의 모든 미확인 메시지 를 일괄 ACK. false: 해당 메시지만 ACK

basicNack

JAVA
channel.basicNack(long deliveryTag, boolean multiple, boolean requeue);
파라미터타입설명
deliveryTaglong메시지의 고유 식별자
multiplebooleantrue: 해당 deliveryTag 이하의 모든 미확인 메시지를 일괄 NACK. false: 해당 메시지만 NACK
requeuebooleantrue: 메시지를 큐에 재적재하여 다른 소비자가 처리. false: 메시지를 폐기 (DLX가 설정되어 있으면 DLQ로 이동)

**주의 **: requeue=true로 설정하면 동일한 메시지가 무한 반복 처리될 수 있습니다. 이를 방지하려면 재시도 횟수를 제한하거나, requeue=false와 함께 DLX(Dead Letter Exchange)를 활용하는 것이 좋습니다.


요약

핵심 개념설명
ACK메시지 정상 처리 확인 → 브로커가 큐에서 삭제
NACK메시지 처리 실패 알림 → 재적재 또는 폐기
Auto-ack수신 즉시 자동 확인 (빠르지만 손실 위험)
Manual-ack처리 완료 후 명시적 확인 (신뢰성 높음)
multiple여러 메시지 일괄 확인으로 네트워크 효율 향상
requeue실패 메시지 재처리 여부 결정

주의할 점

requeue=true의 무한 루프 함정

basicNack에서 requeue=true로 설정하면 실패한 메시지가 큐에 다시 들어갑니다. 같은 소비자가 다시 꺼내서 또 실패하면? 무한 반복입니다. 반드시 재시도 횟수를 제한하거나, requeue=false와 DLX를 조합해서 실패 메시지를 별도 큐로 보내야 합니다.

ACK를 보내지 않으면 메모리가 터진다

Manual-ack 모드에서 ACK도 NACK도 보내지 않으면, 브로커는 해당 메시지를 "처리 중"으로 간주하고 큐에서 삭제하지 않습니다. unacked 메시지가 쌓이면 브로커 메모리가 고갈됩니다. prefetch count를 설정해서 한 번에 처리할 메시지 수를 제한하는 것이 필수입니다.

Prefetch Count — 소비자 과부하 방지

Prefetch Count(basicQos)는 ** 소비자가 ACK를 보내기 전까지 브로커가 전달할 수 있는 최대 미확인 메시지 수 **를 제한하는 설정입니다.

JAVA
// 한 번에 최대 10개의 메시지만 전달받도록 제한
channel.basicQos(10);
Prefetch Count동작적합한 상황
0 (기본값)제한 없음 — 브로커가 가능한 모든 메시지를 한꺼번에 전달거의 사용하지 않음 (메모리 위험)
1메시지 하나씩 전달 — ACK 후 다음 메시지 수신처리 시간이 긴 작업, 공정한 분배
10~50적당한 배치 단위로 전달일반적인 실무 환경

Prefetch Count를 설정하지 않으면 브로커가 큐의 메시지를 한꺼번에 소비자에게 밀어넣어, 소비자 메모리가 고갈되거나 다른 소비자가 놀게 됩니다. Manual-ack 모드에서는 ** 반드시 basicQos를 함께 설정 **해야 합니다.


신뢰성이 중요한 시스템에서는 Manual-ack 모드를 기본으로 사용 하고, 처리 실패 시 requeue와 DLX를 조합하여 메시지 유실 없는 안정적인 처리 파이프라인을 구축하는 것을 권장합니다.

**공식 문서 참고 **: Consumer Acknowledgements and Publisher Confirms

댓글 로딩 중...