느린 메시지 처리 문제 해결: RabbitMQ 병목 지점 식별

프로듀서, 브로커, 큐, 컨슈머, 디스크, 확인 병목을 분리하여 RabbitMQ 속도 저하를 진단합니다.

느린 메시지 처리 문제 해결: RabbitMQ 병목 지점 식별

RabbitMQ 큐가 백업되면, 큐는 단지 증상을 보여줄 뿐입니다. 병목 지점은 느린 컨슈머, 차단된 퍼블리셔, 디스크 알람, 잘못된 프리페치 값, 거대한 메시지 페이로드, 또는 조용히 타임아웃되기 시작한 다운스트림 데이터베이스일 수 있습니다. RabbitMQ를 재시작하면 몇 분 동안 그래프가 깨끗해질 수 있지만, 메시지가 느렸던 이유를 거의 해결하지 못합니다.

가장 빠른 문제 해결 경로는 흐름을 조각으로 나누는 것입니다: RabbitMQ로 게시, 큐로 라우팅, 메시지 저장, 컨슈머로 전달, 작업 처리, 완료 확인. 각 조각은 다른 증거를 남깁니다.

첫 번째 분할: 준비됨 또는 확인되지 않음

큐 카운터부터 시작하세요:

rabbitmqctl list_queues name messages_ready messages_unacknowledged consumers state

messages_ready는 메시지가 전달되기를 기다리며 큐에 있음을 의미합니다. 이 숫자가 증가하면 RabbitMQ에 사용 가능한 컨슈머가 없거나, 컨슈머가 프리페치 한도에 도달했거나, 다른 조건으로 인해 전달이 차단되고 있는 것입니다.

messages_unacknowledged는 메시지가 이미 컨슈머에게 전달되었고 RabbitMQ가 ack, nack, reject 또는 채널 닫기를 기다리고 있음을 의미합니다. 이 숫자가 증가하면 병목 지점은 일반적으로 컨슈머 내부 또는 컨슈머가 호출하는 무언가에 있습니다.

이 구분은 중요합니다. 준비된 메시지가 많고 확인되지 않은 메시지가 적다면 브로커 메모리를 추가해도 컨슈머가 나타나지 않습니다. 확인되지 않은 메시지가 많다면 큐 파티션을 추가해도 도움이 되지 않을 수 있습니다. 작업이 이미 큐를 떠났기 때문입니다.

컨슈머가 실제로 존재하는지 확인

놀랍게도 많은 "RabbitMQ가 느리다"는 사고는 실제로 "컨슈머가 실행되고 있지 않다"는 사고입니다. 배포 실패, 오토스케일링이 0으로 감소, 자격 증명 변경, 잘못된 가상 호스트 사용, 또는 프로듀서가 프로덕션에 게시하는 동안 서비스가 스테이징에 연결된 경우입니다.

다음을 사용하세요:

rabbitmqctl list_consumers queue_name channel_pid consumer_tag ack_required prefetch_count active

컨슈머가 없으면 먼저 수정하세요. 컨슈머가 있지만 비활성 상태이면 애플리케이션 로그와 연결 상태를 확인하세요. 모든 컨슈머의 프리페치가 1이고 각 메시지가 몇 초가 걸리면 낮은 전달 동시성이 예상될 수 있습니다. 모든 컨슈머의 프리페치가 500이고 확인되지 않은 메시지가 많으면 컨슈머가 빨리 완료할 수 없는 작업을 축적하고 있을 수 있습니다.

컨슈머 처리 시간 측정

RabbitMQ는 메시지가 확인되지 않았다고 알려줄 수 있습니다. 컨슈머가 거대한 페이로드를 파싱하고 있는지, PostgreSQL을 기다리고 있는지, HTTP 호출을 재시도하고 있는지, 또는 잠금에 막혀 있는지는 알려줄 수 없습니다.

실제 핸들러 주변에 타이밍을 추가하세요:

message_received_at
decode_ms
business_logic_ms
database_ms
external_api_ms
ack_ms
message_completed_at

무언가를 배우기 위해 완벽한 추적 시스템이 필요하지 않습니다. 몇 가지 구조화된 로그 필드만으로도 핸들러가 일반적으로 80ms가 걸리지만 지금은 다운스트림 API를 기다리며 4초를 소비한다는 것을 보여줄 수 있습니다.

작업이 느리지만 병렬화 가능하다면 컨슈머 인스턴스를 추가하거나 내부 작업자 동시성을 높이세요. 다운스트림 시스템이 한계라면 컨슈머를 추가하면 상황이 더 나빠질 수 있습니다. 속도 제한, 배치, 캐싱 또는 별도의 재시도 큐가 필요할 수 있습니다.

핸들러를 이해한 후 프리페치 조정

프리페치는 RabbitMQ가 각 컨슈머에게 보낼 수 있는 확인되지 않은 메시지 수를 제어합니다. 백로그가 표시되는 위치를 변경하기 때문에 느린 처리 사고에 자주 관련됩니다.

낮은 프리페치에서는 컨슈머가 더 많은 메시지를 받을 준비가 될 때까지 메시지가 RabbitMQ에 준비 상태로 유지됩니다. 이는 공정하고 관찰하기 쉽지만 매우 빠른 컨슈머를 충분히 활용하지 못할 수 있습니다.

높은 프리페치에서는 메시지가 빠르게 컨슈머로 이동합니다. 이는 빠른 핸들러의 처리량을 향상시킬 수 있지만 지연 시간을 숨길 수도 있습니다. 큰 프리페치 값을 가진 느린 컨슈머는 다른 컨슈머가 작업이 부족한 동안 수백 개의 메시지를 보유할 수 있습니다.

실용적인 사고 대응은 느리거나 불안정한 컨슈머의 프리페치를 낮추고 테일 지연 시간이 개선되는지 관찰하는 것입니다. CPU 사용량이 낮고 준비된 메시지 수가 많은 빠른 컨슈머의 경우 조심스럽게 프리페치를 높이고 다시 측정하세요.

퍼블리셔 측 병목 지점 찾기

때로는 큐가 컨슈머가 느려서 백업되는 것이 아닙니다. 프로듀서가 버스트로 게시한 다음 확인을 위해 비효율적으로 기다리기 때문에 백업됩니다.

퍼블리셔 확인은 퍼블리셔가 RabbitMQ가 메시지를 수락했는지 알아야 할 때 올바른 도구입니다. 느린 패턴은 다음 메시지를 게시하기 전에 각 확인을 기다리는 것입니다. 이는 모든 게시를 왕복으로 만듭니다.

더 나은 패턴은 비동기 확인 또는 제한된 배치를 사용합니다. 퍼블리셔는 제한된 수의 메시지를 전송 중으로 유지하고, nack을 처리하며, 여전히 모든 단일 메시지에서 차단되는 것을 피할 수 있습니다. 한계는 중요합니다. 무제한 전송 중 게시는 병목 지점을 퍼블리셔 메모리 또는 브로커 압력으로 이동시킬 수 있습니다.

퍼블리셔 메트릭을 확인하세요: 게시 속도, 확인 지연 시간, 전송 중 확인 수, 재연결, 반환된 메시지, 채널 예외. 관리 UI에서 게시-인 속도를 전달/확인 속도와 비교하세요. 애플리케이션이 바쁜데도 게시-인이 낮다면 프로듀서가 확인, 트랜잭션 또는 연결 변동을 기다리고 있을 수 있습니다.

특별한 이유가 없다면 높은 처리량 게시를 위해 AMQP 트랜잭션을 피하세요. 일반적인 안정적인 게시의 경우 퍼블리셔 확인보다 훨씬 비용이 많이 듭니다.

RabbitMQ를 비난하기 전에 디스크 확인

영구 메시지, 쿼럼 큐, 스트림, 큰 백로그는 모두 디스크를 포함합니다. 디스크 지연 시간이 증가하면 메시지 흐름이 극적으로 느려질 수 있습니다.

RabbitMQ 노드에서 다음을 확인하세요:

rabbitmq-diagnostics status
rabbitmq-diagnostics alarms
rabbitmq-diagnostics memory_breakdown

OS 수준에서는 iostat, vmstat 또는 클라우드 모니터링 그래프와 같은 도구를 사용하세요. 처리량뿐만 아니라 디스크 지연 시간과 I/O 대기를 확인하세요. 버스트 크레딧을 소진한 클라우드 디스크는 구성에서는 정상으로 보일 수 있지만 실제로는 끔찍할 수 있습니다.

디스크가 병목 지점이라면 가능한 수정 사항으로는 더 빠른 스토리지, 더 적은 영구 쓰기, 더 작은 메시지, 더 나은 퍼블리셔 확인 배치, 큐 분할, 또는 재생 스타일 워크로드를 스트림이나 다른 로그 지향 시스템으로 이동하는 것이 있습니다. 그래프를 녹색으로 만들기 위해 비즈니스가 잃을 수 없는 메시지의 지속성을 비활성화하지 마세요.

알람 및 차단된 연결 확인

RabbitMQ는 메모리 및 디스크 알람으로 스스로를 보호합니다. 알람이 활성화되면 퍼블리셔가 차단될 수 있습니다. 이는 프로듀서 측에서 애플리케이션 속도 저하로 보일 수 있습니다.

실행:

rabbitmq-diagnostics alarms
rabbitmqctl list_connections name user state channels send_pend recv_cnt send_cnt

메모리 알람이 활성화된 경우 메모리가 큐, 연결, 확인되지 않은 메시지, 바이너리 또는 플러그인에 의해 점유되고 있는지 찾으세요. 디스크 알람이 활성화된 경우 브로커를 통해 더 많은 메시지를 푸시하려고 시도하기 전에 공간을 확보하거나 용량을 추가하세요.

차단된 연결은 그 자체로 버그가 아닙니다. 노드가 가용성을 보호하고 있기 때문에 RabbitMQ가 퍼블리셔에게 속도를 늦추라고 말하는 것입니다.

메시지 크기가 조용한 원인일 수 있음

초당 10,000개의 작은 메시지를 처리하는 시스템은 초당 500개의 큰 메시지로 어려움을 겪을 수 있습니다. 큰 페이로드는 네트워크 전송, 메모리 압력, 디스크 쓰기, 가비지 컬렉션 작업 및 컨슈머 처리 시간을 증가시킵니다.

메시지에 큰 문서, 이미지, 보고서 또는 큰 배열이 포함된 경우 페이로드를 객체 스토리지나 데이터베이스에 저장하고 RabbitMQ를 통해 참조를 보내는 것을 고려하세요. 라우팅 및 멱등성을 위한 충분한 메타데이터를 포함하되, 가능하면 브로커를 대량 저장 역할에서 제외하세요.

또한 압축 선택을 확인하세요. 거대한 페이로드를 압축하면 네트워크 및 디스크 사용량이 줄어들 수 있지만 CPU가 증가합니다. 이것이 도움이 되는지 여부는 병목 지점이 어디에 있는지에 따라 다릅니다.

재시도가 병목 지점을 만들 수 있음

실패하는 다운스트림 서비스는 하나의 메시지를 여러 번의 시도로 바꿀 수 있습니다. 컨슈머가 실패를 즉시 재큐잉하면 동일한 잘못된 메시지를 반복해서 처리하는 동안 새로운 작업은 대기할 수 있습니다. 큐 깊이가 증가하고 CPU가 바쁘게 보일 수 있으며, 유용한 작업은 거의 수행되지 않습니다.

높은 재전송률과 동일한 메시지 ID를 가진 반복적인 오류 로그를 찾으세요. 동일한 페이로드가 계속 실패하면 기본 흐름에서 이동시키세요. 데드 레터 교환, 지연된 재시도 큐 또는 예약된 재시도 메커니즘은 종속성이 복구될 시간을 주고 포이즌 메시지가 정상 작업을 차단하지 않도록 합니다.

재시도 폭풍에 주의하세요. API가 10분 동안 다운되고 모든 메시지가 매초 재시도되면 API가 돌아왔을 때 복구가 더 어려워집니다. 백오프를 사용하세요. 시도 횟수를 제한하세요. 최종 실패를 조사할 충분한 컨텍스트와 함께 데드 레터 큐에서 볼 수 있게 만드세요.

멱등성도 성능 문제 해결의 일부입니다. 컨슈머가 부분적으로 작업을 완료한 후 재시도하면 중복이 데이터베이스 경합, 고유 키 오류 또는 추가 다운스트림 호출을 만들 수 있습니다. 동일한 메시지를 안전하게 두 번 처리할 수 있는 핸들러는 확장 및 복구가 훨씬 쉽습니다.

관리 UI 속도는 컨텍스트가 필요함

RabbitMQ 관리 UI는 유용하지만, 하나의 라인만 읽으면 속도 차트가 오해를 불러일으킬 수 있습니다. 높은 전달 속도와 낮은 확인 속도는 작업이 완료되는 것보다 빠르게 분배되고 있음을 의미합니다. 높은 확인 속도와 높은 준비된 메시지 수는 컨슈머가 작업 중이지만 따라잡기에 충분하지 않을 수 있음을 의미할 수 있습니다. 사고 중 낮은 게시 속도는 프로듀서가 차단되었거나 확인을 기다리고 있음을 의미할 수 있습니다.

여러 속도를 함께 살펴보세요:

  • publish: 교환에 들어오는 메시지.
  • deliver/get: 컨슈머에게 보내진 메시지.
  • ack: 컨슈머가 완료한 메시지.
  • redeliver: 이전 실패 또는 채널 종료 후 다시 전달되는 메시지.

건강한 정상 작업 큐의 경우 게시 및 확인 속도는 시간이 지남에 따라 가까워야 합니다. 짧은 버스트는 정상입니다. 긴 간격은 백로그가 축적되거나 소진되고 있음을 의미합니다. 재전송이 급격히 증가하면 단순히 컨슈머를 더 추가하지 마세요. 메시지가 돌아오는 이유를 찾으세요.

샘플링 윈도우가 중요합니다. 1분 차트는 사용자에게 해를 끼치는 5초 중단을 숨길 수 있습니다. 1초 차트는 정상적인 버스트성을 혼란스럽게 보이게 할 수 있습니다. 사용자 또는 다운스트림 시스템이 신경 쓰는 지연 시간에 차트 윈도우를 맞추세요.

정상 백로그와 손상된 백로그 분리

모든 백로그가 비상 상황은 아닙니다. 배치 시스템은 낮 동안 의도적으로 작업을 큐에 넣고 밤에 소진할 수 있습니다. 사용자 대면 워크플로우는 메시지가 30초 동안 기다리면 비정상적일 수 있습니다. 동일한 큐 깊이가 한 시스템에서는 허용 가능하고 다른 시스템에서는 심각할 수 있습니다.

단순한 개수가 아닌 연령 기반 신호를 정의하세요. 메시지 개수는 얼마나 많은 메시지가 기다리고 있는지 알려줍니다. 메시지 연령은 비즈니스가 뒤쳐지고 있는지 알려줍니다. 모니터링이 가장 오래된 메시지 연령 또는 게시부터 확인까지의 종단 간 시간을 추적할 수 있다면 큐 깊이만으로보다 더 일찍 속도 저하를 포착할 수 있습니다.

알림을 그 기대치에 연결하세요. 10,000개의 메시지에 대한 알림은 야간 내보내기 큐에는 시끄러울 수 있고 비밀번호 재설정 큐에는 너무 늦을 수 있습니다. "가장 오래된 메시지가 서비스 목표보다 오래됨"에 대한 알림은 일반적으로 사용자가 신경 쓰는 것에 더 가깝습니다.

하나의 핫 큐는 여전히 하나의 핫 큐

클러스터 노드를 추가한다고 해서 하나의 큐가 모든 노드에 자동으로 분할되지는 않습니다. 단일 핫 큐는 리더, 컨슈머 및 스토리지 경로에 의해 제한될 수 있습니다.

하나의 큐가 관련 없는 작업 유형을 전달하는 경우 실제 처리 동작으로 분할하세요. 예를 들어, 이미지 크기 조정, 이메일 전송 및 청구 캡처는 지연 시간 및 재시도 요구 사항이 다른 경우 하나의 일반 jobs 큐를 공유해서는 안 됩니다. 별도의 큐를 사용하면 컨슈머를 독립적으로 확장하고 포이즌 메시지를 격리할 수 있습니다.

하나의 작업 유형이 여전히 너무 핫한 경우 순서 요구 사항이 허용할 때만 샤딩하세요. 고객 ID, 테넌트, 지역 또는 다른 안정적인 키로 샤딩하는 것은 작동할 수 있지만 라우팅 및 운영에 복잡성을 추가합니다. 느린 핸들러를 수정하는 것을 피하기 위해 샤딩하지 마세요.

침착한 문제 해결 순서

사고 발생 시 다음 순서를 사용합니다:

  1. 알람 확인: 메모리, 디스크 및 차단된 연결.
  2. 큐 카운터 확인: 준비됨, 확인되지 않음, 컨슈머.
  3. 컨슈머 로그 및 핸들러 타이밍 확인.
  4. 컨슈머별 프리페치 및 확인되지 않은 분포 확인.
  5. 퍼블리셔 확인 지연 시간 및 반환된 메시지 확인.
  6. 디스크 지연 시간 및 노드 리소스 압력 확인.
  7. 메시지 크기 및 최근 페이로드 변경 확인.
  8. 그런 다음에만 토폴로지를 변경하거나 브로커 노드를 추가하세요.

이 순서는 일반적인 실수를 방지합니다: 병목 지점이 작업자일 때 브로커를 확장하거나, 병목 지점이 디스크일 때 작업자를 확장하는 것입니다.

RabbitMQ는 일반적으로 올바른 카운터를 읽으면 매우 명확합니다. 증가하는 준비된 메시지 수는 작업이 대기 중임을 말합니다. 증가하는 확인되지 않은 메시지 수는 작업이 진행 중이지만 완료되지 않음을 말합니다. 차단된 퍼블리셔는 브로커가 스스로를 보호하고 있음을 말합니다. 각 신호를 단서로 취급하면 수정이 훨씬 덜 극적으로 됩니다.