📁 File7. 파이프라인 이론02. Queue Decoupling — 큐로 단계 분리, backpressure, in-flight 가시성

02. Queue Decoupling — 큐로 단계 분리, backpressure, in-flight 가시성

이 문서가 답하는 질문: 단계 사이에 무엇을 끼우는가? 큐의 어떤 속성이 파이프라인을 살리고 죽이는가?


한 줄 답

단계와 단계 사이에는 persistent queue를 끼운다 — 생산자/소비자의 속도·가용성·장애를 분리하기 위해서. 큐의 5가지 속성 — at-least-once delivery, visibility timeout, retention, ordering, backpressure — 이 곧 파이프라인의 안정성 한계를 결정한다. 가장 흔한 사고는 visibility timeout < 작업시간으로 만들어지는 “죽음의 루프”다.


Why — 왜 직접 호출하면 안 되는가

A → B를 직접 호출(synchronous RPC)하면 4가지 결합이 동시에 생긴다.

결합의미결과
시간 결합A가 B의 응답을 기다림B가 느려지면 A도 느려짐
가용성 결합B가 죽으면 A의 호출 실패B의 다운타임 = A의 다운타임
속도 결합A의 호출 rate = B의 처리 ratespike 발생 시 B 폭발
버전 결합A의 호출 시점 = B의 처리 시점B 배포 중 A 실패

큐를 끼우면 4개 결합이 모두 풀린다.

  • 시간 — A는 publish하고 끝, B의 처리 시간 무관
  • 가용성 — B가 다운타임 동안 큐가 메시지 보관, 복구되면 따라잡음
  • 속도 — A가 spike 쳐도 큐가 받아주고 B는 자기 속도로 처리
  • 버전 — B 배포 중 큐가 메시지 보관, 새 버전 뜨면 처리 재개

How — 큐의 5가지 핵심 속성

1) Delivery Semantics — at-most-once / at-least-once / exactly-once

의미설명비용
at-most-once손실 가능, 중복 없음가장 싸지만 미디어엔 부적합
at-least-once손실 없음, 중복 가능표준 — 멱등성으로 중복 방어
exactly-once둘 다 없음환상에 가까움 (자세히는 04)

대부분의 큐(SQS standard, Kafka 기본, Pub/Sub)는 at-least-once다. 이 사실이 파이프라인 설계의 출발점이다 — 소비자는 항상 중복 메시지를 가정해야 한다.

2) Visibility Timeout — 가장 많이 사고가 나는 설정

시간 < 작업시간이면 죽음의 루프가 만들어진다:

규칙:

  • visibilityTimeout > p99 작업시간 × 1.5 안전 마진
  • 작업이 길어질 수 있다면 worker가 ChangeMessageVisibility 로 timeout을 연장 (heartbeat)
  • Lambda는 fn timeout과 SQS visibility를 둘 다 충분히 잡는다 (보통 fn ≤ visibility)

3) Retention — 큐가 메시지를 얼마나 보관하나

기본 retention최대
SQS4일14일
Kafka7일 (configurable)무한(disk 한도)
Pub/Sub7일31일

의미: 소비자가 다운된 동안 큐가 버틸 수 있는 시간. retention < 다운타임이면 메시지 손실. 미디어 파이프라인은 보통 default(4-7일)면 충분 — 재처리가 그 이상 미뤄지면 다른 문제(외부 알람).

4) Ordering — FIFO를 정말 써야 하나?

Standard (unordered)FIFO
순서보장 안 함보장 (group 안에서)
중복at-least-once (중복 가능)exactly-once 옵션 (5분 dedupe window)
처리량사실상 무제한초당 300 (batching 시 3000) msg
비용싸다약 2배

미디어 파이프라인은 거의 unordered면 충분하다 — 파일 단위 독립 처리이고, 메시지 안의 fileId가 식별자이므로 순서 무관. FIFO가 필요한 경우: (a) 같은 fileId의 update가 순서대로 와야 함 (그러나 보통 멱등 설계로 해결), (b) 순차 단계 강제(불필요).

FIFO는 좁은 영역만. 일반은 standard + 멱등성.

5) Backpressure — 소비자가 막히면 생산자도 막혀야 한다

큐가 무한히 차오르는 것은 정상이 아니다.

대응 패턴:

패턴작동비용
Auto-scaling consumer백로그가 차면 worker 늘림컴퓨트 비용↑
Producer rate limit큐 depth가 임계 넘으면 publish 거부사용자 경험 저하
Priority queue중요 메시지 별도 큐큐 분리 비용
Drop policy오래된 메시지 자동 삭제메시지 손실

미디어 파이프라인은 보통 (1)+(2) 조합 — Lambda 동시성 한도 + 업로드 API rate limit.


What — 주요 큐 시스템 비교

1) AWS SQS

속성
Deliveryat-least-once (standard), exactly-once 옵션 (FIFO)
Retention1분 ~ 14일
Visibility0초 ~ 12시간
메시지 크기256KB (SNS extended로 2GB까지)
순서standard 무, FIFO 보장
트리거Lambda event source mapping
DLQ내장, retry 횟수 기준

용도: 람다 트리거, 단순 작업 큐, 시작용으로 가장 만만함.

2) Apache Kafka

속성
Deliveryat-least-once (기본), exactly-once 가능 (transactional)
Retention시간/크기 기반, 사실상 무한 가능
Visibilityoffset commit 모델 (다름)
메시지 크기1MB 기본 (조정 가능)
순서partition 안에서 보장
소비자 모델consumer group, offset 관리
DLQ내장 X (별도 토픽으로 구현)

용도: 고처리량(>10K msg/s), 이벤트 소싱, 다중 구독자 fan-out, 재처리 가능한 로그.

3) Google Cloud Pub/Sub

속성
Deliveryat-least-once, exactly-once 옵션
Retention7일 기본, 31일까지
Ack deadline10초 ~ 600초 (heartbeat로 연장)
메시지 크기10MB
순서ordering key로 그룹별 보장
소비자 모델push or pull
DLQ내장

용도: GCP 환경, 글로벌 push 모델 필요 시.

4) RabbitMQ / NATS / Redis Streams

  • RabbitMQ — 복잡한 라우팅(exchange), 우선순위 큐. 자체 호스팅 시 운영 부담.
  • NATS JetStream — 가벼운 자체 호스팅, 저지연.
  • Redis Streams — 작은 워크로드, 이미 Redis가 있을 때.

→ 미디어 파이프라인은 대부분 SQS / Kafka / Pub/Sub 셋 중 하나.

5) “큐를 언제 분리하는가” — 단일 큐 vs 다중 큐

큐 분리 신호:

  • 다른 메시지 모양(schema)을 한 큐에 섞지 마라 — consumer가 분기하는 비용↑
  • 다른 retry policy가 필요하면 분리
  • 다른 priority가 필요하면 분리
  • 다른 worker pool로 처리해야 하면 분리

→ 영상 변환 파이프라인의 흔한 분할: 3개 SQS — 미디어 분석, 썸네일/스프라이트, 상태 업데이트. 각 큐는 다른 worker pool, 다른 DLQ retry 횟수.


What-if — 잘못 쓰면 어떻게 깨지는가

1) Visibility timeout < 작업시간 — 죽음의 루프 (이미 본)

증상: 같은 영상이 N번 transcode됨 → MediaConvert 비용 N배. 대응: timeout 늘리기 + ChangeMessageVisibility heartbeat.

2) DLQ 없음 — poison message가 큐를 점유

DLQ 없으면 영구 실패 메시지가 retention 만료까지 같은 worker를 계속 죽인다. → 모든 큐는 DLQ를 가진다 (자세히는 05).

3) Retention < 다운타임

소비자 다운타임이 4일을 넘으면 SQS 기본 retention(4일)에서 메시지 사라짐. → 운영 SLO에 맞춰 retention 설정. 미디어는 보통 4-7일이면 충분.

4) FIFO를 잘못 적용

FIFO는 “MessageGroupId” 단위 순차 — 전체 throughput이 group 갯수에 묶인다. fileId를 group으로 쓰면 같은 file의 메시지만 순차이고 다른 file은 병렬 → 의도한 대로. 모든 메시지를 한 group에 넣으면 throughput 폭락.

5) “메시지에 큰 페이로드 직접 넣음”

256KB(SQS) / 1MB(Kafka 기본) 한도 초과로 publish 실패. → Claim Check Pattern: 본문은 S3에, 메시지에는 s3Key만.

6) “소비자가 못 따라가는데 무시”

큐 depth가 monotonic하게 증가 → 시스템이 살아있는 것처럼 보이지만 처리 지연 폭주. → AgeOfOldestMessage 메트릭에 알람 (10분 이상 = 빨간불).

7) “메시지 ack을 finally에”

// 안티패턴
try {
  await processMessage(m);
} finally {
  await deleteMessage(m); // 실패해도 삭제됨!
}

ack은 성공했을 때만. 실패하면 visibility timeout 뒤 자연스럽게 redeliver.


Insight — 흥미로운 이야기

“큐는 데이터베이스의 사촌이다”

큐(persistent queue)는 사실상 “한쪽 끝에서만 INSERT, 반대쪽에서만 DELETE되는 정렬된 테이블”이다. Kafka는 자기를 “distributed commit log”라 부르는데, 실제로 binlog 기반 DB와 같은 추상화다. 그래서 큐 운영 사고도 DB 운영 사고와 닮았다 — disk 가득참, replication lag, leader election.

“SQS는 처음에 ‘Simple Queue Service’라고 이름 붙였는데, 진짜 simple하지 않다”

SQS는 visibility timeout, dedupe ID, message group, redrive policy, batch ack, long polling, 그리고 standard vs FIFO의 비대칭 등 옵션이 30개를 넘는다. “Simple”은 사용자에게 그렇게 보인다는 뜻일 뿐, 내부는 분산 시스템의 모든 어려움을 흡수한 결과.

“Kafka는 큐가 아니라 로그다”

Jay Kreps의 LinkedIn 시절 통찰 — 모든 큐는 사실 로그를 실시간 tail하는 것이다. 그렇다면 처음부터 로그를 1급 시민으로 만들면 어떨까? 그래서 Kafka는 retention을 무한대로 잡고, consumer offset을 옮겨서 임의 시점부터 재생할 수 있다. 이 차이가 미디어 파이프라인 운영에서 강력하다 — 재처리(replay)가 가능. SQS에서는 메시지가 한 번 처리되면 사라지지만, Kafka에서는 같은 토픽의 같은 메시지를 다시 읽을 수 있다.

“in-flight 메시지 가시성은 큐의 비밀”

큐 depth만 보면 “처리되고 있는 메시지”는 안 보인다. SQS에는 ApproximateNumberOfMessagesNotVisible 메트릭이 있는데 이게 in-flight (visibility 안에 있는 메시지) 갯수다. 운영자가 정말 봐야 할 메트릭: (a) AgeOfOldestMessage — 처리 지연, (b) NotVisible — 동시 처리 갯수, (c) Empty receives — 폴링 낭비.


요약 + Mermaid

요점 — 큐는 시간·가용성·속도·버전 결합을 푸는 도구다. 5가지 속성(delivery / visibility / retention / ordering / backpressure)이 파이프라인 안정성의 한계를 결정한다. SQS·Kafka·Pub/Sub은 같은 개념의 다른 구현이다. 가장 흔한 사고는 visibility timeout 짧음으로 만들어지는 죽음의 루프 — 이걸 피하면 80%는 성공이다.