이코에코(Eco²) Knowledge Base/Troubleshooting

Message Queue 트러블슈팅: Quorum Queue -> Classic Queue 마이그레이션

mango_fr 2025. 12. 24. 09:20

환경 정보

Component Version
RabbitMQ 4.0.9
Celery 5.4.0
kombu 5.6.1
amqp 5.3.1
Python 3.11.14
RabbitMQ Messaging Topology Operator latest (rabbitmqoperator/messaging-topology-operator)
RabbitMQ Cluster Operator latest (rabbitmqoperator/cluster-operator)
Kubernetes v1.28.15

문제 상황

증상

Celery 워커들이 RabbitMQ에 연결 후 즉시 연결이 끊기고 무한 재시작하는 현상 발생:

[2025-12-24 00:03:34,585: INFO/MainProcess] Connected to amqp://admin:**@eco2-rabbitmq.rabbitmq.svc.cluster.local:5672/eco2
[2025-12-24 00:03:34,644: WARNING/MainProcess] consumer: Connection to broker lost. Trying to re-establish the connection...

에러 메시지

amqp.exceptions.AMQPNotImplementedError: Basic.consume: (540) NOT_IMPLEMENTED - queue 'scan.vision' in vhost 'eco2' does not support global qos

영향 범위

  • scan-worker
  • character-worker
  • character-match-worker
  • my-worker

모든 Celery 워커가 정상 작동하지 않음.

근본 원인 분석

1. Quorum Queue와 Global QoS

RabbitMQ의 Quorum Queue는 고가용성과 내구성을 위한 큐 타입이지만, Global QoS를 지원하지 않습니다.

# Quorum Queue의 제한사항
- global QoS 미지원 (per-consumer QoS만 지원)
- Non-durable subscribers 미지원
- Queue exclusivity 미지원
- Message priorities 미지원

2. Celery/kombu의 Global QoS 사용

Celery(kombu)는 기본적으로 basic_qos(global=True)를 호출합니다:

# kombu/transport/pyamqp.py
def basic_qos(self, prefetch_size=0, prefetch_count=0, apply_global=True):
    ...

3. 시도했지만 실패한 해결책

시도 1: broker_transport_options: {"global_qos": False}

# domains/_shared/celery/config.py
"broker_transport_options": {
    "global_qos": False,
},

결과: 실패. celery_app.config_from_object(dict) 방식에서는 broker_transport_options가 제대로 적용되지 않음.

시도 2: celery_app.conf.update(dict) 방식으로 변경

# 변경 전
celery_app.config_from_object(settings.get_celery_config())

# 변경 후  
celery_app.conf.update(settings.get_celery_config())

결과: 설정은 적용되었으나 여전히 global QoS 에러 발생.

# Pod 내부에서 확인
>>> app.conf.broker_transport_options
{'global_qos': False, 'confirm_publish': True}

시도 3: --without-mingle --without-gossip 옵션 추가

# deployment.yaml
args:
  - --without-mingle
  - --without-gossip

결과: 실패. mingle/gossip은 워커 간 통신에 사용되며, global QoS 호출 경로와 무관.

시도 4: worker_prefetch_multiplier = 0

"worker_prefetch_multiplier": 0,  # QoS 비활성화 시도

결과: 미시도 (Classic Queue 전환으로 해결됨).

4. 왜 global_qos: False가 작동하지 않았나

kombu/Celery의 QoS 호출 경로를 분석한 결과:

  1. broker_transport_optionsglobal_qos 설정은 Connection 레벨에서 적용됨
  2. 하지만 실제 basic_qos() 호출은 Channel 레벨에서 발생
  3. kombu 5.6.1에서 이 설정이 Channel까지 전파되지 않는 버그 또는 설계 문제 존재

해결책: Classic Queue로 전환

선택 이유

Queue Type Global QoS 내구성 복제 Celery 호환성
Classic ✅ 지원 단일 노드 ✅ 완벽
Quorum ❌ 미지원 Raft 기반 ⚠️ 제한적
Stream ❌ 미지원 Log 기반

Celery + RabbitMQ 조합에서는 Classic Queue가 가장 호환성이 좋음.

변경 내용

1. RabbitMQ Topology CR (queues.yaml)

# 변경 전
spec:
  name: scan.vision
  type: quorum  # ❌
  arguments:
    x-delivery-limit: 3  # Quorum 전용 옵션

# 변경 후
spec:
  name: scan.vision
  type: classic  # ✅
  arguments:
    # x-delivery-limit 제거 (Classic 미지원)

2. Celery Config (config.py)

# 변경 전
Queue(
    "scan.vision",
    queue_arguments={
        "x-queue-type": "quorum",  # ❌
        "x-delivery-limit": 3,      # ❌
        ...
    },
)

# 변경 후
Queue(
    "scan.vision",
    queue_arguments={
        # x-queue-type 생략 (classic이 기본값)
        # x-delivery-limit 제거
        "x-dead-letter-exchange": "dlx",
        "x-dead-letter-routing-key": "dlq.scan.vision",
        "x-message-ttl": 3600000,
    },
)

3. Worker Deployment

# 변경 전
args:
  - --without-mingle   # ❌ 제거
  - --without-gossip   # ❌ 제거

# 변경 후
args:
  # mingle/gossip 옵션 제거 (Classic에서는 불필요)

4. Celery broker_transport_options

# 변경 전
"broker_transport_options": {
    "global_qos": False,  # ❌ 제거 (Classic은 global QoS 지원)
    "confirm_publish": True,
},

# 변경 후
"broker_transport_options": {
    "confirm_publish": True,
},

마이그레이션 절차

  1. 모든 워커 스케일 다운
  2. kubectl scale deployment/scan-worker -n scan --replicas=0 kubectl scale deployment/character-worker -n character --replicas=0 kubectl scale deployment/character-match-worker -n character --replicas=0 kubectl scale deployment/my-worker -n my --replicas=0
  3. 기존 Quorum 큐 삭제
  4. kubectl exec -n rabbitmq eco2-rabbitmq-server-0 -- \ rabbitmqctl delete_queue scan.vision -p eco2 # ... 모든 큐에 대해 반복
  5. Queue CR 삭제 (Topology Operator가 재생성하지 못하도록)
  6. kubectl delete queue --all -n rabbitmq
  7. Git push & ArgoCD Sync
  8. git push origin develop kubectl -n argocd patch application dev-rabbitmq-topology \ --type merge -p '{"operation":{"sync":{"prune":true}}}'
  9. Classic 타입으로 Queue CR 재생성 확인
  10. kubectl get queue -n rabbitmq -o custom-columns='NAME:.metadata.name,TYPE:.spec.type'
  11. RabbitMQ에서 Classic 큐 생성 확인
  12. kubectl exec -n rabbitmq eco2-rabbitmq-server-0 -- \ rabbitmqctl list_queues -p eco2 name type
  13. 워커 스케일 업
  14. kubectl scale deployment/scan-worker -n scan --replicas=1 # ... 모든 워커에 대해 반복

주의사항: Webhook Validation

Queue CR의 type 필드는 immutable이라 Queue CR 삭제 → 재생성 절차가 필수입

admission webhook "vqueue.kb.io" denied the request: 
[spec.type: Invalid value: "classic": queue type cannot be updated]

결과

성공 로그

[2025-12-24 00:12:55,171: INFO/MainProcess] celery@scan-worker-xxx ready.

최종 큐 상태

name              type
scan.vision       classic
scan.rule         classic
scan.answer       classic
scan.reward       classic
character.match   classic
character.reward  classic
my.reward         classic
celery            classic

교훈 및 권장사항

1. Queue Type 선택 가이드

사용 사례 권장 Queue Type
Celery 태스크 큐 Classic
이벤트 스트리밍 (Kafka 대체) Stream
금융/트랜잭션 (메시지 손실 불가) Quorum
일반 Pub/Sub Classic

2. Celery + Quorum Queue를 사용해야 하는 경우

Celery 5.5.0+에서 개선된 Quorum Queue 지원이 있지만, 여전히 제한사항 존재:

  • task_acks_late = True 필수
  • worker_prefetch_multiplier = 1 권장
  • ETA/countdown 태스크 주의

3. 버전 호환성 매트릭스

Celery kombu RabbitMQ Quorum Queue
< 5.3 < 5.3 any ❌ 지원 안 함
5.3+ 5.3+ 3.10+ ⚠️ 제한적
5.5+ 5.6+ 4.0+ ⚠️ 개선됨 (여전히 global QoS 이슈)

4. 모니터링 권장 사항

# 큐 타입 확인
rabbitmqctl list_queues name type -p eco2

# Celery 워커 상태
celery -A app inspect ping

# 연결 상태
rabbitmqctl list_connections

관련 문서