본문 바로가기

Kafka, RabbitMQ

RabbitMQ에 대해 알아보자 - (3) 메시지 처리에 실패하게 된다면?

들어가기 앞서

RabbitMQ를 구성해 보고 그 과정에 대해 느낀 부분을 작성하려고 합니다. RabbitMQ 뿐만 아니라 Kafka와 같이 비동기 메시지 시스템의 가장 중요한 부분은 오류나 장애를 어떻게 처리할 것인지가 중요한 부분이라고 생각합니다. 이러한 부분을 중점으로 RabbitMQ에 대해 알아 보려 합니다.

 

혹시 DLQ, DLX, PLQ에 대한 내용을 보기 위해 들어 오신 분들은 아래 링크를 추천드립니다.

RabbitMQ에 대해 알아보자 - (4) 메시지 실패 처리 DLQ, DLX, PLQ

 

자세한 코드는 제 깃허브 링크에서 확인해 주시면 감사하겠습니다.


ACK (Acknowledgment)

 

RabbitMQ에서 ACK(Acknowledgment)는 메시지를 소비자(Consumer) 정상적으로 처리했음을 브로커에게 알려주는 메커니즘입니다.

  • 메시지가 큐에서 소비자에게 전달되었다고 해서 바로 큐에서 삭제되지 않습니다.
  • 소비자가 "나 이 메시지 잘 처리했어" 하고 명시적으로 RabbitMQ에게 ACK 신호를 보내야 큐에서 삭제 됩니다.
  • 이러한 ACK 메커니즘을 통해 메시지 손실 방지를 보장할 수 있어.
    • 소비자가 처리 중에 죽거나 오류가 나면, ACK를 보내지 않았기 때문에 브로커는 해당 메시지를 다시 다른 소비자에게 줄 수 있습니다.

 

RabbitMQ에서는 브로커에세 ACK신호를 보내도록 설정할 수 있는 방법이 두 개가 있습니다. 

Manual Acknowledge (수동 확인) 소비자가 직접 "처리 완료" 후에 ACK를 명시적으로 보냄. 실패할 경우 재전송(재처리)이 가능함. 
Auto Acknowledge (자동 확인) 메시지를 받자마자 즉시 ACK를 보냄. 소비자가 메시지를 성공적으로 처리했는지는 신경 쓰지 않음. 실패하면 메시지가 그냥 사라짐. 때문에 메시지는 복구할 수 없다.

 

 

하지만 Spring에서는 조금 다르게 동작하는 것 같습니다. 스프링 공식 문서를 확인해 보면 아래 처럼 설명하고 있습니다.

스프링 공식 문서 ACK 설정

AUTO  리스너 메서드가 정상 종료되면 ACK, 예외가 발생하면 NACK를 전송합니다. Spring 에서 Default 설정 값 입니다.
MANUAL 개발자가 수동으로 ACK/NACK를 처리합니다.
NONE ACK를 보내지 않습니다. 메시지를 받자마자 큐에서 제거됩니다.

 

 


컨슈머가 메세지 처리를 실패 했을 경우

 

메세지가 정상적으로 처리되고 있을 때

위에 관리자 페이지의 사진은 정상적으로 메시지를 처리하고 있을때 입니다. 만약 장애로 인해 컨슈머가 메시지를 올바르게 처리하지 못하면 어떻게 될까요?

 

컨슈머가 메시지 처리중에 장애가 발생했을 떄

제가 컨슈머(Consumer)에서 일부러 장애가 발생하도록 잘못된 형식으로 3개의 메시지를 발행하였습니다. 사진에 보면 이전과 다르게 'Total'과 'Unacked'의 수치가 메시지 수 만큼 증가한 것을 볼 수 있습니다. 

 

ready 큐에 쌓여 있고 아직 어떤 컨슈머에도 전달되지 않은 메시지 상태. 컨슈머가 연결되면 처리됨.
unacked 컨슈머에게 전달되었지만 아직 ACK(확인 응답)를 받지 못한 상태. 컨슈머가 처리 중이거나 예외로 인해 처리에 실패했을 수 있음.

 

 

컨슈머(Consumer)에서 메시지가 처리 중 예외 발생하면 ACK을 브로커로 보내지 않습니다. 또한 메시지는 unacked 상태가 됩니다. 이렇게 메시지가 unacked 상태가 되면 애플리케이션을 계속 재실행을 하더라도 장애는 계속 발생합니다.

 

 

1. 장애 대응 1단계 : 컨슈머에서 메시지가 정상적으로 처리 될 때 까지 무한 에러 발생

 

2. 장애 대응 2단계 : 애플리케이션 종료 및 재실행

 

3. 장애 대응 3단계 : 여전히 계속 장애 발생

 

여기서 서버를 껐다 켜도 지속적으로 장애가 나는 이유는 다음과 같습니다.

  1. 컨슈머가 메시지 처리 도중 장애가 발생하면 unacked 상태가 된다.
  2. 애플리케이션을 종류하면, 컨슈머가 종료되어 unacked → ready 상태가 된다.  
  3. 애플리케이션을 실행하면, 큐에 있던 메시지가 다시 컨슈머에 할당되고, 다시 동일한 장애가 발생하여 unacked 상태가 된다.
  4. 애플리케이션을 종류하면, 컨슈머가 종료되어 unacked → ready 상태가 된다. 

즉, Consumer에서 메시지 처리가 실패하면 RabbitMQ에서 자동으로 해결되지 않아 무한 재시도를 할 가능성이 있습니다. 따라서 코드를 수정 후 재배포를 하거나  Spring에서 설정(defaultRequeueRejected)을 통해서 해결해야 합니다.

 

혹은 unacked 메시지를 제거하기 위해서는 컨슈머의 연결을 종료하면 unacked → ready 상태로 변경되게 됩니다. 이때 ready 상태가 되면 purge를 통해 메시지를 제거할 수 있습니다.

 


큐에서 메세지 발송에 실패 했을 경우

큐에서 메세지 전송을 실패 했을 경우

 

Ready에 값이 있다는 것은, 큐에 메세지가 쌓여 있고 아직 어떤 컨슈머에도 전달되지 않은 메시지 상태로 컨슈머가 연결되면 처리된다는 의미 입니다.

 

만약 Ready의 메세지를 강제로 처리하고 싶다면 Purge를 사용할 수 있습니다.

 

purge를 수행하고난 다음, Ready의 값이 사라진 것을 볼 수 있습니다. 즉, 큐에서 그동안 처리되지 못한 메시지가 제거 된 것을 알 수 있습니다.

 

purge는 ready 상태의 메시지만 삭제합니다. unacked 메시지는 purge의 대상이 아닙니다. unacked 메시지를 제거하고 싶다면, 컨슈머를 종료하여 메시지를 ready 상태로 변경하고, purge를 통해 메시지를 제거할 수 있습니다.

 

ready 많음 컨슈머 수를 늘리거나, 컨슈머 처리 속도 개선
unacked 많음 컨슈머 코드에서 예외 발생 가능성 점검, 메시지 정상 처리 후 ACK 보내도록 수정, 컨슈머 재시작으로 ready 상태로 전환 유도