📁 File7. 파이프라인 이론03. Fan-out / Fan-in — 분기·병합 패턴, 합류 시 동기화

03. Fan-out / Fan-in — 분기·병합 패턴, 합류 시 동기화

이 문서가 답하는 질문: 한 입력에서 여러 산출물을 만들고 다시 합치려면 어떻게 하는가?


한 줄 답

Fan-out은 한 입력을 N개 분기로 흩뿌리는 것, fan-in은 N개 결과를 하나로 합치는 것이다. Fan-out은 쉽다 (이벤트 publish + 다수 subscriber). Fan-in이 어렵다 — N개가 모두 끝났다는 사실을 누가, 어디에서 알 것인가가 핵심. 도구 선택은 “분기 갯수가 정적이냐 동적이냐”로 갈린다 — 정적이면 EventBridge 라우팅, 동적이면 Step Functions Map.


Why — 왜 fan-out이 필요한가

미디어 한 파일은 보통 여러 산출물을 동시에 필요로 한다.

업로드된 비디오 1개 → HLS 매니페스트 + thumbnail sprite + waveform + metadata.json + DB row
                    └── HLS도 다시 360p / 720p / 1080p로 fan-out

만약 이걸 linear pipeline으로 짜면:

문제:

  1. 순차 → 총 시간 = sum. 병렬이면 max로 단축 가능.
  2. 부분 실패가 전체 실패. sprite 실패가 waveform 실패를 만든다.
  3. 단계 의존성이 인위적. waveform은 transcode와 무관한데 왜 뒤에 있는가.

Fan-out으로 풀면 시간은 max, 부분 실패는 부분 손실로 격리.


How — Fan-out과 Fan-in의 실제 패턴

1) Fan-out 패턴 4가지

A. EventBridge Rule fan-out (정적, 선언적)

한 이벤트를 여러 룰이 매칭해서 각자 다른 큐/람다로 보냄.

  • 분기 조건: 메시지 attribute(detail-type, status 등)
  • 새 분기 추가: 새 rule만 추가, 기존 코드 무변경
  • 한계: 분기 갯수가 사전에 정해져 있어야 함 (정적)

B. SNS topic + multi-subscriber fan-out

같은 메시지를 여러 SQS/Lambda/HTTP가 동시에 받음.

  • EventBridge보다 라우팅 능력 약함 (filter는 있지만 단순)
  • 모든 subscriber가 같은 메시지 받음 (동일 페이로드)
  • pre-EventBridge 시대의 패턴 — 지금은 EventBridge 선호

C. Step Functions Map (동적 분기)

런타임에 결정되는 N개 항목을 병렬 처리.

// Step Functions ASL
{
  "Type": "Map",
  "ItemsPath": "$.segments",          // 동적으로 결정되는 배열
  "MaxConcurrency": 10,
  "Iterator": { "Type": "Task", "Resource": "...lambda..." }
}
  • 분기 갯수가 입력 데이터에 따라 다를 때 (예: 영상을 N segment로 쪼갬)
  • 동시성 제한, 부분 실패 시 자동 retry, 완료 집계 모두 자동
  • 단점: Step Functions state transition 비용

D. Producer가 직접 N개 publish (코드 fan-out)

const segments = await splitVideo(video);
await Promise.all(segments.map(s => sqs.send({ /* ... */ })));
  • 가장 단순, 동적 갯수 OK
  • 부분 실패 시 어느 segment까지 publish됐는지 추적 필요 → 멱등성 + 재실행으로 풀어야 함
  • 작은 N에 적합

2) Fan-in 패턴 — “모두 끝났다”를 누가 아는가

Fan-out은 쉽지만 fan-in은 항상 어렵다. 핵심 질문:

N개 단계 중 마지막 하나가 끝났음을 누가, 어디에서 알 것인가?

A. Counter pattern (DB / Redis)

각 단계가 끝날 때 카운터 +1, 카운터 == N이면 다음 단계 호출.

  • 단순. 그러나 race condition 위험 — INCR 후 read 사이에 다른 INCR이 들어올 수 있음.
  • atomic INCR + 결과값 체크로 풀어야 함:
    INCR pipeline:abc:done
    → 결과가 N이면 호출
  • DB 사용 시 transaction 필요.

B. Step Functions Parallel / Map 자동 join

{
  "Type": "Parallel",
  "Branches": [
    { "StartAt": "HLS", "States": { /* ... */ } },
    { "StartAt": "Sprite", "States": { /* ... */ } },
    { "StartAt": "Waveform", "States": { /* ... */ } }
  ],
  "Next": "Aggregate"
}
  • Step Functions가 자동으로 모든 branch 완료를 기다린 뒤 Next로 진행
  • 한 branch라도 실패하면 전체 실패 (catch로 부분 성공 허용 가능)
  • 가장 깔끔하지만 SFN 비용

C. Saga pattern (코레오그래피 fan-in)

각 단계가 자기 완료 이벤트를 publish, 수집자(saga) 가 모든 이벤트를 보고 상태 추적.

  • 각 단계가 끝나면 pipelineId, stage, status 이벤트 발행
  • Saga가 상태를 누적, “모두 done”이 되면 다음 트리거
  • 유연하지만 saga 자체의 가용성/일관성 부담

D. “Eventually consistent” — fan-in을 안 함

각 단계가 자기 산출물을 자기가 DB에 기록. 사용자가 “모두 준비됐는지”를 polling하거나, frontend가 산출물 별 fallback 처리.

  • 가장 간단. 미디어 파이프라인에서 흔함.
  • 사용자에게 “0%, 33%, 66%, 100%” 같은 progressive 피드백 가능.
  • “전체 완료” 알림이 정말 필요한 경우만 saga.

→ 흔한 영상 변환 사례는 D에 가깝다 — Lambda①·②가 각자 status 메시지를 보내고 Lambda③이 누적. 산출물 테이블 UPSERT로 자연스러운 fan-in.


What — 도구 매트릭스

1) Fan-out 도구 비교

도구동적 갯수라우팅 능력비용적합
EventBridgeX (정적 룰)강력 (JSON 패턴)발행당 1M / $1정적 분기, 다수 룰
SNS topicX약함 (filter policy)100M req / $0.5단순 broadcast
Step Functions MapO무관state transition당동적 갯수, 동시성 제한
코드 fan-outO직접 짜야publish 비용만작은 N, 단순

2) Fan-in 도구 비교

패턴일관성복잡도부분 실패 처리적합
Counter (Redis INCR)strong (atomic)낮음직접 짜야작은 fan-out
Counter (DB row)strong (transaction)중간DB와 결합DB 이미 있을 때
Step Functions Parallelstrong낮음자동 catch정적 N
Step Functions Mapstrong낮음자동 + retry동적 N
Saga / state trackereventual높음직접 짜야복잡한 워크플로
Eventually consistenteventual매우 낮음자연스럽게 부분 성공미디어 산출물

3) 미디어 파이프라인의 흔한 토폴로지

이 그림에서:

  • Fan-out 1: probe → MC + waveform + loudness (병렬 시작)
  • Fan-out 2: MC 안에서 360p/720p/1080p로 segment 분할 (MC 내부)
  • Fan-out 3: EventBridge가 COMPLETE/ERROR로 라우팅
  • Fan-in: 각 산출물이 status queue로 보내고, DB 갱신 lambda가 artifacts UPSERT로 누적

→ “전체 완료” 신호는 명시적으로 만들지 않는다. medias.status가 COMPLETE인지 + 필요한 artifacts가 다 있는지로 정의.


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

1) “Counter pattern을 non-atomic하게 짬”

// 안티패턴 — race condition
const count = await db.read('counter');
await db.write('counter', count + 1);
if (count + 1 === N) trigger();  // 두 worker가 동시에 N번째일 수 있음

→ atomic INCR 또는 DB transaction 필수. Redis는 INCR이 원자적, DynamoDB는 conditional update.

2) “Step Functions에 너무 많은 비즈니스 로직”

ASL JSON에 if/else/loop을 잔뜩 넣으면 IaC가 비즈니스 코드가 됨 → 변경 비용 폭주. → Step Functions는 흐름만, 로직은 Lambda 안에.

3) “Fan-out 후 한 분기 실패 = 전체 실패로 처리”

await Promise.all([hls(), sprite(), waveform()]);
// 하나라도 throw면 전체 throw

미디어 파이프라인에서 sprite 하나 실패가 HLS까지 폐기시킬 이유가 없다. → Promise.allSettled() + 부분 산출물 허용 + 사용자에게는 “available + missing” 노출.

4) “EventBridge rule 너무 광범위”

{ "source": ["aws.mediaconvert"] }  // 이 계정의 모든 MC 이벤트 매칭

다른 팀의 MC 작업까지 받아 잘못된 큐에 들어감. → detail.queue 등으로 자기 큐만 필터링.

5) “Saga가 비대해짐”

Saga가 모든 단계의 모든 상태를 알게 되면 결국 모놀리식. → saga는 흐름 상태만, 데이터는 각 단계 소유.

6) “MaxConcurrency 안 잡고 Step Functions Map”

{ "Type": "Map", "ItemsPath": "$.items" }  // 동시성 무제한

10000개 segment가 동시에 Lambda 호출 → throttling 또는 다운스트림 폭발. → MaxConcurrency: 10 같은 제한 필수.

7) “Fan-in 시점에 데이터 일관성 가정”

병렬 단계가 끝난 직후에 DB에서 산출물 읽으려 하면 read replica lag 만날 수 있음. → 메시지 안에 산출물 포인터를 직접 실어 보내거나, primary read.


Insight — 흥미로운 이야기

“Map-Reduce는 fan-out/fan-in의 원형이다”

Google의 Map-Reduce(2004) 논문이 사실상 fan-out/fan-in의 정식화다 — Map 단계가 fan-out, Reduce 단계가 fan-in. 20년 뒤 Step Functions Map state는 이름 그대로 그 상속. Hadoop, Spark, Beam 모두 이 추상화의 후예. 미디어 파이프라인에서 한 파일을 N개 segment로 쪼개 병렬 transcode하는 것은 Map-Reduce 그 자체다.

“MediaConvert는 fan-out/fan-in을 외주받는 매니지드 서비스”

AWS MediaConvert가 비싼 이유 — 한 Job 안에서 자동으로 (a) 입력 분석, (b) segment 분할, (c) 병렬 워커에 분산, (d) 인코딩, (e) 결과 합쳐 manifest 작성을 한다. 이걸 직접 짜면 Step Functions + Lambda + S3로 큰 코드가 된다. minute당 비용이 비싼 만큼 fan-out/fan-in의 운영 부담을 통째로 가져간다.

“Choreography의 fan-in은 결국 ‘eventual consistency를 받아들이자’다”

정확한 fan-in을 코레오그래피로 구현하는 것은 saga + 카운터 + 트랜잭션 등 큰 인프라가 필요하다. 미디어 파이프라인 같은 사용자 시나리오는 eventual fan-in이면 충분 — 사용자가 화면을 새로고침하면 더 많은 산출물이 보이는 식. 강한 동기화는 정말 필요할 때만.

“Pinterest의 fan-out 사고 (2013)”

Pinterest가 follower fan-out을 했다 — 한 사람이 핀을 만들면 follower 1만 명의 timeline에 fan-out write. 한 명이 슈퍼스타가 되면 fan-out이 polynomial하게 폭발했다. 결국 hybrid로 전환 — 보통 사용자는 fan-out, 슈퍼스타는 read-time merge. 미디어 파이프라인도 마찬가지 — fan-out N이 무한히 커질 수 있으면 항상 limit 필요.


요약 + Mermaid

요점 — Fan-out은 쉽고 fan-in이 어렵다. 도구는 “분기가 정적/동적”으로 갈리고 (정적 → EventBridge, 동적 → Step Functions Map), fan-in은 “강한 동기 vs eventual”의 결정이다. 미디어 파이프라인은 대부분 eventual fan-in으로 충분 — 각 단계가 자기 산출물을 DB에 UPSERT하면 자연스러운 fan-in이 만들어진다. 다음 문서(04)는 이 모든 것이 의존하는 멱등성을 본다.