📁 File7. 파이프라인 이론01. Pipeline Pattern — 파이프 & 필터, batch vs stream, 오케스트레이션 vs 코레오그래피

01. Pipeline Pattern — 파이프 & 필터, batch vs stream, 오케스트레이션 vs 코레오그래피

이 문서가 답하는 질문: 미디어 변환을 어떤 패턴으로 단계화하는가? 단계 사이를 누가 지휘하는가?


한 줄 답

미디어 파이프라인은 파이프 & 필터(Pipes and Filters) 패턴이 기본형이고, 데이터 도착 양식에 따라 batch / stream으로 갈라지며, 단계의 의사결정을 누가 쥐느냐에 따라 orchestration / choreography로 갈라진다. 우리가 다루는 미디어 변환은 거의 “chunked-batch + choreography(EBus 라우팅) + 부분 orchestration(분기)” 의 hybrid다.


Why — 왜 모놀리식 람다 하나가 망하는가

가장 흔한 출발점은 한 람다에 모든 단계를 넣는 모놀리식이다.

이 구조의 4가지 무너지는 이유:

압력무너지는 방식일반 원칙
시간람다 timeout(15분) 1시간 영상 transcode 불가”단계의 시간 분포는 단계마다 다르다”
CPU/메모리 프로파일probe(저메모리) + transcode(고메모리)가 한 람다 메모리 설정 공유”프로파일이 다르면 분리가 더 싸다”
재시도 단가sprite 단계만 실패해도 transcode 재실행 → 비용 N배”실패 단위 = 재실행 단위”
관측”어디서 실패?” 단일 stack trace로는 stage 식별 어려움”단계는 1급 시민이어야 한다”

→ 4가지 압력 중 어느 하나라도 들어오면 단계 분리(파이프 & 필터) 로의 전환이 강제된다.


How — 파이프 & 필터의 원리

1) 골격 — Filter는 stateless 변환자, Pipe는 전달자

Filter의 5계명:

  1. 단일 책임 — 한 Filter는 한 변환만 한다 (probe만, transcode만).
  2. Stateless — 입력 → 출력만 정의. 인스턴스 간 상태 공유 금지.
  3. 멱등 — 같은 입력 두 번 들어오면 같은 결과 (자세히는 04).
  4. 명시적 입출력 스키마 — Pipe로 흘러가는 메시지의 모양은 contract.
  5. 자기 단계만 분류 — 자기가 throw하는 에러를 자기가 분류 (05).

Pipe의 3계명:

  1. At-least-once 전달 — 중복은 허용, 손실은 금지 (자세히는 02).
  2. 순서 보장은 옵션 — 필요할 때만 FIFO; 일반은 unordered.
  3. Backpressure 가능 — 소비자 속도에 맞춰 생산을 막을 수 있어야 함.

2) 데이터 도착 양식 — batch vs stream

BatchStream
단위파일 1개 (또는 묶음)끊임없는 byte/event 흐름
시간성도착 후 처리 (eventual)연속 처리 (low-latency)
업로드된 비디오 transcode라이브 스트리밍 인코딩
실패 단위파일 단위 retry시점/오프셋 단위 retry
인프라SQS + Lambda / Step FunctionsKinesis / Kafka / Flink

미디어 변환은 거의 batch다 — 사용자가 업로드하고, 시스템이 비동기 처리하고, 결과 URL을 알린다. stream이 필요한 경우는 라이브 방송, 실시간 STT/번역 같은 좁은 영역.

chunked-batch — 큰 batch를 잘게 잘라 stream처럼 흘리는 hybrid가 흔하다. 예: 1시간 영상을 segment 단위로 쪼개 병렬 transcode (MediaConvert가 내부에서 함).

3) 의사결정 위치 — Orchestration vs Choreography

이 두 패턴은 “다음 단계로 갈지 말지를 누가 결정하는가” 의 차이다.

비교 축OrchestrationChoreography
흐름 가시성한 화면에 (Step Functions 그래프)분산 (어디로 가는지 추적 어려움)
결합도지휘자가 모든 단계를 안다단계는 자기 다음만 publish
분기 로직지휘자가 if/else 가짐이벤트 type/filter로 분기
수정 비용지휘자만 수정다수 subscriber 수정 가능
에러 처리지휘자가 catch + 재시도 정책각 단계가 자기 retry, DLQ
관측단일 trace ID 자연스러움명시적 correlation ID 필요
벤더Step Functions, Airflow, TemporalEventBridge, Kafka, Pub/Sub

→ 둘은 배타적이지 않다. 고정 분기는 orchestration, 확장 가능 분기는 choreography가 어울린다.


What — 구체 사양과 패턴 카탈로그

1) 4가지 변형 패턴

A. Linear Pipeline (가장 단순)

[A] → [B] → [C] → [D]
  • 미디어 분석 → transcode → DB update 같은 일자형
  • 단점: 하나 늦어지면 전체 늦어짐 (head-of-line blocking)

B. Branching (분기)

[A] → [B] ─→ [C1]
         └→ [C2]
  • 한 입력에서 여러 산출물 생성 (HLS + sprite + waveform)
  • 자세히는 03-fan-out-fan-in.md

C. Joining (병합)

[B1] ─┐
[B2] ─┴→ [C]
  • 여러 산출물이 모두 완료되어야 다음 단계 진행
  • 동기화가 까다롭다 (03 참조)

D. Cyclic / Conditional Retry

[A] → [B] → [C]
       ↑     ↓
       └─────┘ (조건부 재시도)
  • 일시 에러 시 같은 단계 재실행 — Step Functions의 Retry/Catch

2) 메시지 스키마 — Filter 사이 contract

각 Pipe를 흘러가는 메시지는 명시적 스키마여야 한다. 흔한 형태:

// 모든 단계 공통 필드
interface PipelineEvent {
  // 1. 식별
  pipelineId: string;       // 한 파이프라인 실행의 trace
  fileId: string;           // 처리 대상
 
  // 2. 멱등 — 04 참조
  idempotencyKey: string;   // 보통 contentHash 또는 (fileId + stage)
 
  // 3. 시간 추적
  enqueuedAt: ISO8601;      // 큐 진입 시각
  attemptNumber: number;    // 재시도 횟수
 
  // 4. 단계별 페이로드
  stage: 'probe' | 'transcode' | 'sprite' | 'db-update';
  payload: unknown;         // stage별 schema
}

스키마는 다음을 만족해야 한다:

  • Forward compatible — 새 필드 추가 시 기존 consumer 안 깨짐
  • Versioned — major 변경 시 version: 2로 분리 (또는 새 큐로 분리)
  • Validated at boundary — 메시지를 받자마자 스키마 검증, 실패 시 명시적 분류 (05)

3) “단계는 어디까지 쪼개야 하나”의 기준

너무 잘게 쪼개면 메시지 hop 비용 + 복잡도 폭주. 너무 굵으면 모놀리식 회귀.

분리해야 하는 신호합쳐도 되는 신호
시간 프로파일이 N배 차이 (probe 1초 vs transcode 10분)합쳐 1분 안에 끝남
메모리/CPU 프로파일이 다름같은 메모리 설정으로 충분
의존 외부 시스템이 다름 (S3-only vs MediaConvert vs DB)같은 외부 의존만 사용
실패 단위를 따로 가져가고 싶음한 단위로 같이 실패하는 게 자연스러움
다른 팀/모듈이 소유한 팀이 소유

→ 한 영상 변환 사례: probe+loudness+waveform은 한 람다, sprite는 다른 람다, DB update는 세 번째 람다. transcode는 람다가 아니라 MediaConvert. 4단계 = 의존 외부의 갯수.


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

1) 단일 람다 모놀리식의 함정 (앞서 본)

15분 timeout · 단일 retry policy · 부분 산출물 폐기.

2) “Filter가 stateful”이 되는 사고

// 안티패턴 — 모듈 전역 변수로 상태 보관
let cache = new Map<string, Buffer>();
 
export async function handler(event) {
  if (cache.has(event.fileId)) { /* ... */ }
  // cold start 후 다른 인스턴스는 캐시 못 봄
}

람다는 컨테이너 reuse가 보장되지 않는다. 캐시는 명시적 외부 저장(Redis, DynamoDB)으로.

3) “지휘자가 너무 똑똑해진다”

Step Functions 안에 비즈니스 로직을 넣기 시작하면 IaC가 비즈니스 코드처럼 되어 변경 비용 폭주. → 지휘자는 흐름만, Filter는 변환만.

4) “Pipe로 큰 페이로드를 직접 흘림”

SQS 256KB 한도, Kafka는 기본 1MB. 비디오 자체를 메시지에 넣으면 안 됨. → S3에 올리고 포인터(s3Key)만 메시지에 실어 보낸다 (“claim check pattern”).

5) “Choreography에서 누가 끝났는지 모름”

이벤트만 publish하면 “전체 파이프라인이 완료됐다”는 신호가 없다. → 종착 단계가 명시적으로 pipeline.completed 이벤트를 publish, 또는 별도 saga/state-tracker.


Insight — 흥미로운 이야기

“파이프 & 필터는 1973년 Doug McIlroy의 메모에서 시작했다”

Bell Labs의 Unix 그룹에서 McIlroy는 한 줄 메모를 썼다 — “We should have some ways of coupling programs like garden hose”. 다음 날 아침 Ken Thompson이 | 연산자를 구현했다. 50년 뒤 우리가 SQS+Lambda로 짜는 미디어 파이프라인은 그 메모의 분산·영속·비동기 버전일 뿐이다. 본질은 같다 — 작은 도구를 표준 입출력으로 잇는다.

“orchestration이 부활한 이유”

2010년대 microservices 붐에서 choreography가 유행했다 (“우리는 모놀리식이 아니다, EBus만 있으면 된다”). 그러나 5년 만에 다들 깨달았다 — 장애 디버깅이 너무 어렵다. 그래서 Temporal, Step Functions, Cadence 같은 orchestrator가 부활했다. 결론: 흐름 가시성이 비즈니스 critical할수록 orchestration 비중이 높아야 한다. 미디어 파이프라인은 사용자에게 “처리 중 X% / 실패” 같은 status를 보여줘야 하므로 orchestration 요소가 필수적.

“MediaConvert는 그 자체가 파이프라인 안의 파이프라인”

AWS MediaConvert는 한 Job 안에 다시 (분석 → split → 병렬 transcode → mux → manifest 생성)이라는 5단계가 들어있다. 우리가 짜는 파이프라인 안에 또 다른 매니지드 파이프라인이 들어가는 식. 추상화의 층이 생각보다 깊다.


요약 + Mermaid

요점 — 파이프 & 필터는 미디어 파이프라인의 골격이다. 단계는 시간·CPU·외부의존·실패단위·소유권 5축으로 쪼갠다. 흐름 의사결정은 orchestration(가시성)과 choreography(확장성) 사이의 trade-off를 hybrid로 푼다. 다음 문서(02)는 이 골격에서 “Pipe”의 디테일을 본다.