메시지 처리량 극대화: 자동 승인 모드와 수동 승인 모드

RabbitMQ에서 최고의 메시지 처리량을 달성하려면 승인 모드를 마스터해야 합니다. 이 가이드는 자동 승인(Auto-Ack)과 수동 승인(Manual Acknowledgement) 전략을 비교하며, 자동 승인이 원시 속도를 위해 메시지 안전성을 희생하는 방식을 자세히 설명합니다. 대용량 시스템에서 중요한 전달 보장을 유지하면서 처리량을 극대화하는 데 있어 소비자 프리페치(QoS) 설정의 중요한 역할을 이해함으로써 실용적인 성능 튜닝을 배우세요.

메시지 처리량 극대화: 자동 승인 모드와 수동 승인 모드

RabbitMQ 승인 모드는 클라이언트 코드에서 작아 보이지만 운영에 큰 영향을 미치는 설정 중 하나입니다. 이 설정은 브로커가 메시지를 잊어도 되는 시점을 결정합니다. 그 선택은 처리량, 메모리 압력, 재시도, 중복 작업, 그리고 소비자가 처리 도중에 충돌할 때 발생하는 상황에 영향을 미칩니다.

간단히 말하면, 자동 승인은 RabbitMQ가 메시지가 전달되는 즉시 처리된 것으로 간주하기 때문에 빠릅니다. 수동 승인은 소비자가 처리가 성공했음을 RabbitMQ에 명시적으로 알리기 때문에 더 안전합니다. 대부분의 프로덕션 시스템은 수동 승인으로 시작하고, 자동 승인을 고려하기 전에 프리페치를 조정해야 합니다.

승인이 실제로 의미하는 것

승인은 비즈니스 영수증이 아닙니다. 이는 브로커 수준의 신호입니다. 소비자가 basic.ack를 보내면 RabbitMQ에 "이 전달은 큐에서 제거될 수 있습니다"라고 알리는 것입니다.

이 구분은 중요합니다. 소비자가 데이터베이스에 주문을 쓰고, 이메일을 보내고, 검색 인덱스를 업데이트하는 경우, 올바른 승인 시점은 일반적으로 작업의 내구성 있는 부분이 성공한 후입니다. 데이터베이스 커밋 전에 승인하고 프로세스가 충돌하면 RabbitMQ는 요청한 대로 정확히 수행한 것입니다: 메시지를 제거했습니다. 애플리케이션은 작업을 잃게 됩니다.

자동 승인

자동 승인을 사용하면 클라이언트가 자동 승인을 활성화한 상태로 구독합니다. RabbitMQ는 메시지를 보내고 즉시 성공적으로 전달된 것으로 처리합니다. 소비자는 나중에 basic.ack를 보내지 않습니다.

많은 클라이언트 라이브러리에서 이 설정은 consume에 대한 부울 값으로 나타납니다. 예를 들어, Java는 basicConsume에서 autoAck를 사용합니다. 여러 라이브러리가 약간 다른 이름으로 동일한 개념을 노출합니다.

매력은 분명합니다. 프로토콜 작업이 적고 부기(bookkeeping)가 줄어듭니다. 소비자는 RabbitMQ와 네트워크가 전달할 수 있는 만큼 빠르게 메시지를 수락할 수 있습니다. 원격 측정, 일시적인 진행 업데이트 또는 일회성 작업 부하의 경우 허용될 수 있습니다.

위험도 프로덕션에서 본 적이 있으면 분명합니다. 소비자가 만 개의 메시지를 수신한 다음 인메모리 버퍼를 처리하기 전에 충돌하면 해당 메시지는 큐에서 사라집니다. RabbitMQ는 이미 자동으로 승인되었기 때문에 해당 메시지를 재전달할 수 없습니다.

자동 승인은 메시지가 중요하지 않거나, 재생성될 수 있거나, 오래된 데이터가 유용하지 않은 라이브 스트림을 나타내는 경우에 합리적입니다. 예를 들어, 최선 노력 메트릭, UI 존재 업데이트, 또는 별도의 내구성 있는 파이프라인이 기록 소스인 로그 스타일 이벤트가 있습니다. 결제, 주문, 재고 변경, 계정 업데이트 또는 누락된 메시지가 수동 정리를 초래하는 작업에는 적합하지 않습니다.

수동 승인

수동 승인을 사용하면 RabbitMQ는 소비자가 응답할 때까지 전달된 메시지를 미승인 상태로 유지합니다. 소비자 연결이 승인 전에 종료되면 RabbitMQ는 해당 미승인 메시지를 다시 큐에 넣고 다시 전달할 수 있습니다.

이 동작이 중요한 작업의 일반적인 기본값으로 수동 승인이 사용되는 이유입니다. 이것이 정확히 한 번 처리를 의미하지는 않습니다. 메시지가 처리된 다음 소비자가 승인을 보내기 전에 충돌할 수 있습니다. RabbitMQ는 이를 재전달하며, 애플리케이션은 동일한 논리적 작업을 두 번 볼 수 있습니다. 수동 승인은 최소 한 번 전달을 제공하므로, 중복 부작용이 문제가 되는 경우 핸들러는 여전히 멱등성이 필요합니다.

안전한 소비자 루프는 일반적으로 다음 형태를 따릅니다:

메시지 수신
페이로드 검증
내구성 있는 작업 수행
데이터베이스 트랜잭션 또는 외부 부작용 커밋
메시지 승인

실패의 경우 메시지를 재시도, 지연 또는 데드 레터링할지 결정합니다. 모든 실패를 즉시 재큐잉하면 동일한 잘못된 메시지가 하루 종일 CPU를 소모하는 핫 루프가 생성될 수 있습니다. 데드 레터 익스체인지, 재시도 큐 또는 지연된 재시도 패턴이 종종 더 좋습니다.

프리페치가 실제 처리량 레버입니다

많은 팀이 자동 승인과 수동 승인을 비교하고, 기본 설정으로 수동 승인이 더 느리다는 것을 보고 잘못된 결론을 내립니다. 누락된 부분은 프리페치입니다.

basic.qos로 구성된 RabbitMQ 프리페치는 소비자가 한 번에 보유할 수 있는 미승인 메시지 수를 제한합니다. 수동 승인 및 prefetch=1을 사용하면 소비자는 하나의 메시지를 수신하고, 처리하고, 승인한 다음에야 다른 메시지를 받습니다. 이는 안전하지만, 동시에 처리하거나 작은 로컬 버퍼를 허용할 수 있는 작업자의 처리량을 낭비합니다.

더 높은 프리페치를 사용하면 RabbitMQ가 소비자를 계속 바쁘게 유지할 수 있습니다:

prefetch = worker_concurrency * expected_work_buffer

작업자가 8개의 작업을 동시에 처리하는 경우 프리페치 16 또는 32가 합리적인 시작점입니다. 각 메시지가 크거나 처리가 메모리 집약적인 경우 더 낮게 시작하십시오. 각 메시지가 작고 처리가 주로 네트워크 I/O인 경우 더 높은 숫자가 도움이 될 수 있습니다.

모든 서비스에 무작위로 250의 프리페치를 복사하지 마십시오. 높은 프리페치는 불균등한 분포를 초래할 수 있습니다. 한 소비자가 큰 배치를 수신하고 다른 소비자가 유휴 상태인 동안 이를 보유할 수 있습니다. 또한 소비자가 죽을 때 재전송 버스트를 증가시킵니다. RabbitMQ는 해당 연결에서 모든 미승인 전달을 다시 큐에 넣어 다른 작업자가 갑자기 큰 백로그를 상속받게 할 수 있습니다.

처리량과 안전성 간의 트레이드오프

다음은 실용적인 비교입니다:

모드 RabbitMQ가 하는 일 강점 주요 위험
자동 승인 전달 시 메시지 제거 가장 높은 원시 전달 속도 소비자 충돌 시 작업 손실
수동 승인, 낮은 프리페치 더 많이 보내기 전에 각 승인 대기 간단한 실패 동작 소비자 활용도 저하
수동 승인, 조정된 프리페치 제어된 수의 메시지를 전송 중 유지 복구와 함께 좋은 처리량 멱등성 핸들러 및 재시도 설계 필요

중요한 세부 사항은 수동 승인이 느릴 필요가 없다는 것입니다. 제대로 조정되지 않은 수동 승인은 느립니다. 합리적인 프리페치, 동시 작업자 및 짧은 데이터베이스 트랜잭션을 사용한 수동 승인은 복구 동작을 유지하면서 심각한 볼륨을 처리할 수 있습니다.

구체적인 튜닝 워크플로우

수동 승인과 보수적인 프리페치로 시작하십시오:

prefetch = 작업자 스레드당 1~4

소비자 활용도, 큐 깊이, 메시지 처리 시간, 메모리 및 재전송을 측정하십시오. 큐에 메시지가 있는 동안 소비자가 유휴 상태이면 프리페치를 높이십시오. 메모리가 증가하거나 한 소비자가 작업을 독점하면 낮추십시오. 재전송이 급증하면 프리페치를 다시 변경하기 전에 충돌, 시간 초과 및 nack 동작을 검사하십시오.

브로커도 관찰하십시오. 높은 처리량은 단지 소비자 숫자만이 아닙니다. 디스크 I/O, 게시자 확인, 큐 유형, 메시지 크기, 내구성, 미러링 또는 쿼럼 복제, 네트워크 대역폭이 모두 결과에 영향을 미칩니다. 승인 모드는 더 큰 시스템의 하나의 레버입니다.

오류 처리가 플래그보다 중요합니다

실패 계획이 없는 수동 승인 소비자는 반쯤 완성된 것입니다. 성공 시 승인하십시오. 일시적 실패 시 즉시 재시도가 합리적인 경우에만 nack 및 재큐잉하십시오. 포이즌 메시지의 경우 거부하거나 재큐잉 없이 nack하고 구성된 경우 데드 레터 익스체인지로 라우팅하십시오.

또한 소비자의 기본 큐 외부에 최대 재시도 정책을 설정하십시오. RabbitMQ는 설계가 헤더, 재시도 큐 또는 애플리케이션 상태를 통해 시도를 추적하지 않는 한 잘못된 형식의 JSON 메시지가 5번 실패했음을 마법처럼 알지 못합니다.

기본적으로 선택할 것

비즈니스 이벤트 및 백그라운드 작업의 경우 수동 승인을 사용하십시오. 작업자 동시성 및 메모리에 따라 프리페치를 조정하십시오. 핸들러를 멱등성으로 만드십시오. 잘못된 메시지가 끝없는 즉시 재시도가 왜 고통스러운지 가르쳐주기 전에 데드 레터링을 추가하십시오.

손실이 허용 가능하고 문서화된 경우에만 자동 승인을 사용하십시오. 해당 문장은 인시던트 검토 중에 방어하기 쉬워야 합니다. 팀이 전달되었지만 처리되지 않은 메시지가 사라진 것을 발견했을 때 화가 날 것 같다면 자동 승인은 잘못된 설정입니다.

메시지 크기에 따라 답이 달라집니다

2KB 메시지에 완벽하게 작동하는 프리페치 값은 5MB 메시지에 무모할 수 있습니다. 프리페치는 개수를 제어하며 총 바이트를 제어하지 않습니다. 한 소비자가 100개의 미승인 메시지를 보유할 수 있고 각 메시지가 큰 경우 로컬 메모리 사용량이 빠르게 증가할 수 있습니다. 브로커는 또한 해당 전달이 승인될 때까지 추적해야 합니다.

메시지가 큰 경우 낮은 프리페치로 시작하고 소비자 프로세스의 상주 메모리를 측정하십시오. 가능하면 메시지 본문을 작게 유지하고 대용량 페이로드는 객체 스토리지와 같은 다른 곳에 저장하고 메시지는 참조와 체크섬을 전달하십시오. 이 설계가 항상 적절한 것은 아니지만 브로커가 대용량 파일 전송 수단이 되는 것을 방지합니다.

배치 승인으로 프로토콜 채팅을 줄일 수 있습니다

많은 클라이언트 라이브러리에서 multiple 플래그를 사용하여 한 번의 승인으로 여러 전달을 승인할 수 있습니다. 이는 소비자가 메시지를 순서대로 처리하고 안전하게 전달 태그 범위를 승인할 수 있을 때 프로토콜 오버헤드를 줄일 수 있습니다.

문제는 실패 처리입니다. 메시지를 동시에 처리하는 경우 전달 태그 순서가 완료 순서와 일치하지 않을 수 있습니다. 최신 메시지가 성공했기 때문에 여러 메시지를 승인하면 아직 실행 중이거나 실패한 이전 메시지가 실수로 승인될 수 있습니다. 동시 작업자의 경우 메시지별 승인이 종종 더 간단하고 안전합니다.

유용한 규칙: 소비자의 처리 모델이 승인에 포함된 메시지를 정확히 설명할 수 있을 정도로 정렬된 경우에만 배치 승인을 사용하십시오.

인시던트 중 미승인 메시지 관찰

RabbitMQ는 준비 및 미승인 메시지 수를 노출합니다. 준비된 메시지가 많은 큐는 소비자가 따라잡지 못하거나 연결되지 않았음을 의미합니다. 미승인 메시지가 많은 큐는 RabbitMQ가 소비자에게 작업을 전달했지만 아직 승인을 받지 못했음을 의미합니다.

두 번째 경우는 소비자 행동(느린 처리, 막힌 외부 호출, 너무 높은 프리페치, 차단된 스레드 또는 예외 후 승인을 중단한 소비자)을 가리킵니다. 게시자가 소비자가 수신할 수 있는 것보다 빠르게 큐를 넘치는 것과는 다릅니다.

관리 UI 또는 rabbitmqctl을 사용하여 다음을 확인하십시오:

rabbitmqctl list_queues name messages_ready messages_unacknowledged consumers

messages_unacknowledged가 높고 소비자가 살아 있으면 브로커 설정을 변경하기 전에 소비자 로그 및 스레드 덤프를 확인하십시오. 브로커는 단순히 애플리케이션이 작업을 완료하기를 기다리고 있을 수 있습니다.

재전송은 정상이지만, 반복된 재전송은 문제의 징후입니다

수동 승인은 소비자 실패 후 메시지가 재전송될 수 있음을 의미합니다. 이는 예상됩니다. 원하지 않는 것은 동일한 포이즌 메시지가 전달되고, 실패하고, 재큐잉되고, 영원히 다시 전달되는 것입니다.

재시도를 진단하기에 충분한 메타데이터를 추가하십시오. 일부 팀은 헤더를 사용하여 시도를 추적합니다. 다른 팀은 실패를 재시도 익스체인지로 이동한 다음 제한 후 데드 레터 큐로 이동합니다. 정확한 패턴은 다양하지만 운영 목표는 동일합니다: 일시적 실패는 또 다른 기회를 얻고, 영구적 실패는 가시화되어 유용한 작업을 차단하지 않습니다.

핸들러가 멱등성이 아닌 경우 재전송은 위험해집니다. 작업자가 카드를 청구한 다음 승인하기 전에 충돌한다고 가정해 보십시오. RabbitMQ는 메시지를 재전송합니다. 핸들러가 다시 청구하면 브로커가 버그를 만든 것이 아닙니다. 누락된 멱등성 키를 드러낸 것입니다. 외부 부작용의 경우 내구성 있는 작업 ID를 저장하고 부작용을 반복해도 안전하게 만드십시오.

게시자 확인은 별도의 문제입니다

소비자 승인은 RabbitMQ에 소비자가 전달을 처리했음을 알립니다. 게시자 확인은 게시자에게 RabbitMQ가 게시된 메시지를 수락했음을 알립니다. 이들은 흐름의 반대쪽을 해결합니다.

시스템은 수동 소비자 승인을 사용하면서도 게시자가 확인 없이 발송 후 망각(fire-and-forget)하고 연결이 잘못된 순간에 끊어지면 게시 시간에 메시지를 잃을 수 있습니다. 마찬가지로 게시자 확인은 소비자가 메시지를 수신한 후의 작업을 보호하지 않습니다. 안정적인 파이프라인의 경우 비즈니스 사례가 요구하는 곳에서 둘 다 사용하십시오: 게시 측에서는 확인, 소비 측에서는 수동 승인, 적절한 경우 내구성 있는 큐, 애플리케이션 계층에서 멱등성 처리.

큐 유형 및 내구성이 동일한 처리량 논의에 영향을 미칩니다

승인 모드는 단독으로 존재하지 않습니다. 비영구적 메시지가 있는 일시적 클래식 큐는 영구적 메시지가 있는 내구성 있는 쿼럼 큐와 다른 성능 및 안전성 프로필을 가집니다. 일회용 큐에서 자동 승인을 벤치마킹한 다음 결과를 내구성 있는 프로덕션 큐에 적용하면 비교가 유용하지 않습니다.

중요한 작업 부하의 경우 내구성 있는 큐와 영구적 메시지가 일반적이지만 디스크 및 복제 작업이 추가됩니다. 쿼럼 큐는 이전 미러링된 클래식 큐 패턴에 비해 데이터 안전성을 향상시키지만 처리량 특성도 변경합니다. 실제로 실행하는 큐 유형을 측정하십시오.

공정한 테스트는 이러한 변수를 안정적으로 유지합니다:

동일한 메시지 크기
동일한 큐 유형
동일한 내구성 설정
동일한 게시자 확인 동작
동일한 소비자 수
동일한 프리페치
동일한 다운스트림 처리

한 번에 하나의 레버만 변경하십시오. 그렇지 않으면 결과가 승인 모드, 프리페치, 큐 유형, 메시지 크기 또는 소비자 코드에서 비롯되었는지 알 수 없습니다.

소비자 동시성은 작업과 일치해야 합니다

각 메시지가 대부분의 시간을 HTTP 또는 데이터베이스에서 대기하는 데 보내는 경우 소비자는 동시 처리의 이점을 얻을 수 있습니다. 각 메시지가 CPU 집약적인 경우 너무 많은 동시성은 모든 메시지를 느리게 만들 수 있습니다. 프리페치는 그 현실을 따라야 합니다.

단일 스레드 소비자의 경우 프리페치 100은 단순히 큰 로컬 대기실을 만들 수 있습니다. 20개의 활성 처리 슬롯이 있는 작업자의 경우 프리페치 40이 해당 슬롯을 계속 공급할 수 있습니다. 4개의 코어가 있는 CPU 바운드 프로세스의 경우 동시성 100은 처리량을 개선하지 않고 컨텍스트 스위칭을 증가시킬 수 있습니다.

큐 깊이뿐만 아니라 소비자 내부의 처리 시간을 측정하십시오. 수신 시간, 시작 시간, 완료 시간, 승인 시간, 실패 이유 및 재전송 플래그에 대한 로그 또는 메트릭을 추가하십시오. 이러한 타임스탬프는 작업이 RabbitMQ에서 대기 중인지, 소비자 내부에서 대기 중인지, 아니면 다운스트림 시스템에서 막혀 있는지 훨씬 쉽게 알 수 있게 해줍니다.