정우님, 이 질문 핵심이 정말 중요합니다.
“그러면 consume도 worker마다 자기 전용 채널을 주면 안전한 것 아니냐?”
→ 기술적으로는 맞는 말처럼 보여요. 하지만 실제 RabbitMQ 구조에서는 문제가 크게 2가지 발생합니다.
worker마다:
ch := conn.Channel()
msgs := ch.Consume(...)
for msg := range msgs {
handle(msg)
}
이렇게 하면 확실히 worker는 자기 채널만 사용하니까 goroutine-safe 문제가 없습니다.
이유는 아래 2개가 진짜 핵심:
RabbitMQ는 채널마다 consumer 1명이 붙어버립니다.
즉, worker가 100개면 consumer도 100명.
그러면 메시지 분배 방식이:
RabbitMQ의 내부 라운드로빈으로 worker들에게 분산됨
이게 문제입니다.
메시지를 중앙에서 받고
→ worker 풀이 고르게 처리하게 하고
→ worker가 느리면 백프레셔가 걸리고
→ Ack도 worker 쪽에서 제어
즉, Worker Pool을 구축할 수가 없음.
RabbitMQ가 강제로 라운드로빈 worker 스케줄러가 되어버림.
worker들이 바로 msg.Ack() 하면:
worker pool 구조에서는 “느린 worker는 메시지를 적게 받고”, “빠른 worker는 여러 개 처리”해야 정상인데
RabbitMQ는 그런 걸 고려하지 않습니다.
즉, backpressure 불가능.
consumer 수가 많아지면:
실전에서는 5~20명 정도만 consumer 붙이고, 나머지는 worker 풀로 처리합니다.
[RabbitMQ]
⬇ (하나의 channel / 하나의 consumer)
[Go goroutine 1개]
⬇ msgs → workerChan
[Worker Goroutine Pool N개]
→ 병렬 처리
→ Ack 여기서
이 구조가 전 세계 RabbitMQ consumer 구현의 정석입니다.
각 worker는 서로 다른 queue를 consume 한다.
즉,
그럴 땐 channel 1개씩 줘도 됨.
| 질문 | 답변 |
|---|---|
| worker마다 consume 채널 한 개씩 줘도 되냐? | 기술적으로 YES |
| 실전에서 그렇게 쓰면 되냐? | 거의 NO (강력 비권장) |
| 문제점 | backpressure 불가, 전체 처리량 불안정, RabbitMQ 스케줄러가 제어권을 가져감 |
| 정답 구조 | consume 1개 + Go worker pool |
정우님이 만약 ffmpeg 작업 큐나 파일 변환 작업 큐를 만들고 있다면
무조건 consumer 하나 + worker pool 구조가 가장 안정적입니다.
원하면 이 구조를 정우님 프로젝트에 맞는 실제 코드 형태로 만들어서 전달드릴까요?