RabbitMQ 프리페치 설정 마스터하기: 최적의 소비자 성능을 위한 가이드
RabbitMQ 프리페치를 조정하여 소비자가 메시지를 독점하거나 느린 처리를 숨기지 않으면서도 바쁘게 유지하는 방법을 알아보세요.
RabbitMQ 프리페치 설정 마스터하기: 최적의 소비자 성능을 위한 가이드
RabbitMQ 프리페치는 작아 보이지만 모든 것을 바꾸는 설정 중 하나입니다. 이는 RabbitMQ가 한 번에 소비자가 보유할 수 있는 확인되지 않은 메시지의 수를 제어합니다. 너무 낮게 설정하면 빠른 소비자가 다음 전달을 기다리는 데 너무 많은 시간을 소비합니다. 너무 높게 설정하면 느린 소비자가 조용히 작업을 독점하고, 지연 시간을 증가시키며, 큐 깊이 그래프를 거짓으로 만듭니다.
프리페치를 생각하는 유용한 방법은 미완료 작업입니다. 프리페치가 20이면 소비자가 20개의 메시지를 전달받았지만 아직 확인하지 않았음을 의미합니다. 해당 메시지는 더 이상 큐에서 ready 상태가 아닙니다. 소비자가 ack, nack, reject 또는 연결을 끊을 때까지 소비자와 함께 unacked 상태로 있습니다.
즉, 프리페치는 단순한 처리량 조절기가 아닙니다. 공정성, 메모리, 장애 복구를 조절하는 도구입니다.
RabbitMQ에서 basic.qos의 역할
소비자는 basic.qos로 프리페치를 설정합니다. 대부분의 클라이언트 라이브러리에서 prefetch_count를 설정합니다. prefetch_size는 거의 사용되지 않으며 일반적으로 0으로 남겨둡니다.
Python(Pika) 예시:
channel.basic_qos(prefetch_count=10)
channel.basic_consume(
queue="jobs",
on_message_callback=handle_message,
auto_ack=False,
)
Node.js(amqplib) 예시:
await channel.prefetch(10);
await channel.consume("jobs", async (msg) => {
try {
await handleMessage(msg.content);
channel.ack(msg);
} catch (err) {
channel.nack(msg, false, false);
}
}, { noAck: false });
수동 확인이 중요합니다. 자동 확인을 사용하면 RabbitMQ는 메시지가 전달되는 즉시 완료된 것으로 간주합니다. 프리페치는 더 이상 동일한 방식으로 처리 신뢰성을 보호하지 않습니다. 관리할 미확인 창이 없기 때문입니다.
RabbitMQ는 AMQP의 원래 표현이 채널 지향적이지만, 현대 사용에서는 기본적으로 소비자별로 프리페치를 적용합니다. 일부 클라이언트는 global 플래그를 노출합니다. 주의해서 사용하세요. 공유 채널 또는 연결 전체 제한은 소비자 간에 혼란스러운 상호 작용을 만들 수 있습니다. 대부분의 서비스는 각 소비자가 자체 채널과 자체 프리페치 수를 가질 때 추론하기 더 쉽습니다.
프리페치가 지연 시간을 변경하는 이유
두 명의 소비자가 있는 큐를 상상해보세요. 소비자 A는 100개의 메시지 배치를 받은 다음 느린 외부 API를 호출합니다. 소비자 B는 건강하고 빠르지만, 그 100개의 메시지는 이미 A에 할당되었습니다. RabbitMQ는 A가 메시지를 거부하거나 채널이 닫히지 않는 한 B에게 메시지를 제공하지 않습니다.
큐의 관점에서 해당 메시지는 준비되지 않았습니다. 사용자의 관점에서 지연됩니다. 이것이 높은 프리페치가 브로커 그래프에서는 시스템을 더 좋게 보이게 하지만 실제 지연 시간을 악화시킬 수 있는 이유입니다.
낮은 프리페치는 RabbitMQ가 작업을 더 공정하게 분배할 기회를 더 많이 제공합니다. 높은 프리페치는 소비자에게 더 많은 로컬 작업과 더 적은 브로커 왕복을 제공합니다. 어느 쪽도 항상 올바른 것은 아닙니다.
합리적인 시작 값
느린 작업의 경우 작게 시작하세요. 각 메시지가 타사 API를 호출하거나, 여러 데이터베이스 행을 쓰거나, CPU 집약적인 변환을 수행하는 경우 prefetch_count=1에서 10을 시도하세요. 실패하거나 느린 소비자가 소량의 작업만 보유하도록 하려는 것입니다.
수십 또는 수백 밀리초가 걸리고 안정적인 작업자에서 실행되는 중간 작업의 경우 10, 20 또는 50과 같은 값이 일반적인 시작점입니다. 더 높이 올리기 전에 측정하세요.
매우 빠른 핸들러의 경우 브로커와 소비자가 저지연 네트워크에 있을 때 더 높은 프리페치가 왕복을 줄이고 처리량을 향상시킬 수 있습니다. 그렇더라도 벤치마크가 5분 동안 좋아 보였다고 해서 엄청난 숫자를 선택하지 마세요. 소비자 메모리와 꼬리 지연 시간을 주시하세요.
간단한 경험 법칙은 소비자가 짧은 시간 동안 편안하게 보유할 수 있는 작업량을 기준으로 프리페치를 조정하는 것입니다. 작업자가 초당 약 20개의 메시지를 처리하고 약 1초의 로컬 버퍼링된 작업에 만족한다면 프리페치를 20에 가깝게 설정하는 것이 합리적인 실험입니다.
프리페치가 너무 높은지 확인하는 방법
다음과 같은 경우 프리페치가 너무 높을 가능성이 있습니다:
messages_unacknowledged가 활성 소비자에 비해 큽니다.- 일부 소비자는 많은 미확인 메시지를 보유하고 있는 반면 다른 소비자는 유휴 상태입니다.
messages_ready가 낮은데도 메시지 지연 시간이 높습니다.- 버스트 중에 소비자 메모리가 증가합니다.
- 소비자 충돌로 인해 대규모 재전송이 발생합니다.
마지막 요점은 놓치기 쉽습니다. 작업자가 1,000개의 미확인 메시지를 보유하고 충돌하면 RabbitMQ는 해당 메시지를 재전송할 수 있습니다. 이는 올바른 동작이지만, 핸들러가 멱등성이 아닌 경우 다운스트림 시스템에 중복 압력을 생성할 수 있습니다.
프리페치를 낮추면 공정성과 복구 동작이 개선되는 경우가 많습니다. 최대 처리량이 약간 감소할 수 있지만 사용자가 실제로 느끼는 지연 시간을 개선할 수 있습니다.
프리페치가 너무 낮은지 확인하는 방법
다음과 같은 경우 프리페치가 너무 낮을 가능성이 있습니다:
- 소비자의 CPU 및 메모리 사용량이 낮은 반면
messages_ready는 계속 증가합니다. - 처리 시간은 매우 짧지만 전달 속도가 제한됩니다.
- 소비자와 RabbitMQ 간의 네트워크 지연 시간이 눈에 띕니다.
- 프리페치를 높이면 꼬리 지연 시간이나 메모리 압력이 증가하지 않고 처리량이 향상됩니다.
전형적인 예는 작은 인메모리 계산을 수행하고 즉시 ack하는 빠른 작업자입니다. prefetch_count=1을 사용하면 다음 메시지를 기다리는 데 너무 많은 시간을 소비할 수 있습니다. 프리페치를 높이면 작은 로컬 버퍼를 제공하고 작업자를 바쁘게 유지합니다.
다운스트림 병목 현상을 숨기지 마세요
프리페치 조정은 느린 데이터베이스를 해결하지 못합니다. 작업이 분산되고 버퍼링되는 방식만 변경할 수 있습니다. 모든 메시지가 동일한 과부하 API를 기다리는 경우 더 높은 프리페치는 일시적으로 처리량이 더 좋아 보일 수 있지만 시간 초과 및 재시도가 증가합니다.
소비자 내부를 측정하세요. 메시지 디코딩, 데이터베이스 대기, 외부 서비스 호출 및 ack에 소요된 시간을 기록하거나 메트릭을 내보내세요. RabbitMQ는 준비 및 미확인 수를 표시할 수 있지만 핸들러가 8초가 걸리는 이유를 알려줄 수는 없습니다.
다운스트림 서비스가 속도 제한되는 경우 프리페치는 더 높지 않고 더 낮아야 하는 경우가 많습니다. 작업자 내부에 수천 개의 진행 중인 호출을 숨기는 대신 큐가 백로그를 눈에 띄게 흡수하도록 하세요.
프리페치와 동시성은 다릅니다
프리페치가 50이라고 해서 소비자가 50개의 메시지를 병렬로 처리한다는 의미는 아닙니다. 이는 RabbitMQ가 확인을 받기 전에 50개의 메시지를 전달할 수 있음을 의미할 뿐입니다. 메시지가 동시에 실행되는지 여부는 소비자 코드에 따라 다릅니다.
프리페치가 50인 단일 스레드 소비자는 한 번에 하나의 메시지를 처리하는 동안 49개가 메모리에서 대기할 수 있습니다. 동시성이 10이고 프리페치가 50인 작업자 풀은 10개의 활성 작업과 40개의 버퍼링된 작업을 유지할 수 있습니다. 때로는 해당 버퍼가 유용합니다. 때로는 단순히 지연 시간입니다.
프리페치를 실제 동시성과 일치시키세요. 프로세스가 한 번에 5개의 핸들러를 실행할 수 있는 경우 프리페치 5~20이 500보다 추론하기 쉽습니다.
순서 및 공정성 트레이드오프
RabbitMQ 큐는 큐 수준에서 순서를 유지하지만 소비자 동작은 작업이 완료되는 순서를 변경할 수 있습니다. 여러 소비자와 1보다 큰 프리페치를 사용하면 메시지 20이 메시지 3보다 먼저 완료될 수 있습니다. 더 빠른 작업자로 이동했거나 더 쉬운 작업이 있었기 때문입니다.
대부분의 작업 큐에서 완료 순서는 중요하지 않습니다. 계정 업데이트, 재고 변경 또는 순차적으로 처리해야 하는 워크플로의 경우 매우 중요할 수 있습니다. 이러한 경우 순서 지정 키당 하나의 큐를 사용하거나, 키로 샤딩하거나, 프리페치를 낮게 유지하는 것이 최대 처리량을 추구하는 것보다 더 안전할 수 있습니다.
공정성에도 유사한 트레이드오프가 있습니다. 낮은 프리페치는 소비자가 메시지를 더 자주 요청하기 때문에 RabbitMQ가 작업을 더 고르게 분배할 수 있게 합니다. 높은 프리페치는 먼저 메시지를 수신하는 소비자에게 보상합니다. 메시지 처리 시간이 고르지 않은 경우 한 작업자가 느린 작업 더미를 보유하고 다른 작업자가 배치를 빠르게 완료하는 상황이 발생할 수 있습니다.
사람들이 "RabbitMQ 로드 밸런싱이 고르지 않다"고 말할 때 프리페치는 가장 먼저 확인해야 할 사항 중 하나입니다. 브로커는 아직 전달되지 않은 메시지만 균형을 맞출 수 있습니다.
장애 동작이 중요합니다
프리페치는 소비자가 중단될 때 발생하는 상황을 변경합니다. prefetch_count=1을 사용하면 채널이 닫힐 때 하나의 미확인 전달이 반환됩니다. prefetch_count=500을 사용하면 한 번에 수백 개가 반환될 수 있습니다. 소비자가 충돌하기 전에 부분적인 부작용을 수행한 경우 핸들러가 멱등성이 아닌 한 해당 재전송으로 인해 중복 쓰기, 중복 이메일 또는 중복 API 호출이 트리거될 수 있습니다.
이는 높은 프리페치가 잘못되었다는 의미는 아닙니다. 이는 높은 프리페치가 멱등성 핸들러, 명확한 재시도 규칙 및 재전송 속도 모니터링과 함께 사용되어야 함을 의미합니다. 중복 처리가 위험한 경우 애플리케이션이 이를 처리할 수 있도록 구축될 때까지 미확인 창을 작게 유지하세요.
소비자에서 redelivered 플래그를 확인하세요. 완전한 재시도 카운터는 아니지만 메시지가 이전에 전달된 적이 있다는 유용한 신호입니다. 강력한 재시도 제한을 위해 헤더 또는 애플리케이션 상태에서 시도를 추적하고 소진된 메시지를 데드 레터 큐로 라우팅하세요.
여러 큐 및 혼합 워크로드
하나의 프리페치 값이 모든 큐에 적합한 경우는 거의 없습니다. thumbnail.generate 및 email.send를 소비하는 서비스는 각각 다른 설정이 필요할 수 있습니다. 썸네일 생성은 CPU 집약적이며 낮은 동시성이 가장 좋을 수 있습니다. 이메일 전송은 네트워크 바운드이며 더 많은 진행 중인 메시지를 허용할 수 있습니다.
단일 프로세스가 하나의 채널에서 여러 큐를 소비하는 경우 QoS 동작을 추론하기가 더 어려워질 수 있습니다. 의미 있게 다른 워크로드에는 별도의 채널을 사용하는 것이 좋습니다. 이렇게 하면 프리페치, 모니터링 및 장애 처리가 더 명확해집니다.
혼합 메시지 크기는 또 다른 경고 신호입니다. 큐에 작은 이벤트와 큰 페이로드가 모두 포함된 경우 개수 기반 프리페치는 메모리 압력을 잘 반영하지 못합니다. 작은 메시지 10개와 큰 메시지 10개는 동일한 비용이 아닙니다. 이러한 상황에서는 워크로드를 분할하거나 RabbitMQ에서 큰 페이로드를 제거하고 대신 참조를 전달하세요.
큐별뿐만 아니라 소비자별 미확인 메시지 확인
큐 수준의 미확인 수는 미완료 작업이 있음을 알려주지만 불균형을 숨길 수 있습니다. 한 소비자가 대부분의 미확인 메시지를 보유하고 나머지는 거의 비어 있을 수 있습니다. 이는 종종 높은 프리페치, 고르지 않은 메시지 비용 또는 비정상적인 작업자를 나타냅니다.
테스트 중에 관리 UI, Prometheus 또는 rabbitmqctl list_consumers에서 소비자 수준 메트릭을 사용하세요. 분포가 고르지 않은 경우 프리페치를 낮추거나 느린 메시지 유형을 분할하면 총 처리량이 약간만 변경되더라도 실제 지연 시간을 개선할 수 있습니다.
배포 후 프리페치 재검토
프리페치 값은 오래됩니다. 핸들러가 하나의 데이터베이스 행만 쓸 때 작동했던 값은 다음 릴리스에서 API 호출, 추가 검증 또는 더 큰 페이로드가 추가된 후에는 잘못될 수 있습니다. 프리페치를 한 번 설정하고 잊어버리는 숫자가 아닌 성능 구성의 일부로 취급하세요.
소비자 릴리스 후에는 처리 지연 시간, 미확인 수, 재전송 및 소비자 메모리를 이전 버전과 비교하세요. 지연 시간이 증가했지만 CPU가 포화되지 않은 경우 핸들러가 외부에서 무언가를 기다리고 있을 수 있으며 더 낮은 프리페치가 시스템을 더 공정하게 유지할 수 있습니다. CPU가 높고 각 메시지가 CPU 바운드인 경우 작업자를 추가하거나 메시지당 작업을 줄이는 것이 프리페치를 변경하는 것보다 더 중요할 수 있습니다.
선택한 값의 이유를 소비자 구성 근처에 문서화하세요. 향후 유지 관리자는 prefetch_count=5가 공정성, 메모리, 순서, 다운스트림 속도 제한 또는 단지 임시 기본값으로 선택되었는지 알아야 합니다.
실제 메시지 형태로 테스트
프로덕션 메시지가 큰 JSON 페이로드이거나 값비싼 데이터베이스 조회를 포함하는 경우 작은 가짜 메시지로 프리페치를 조정하지 마세요. 메시지 크기와 핸들러 비용이 중요합니다.
유용한 테스트 루프는 다음과 같습니다:
- 프리페치 값을 선택합니다.
- 안정적인 동작을 볼 수 있을 만큼 오랫동안 현실적인 게시 속도로 실행합니다.
messages_ready,messages_unacknowledged, 소비자 CPU, 소비자 메모리, 처리 지연 시간 및 오류율을 확인합니다.- 하나의 소비자를 중단하고 얼마나 많은 메시지가 재전송되는지 확인합니다.
- 프리페치를 늘리거나 줄이고 반복합니다.
최상의 값은 단기 벤치마크 처리량이 가장 높은 값인 경우는 거의 없습니다. 소비자를 바쁘게 유지하고, 지연 시간을 허용 가능하게 유지하며, 시스템이 처리할 수 있는 방식으로 실패하는 값입니다.
실용적인 기본값
아직 데이터가 없는 경우 일반 작업 큐의 경우 수동 확인 및 prefetch_count=10으로 시작하세요. 느리거나, 비용이 많이 들거나, 엄격하게 공정한 처리에는 1을 사용하세요. 측정 후 빠르고 안정적인 핸들러에는 20 또는 50을 시도하세요. 메트릭이 전달 왕복이 병목 현상이고 소비자에게 메모리 여유가 있음을 보여줄 때만 더 높이 올리세요.
RabbitMQ 프리페치 조정은 일회성 설정이 아닙니다. 메시지 크기, 소비자 코드, 다운스트림 종속성이 변경되거나 작업자 인스턴스를 더 추가할 때 다시 검토하세요. 올바른 프리페치 값은 현재 작업 형태와 일치하는 값입니다.