06 — Segmentation & Manifest 디자인
이 문서가 답하는 질문: segment는 몇 초가 정답인가? 그리고 GOP·키프레임 정렬은 왜 반드시 필요한가? 핵심 한 줄: segment 길이는 지연과 효율의 trade-off다. 그리고 segment 경계는 반드시 IDR이어야 한다 — 이 한 가정이 깨지면 모든 ABR 전환이 깨진다.
한 줄 답 (Pyramid Top)
Segment 길이는 짧을수록 지연 ↓ 오버헤드 ↑, *길수록 지연 ↑ 효율 ↑*의 단순한 trade-off다. 그리고 모든 segment의 첫 프레임은 IDR이어야 한다 — 그래야 다른 quality variant로 자유롭게 전환할 수 있다 (GOP-aligned).
Why — 왜 segment 길이가 결정 변수인가
스트리밍의 거의 모든 운영 결정이 segment 길이에서 갈린다.
| 영향 | 짧은 segment (2s) | 긴 segment (10s) |
|---|---|---|
| 라이브 지연 | 6~10s | 25~40s |
| HTTP 요청 수 (1시간 영상) | 1800회 | 360회 |
| 매니페스트 크기 | 큼 | 작음 |
| GOP 압축 효율 | 낮음 (IDR 자주) | 높음 |
| 시크 정밀도 | 좋음 | 거침 |
| CDN 캐시 hit | 잘 됨 (작은 객체 → edge friendly) | 잘 됨 (요청 적음) |
| 에러 발생 시 영향 | 한 segment만 누락 (작음) | 한 segment 누락 = 10초 손실 |
→ 짧으면 6배 더 많은 HTTP 요청, 길면 4배 더 많은 라이브 지연. 그래서 모두 중간(4~6초)에 수렴.
How — segment 길이별 실제 trade-off
2초 segment (라이브 빠름, VOD 흔치 않음)
- 라이브 지연:
610초 (LL 없이도) - IDR 간격 = 2초 = 60 frames @30fps
- H.264 비트레이트가 5~10% 증가 (IDR가 자주 → 압축 효율 ↓)
4초 segment
- 라이브 지연:
1216초 - 균형 — Twitch 등 빠른 라이브 OTT가 사용
- IDR 간격 = 4초 = 120 frames
6초 segment (Apple HLS Authoring 권장)
- 라이브 지연:
1824초 - IDR 간격 = 6초 = 180 frames @30fps (또는 180~191 frame, 29.97fps)
- VOD/일반 라이브 OTT의 default
- HLS 기본값, 대다수의 인코더 preset
10초 segment (오래된 HLS, MPEG-TS)
- 라이브 지연:
3040초 - IDR 간격 = 10초 → 압축 효율 ↑
- AES-128 키 회전과 잘 맞음 (10s 단위)
- 신규 디자인에선 거의 사용 안 함
How — GOP-aligned segmentation (가장 중요한 contract)
왜 모든 segment 첫 프레임이 IDR이어야 하나
비디오는 frame 사이의 예측으로 압축된다 (P-frame은 이전 frame을 참조, B-frame은 양방향). IDR(Instantaneous Decoder Refresh) 만이 자기 자신만으로 디코딩 가능한 frame.
ABR이 720p → 1080p로 전환할 때:
- 720p의 segment N까지 디코딩 완료.
- 1080p의 segment N+1을 받기 시작.
- 1080p segment N+1의 첫 frame이 IDR이 아니면 — 디코더는 없는 reference frame을 참조하려 한다 → 디코딩 실패 → 화면이 깜빡거나 멈춘다.
인코더 설정 (ffmpeg 예시)
ffmpeg -i input.mp4 \
-c:v libx264 \
-g 180 # GOP size = 180 frames (= 6초 @ 30fps)
-keyint_min 180 # 최소 IDR 간격 = 180 (강제)
-force_key_frames "expr:gte(t,n_forced*6)" # 정확히 6초마다 IDR
-sc_threshold 0 # scene-change 자동 IDR 비활성 (의도치 않은 IDR 방지)
-hls_time 6 \
-hls_segment_type fmp4 \
output/index.m3u8| 옵션 | 의미 |
|---|---|
-g 180 | GOP size 고정 |
-keyint_min 180 | IDR 사이 최소 간격 (보장) |
-force_key_frames "expr:gte(t,n_forced*6)" | 6초 경계 시점마다 강제 IDR — segment 경계에 정확히 맞음 |
-sc_threshold 0 | scene change 감지 IDR 비활성화 (이게 켜져 있으면 임의 시점에 IDR이 추가되어 segment 경계와 어긋남) |
모든 variant가 같은 -force_key_frames를 써야 segment 경계가 정확히 같은 시점. 이게 segmentAlignment="true"(DASH) / EXT-X-INDEPENDENT-SEGMENTS(HLS)의 전제.
MediaConvert / Bento4 / Shaka Packager의 동치 설정
| 도구 | GOP-aligned segmentation 옵션 |
|---|---|
| AWS MediaConvert | ”GOP Size” + “Segment Length” + “Cadence Adapt” |
| Bento4 (mp4fragment) | --fragment-duration 6000 |
| Shaka Packager | --segment_duration 6 (그리고 인코더가 이미 IDR을 정렬했다고 가정) |
| ffmpeg HLS muxer | -hls_time 6 (실제 GOP가 정렬되어야 정확하게 작동) |
→ packager는 자르기만 한다. IDR 정렬은 인코더가 보장해야 한다.
What — Discontinuity (HLS의 segment 끊어내기)
서로 다른 인코딩 세션·codec·해상도의 segment를 한 매니페스트에 이어 붙여야 할 때 (광고 삽입, 재인코딩, 라이브 stitching).
#EXTM3U
#EXT-X-VERSION:7
#EXT-X-TARGETDURATION:6
#EXT-X-MEDIA-SEQUENCE:0
#EXT-X-DISCONTINUITY-SEQUENCE:0
#EXTINF:6.0,
content_001.m4s
#EXTINF:6.0,
content_002.m4s
#EXT-X-DISCONTINUITY ← 여기서부터 새 인코딩
#EXT-X-PROGRAM-DATE-TIME:2026-05-10T10:30:00Z
#EXT-X-MAP:URI="ad_init.mp4" ← 새 init segment
#EXTINF:5.0,
ad_001.m4s
#EXTINF:5.0,
ad_002.m4s
#EXTINF:5.0,
ad_003.m4s
#EXT-X-DISCONTINUITY ← 광고 끝 → 본편 복귀
#EXT-X-MAP:URI="content_init.mp4"
#EXTINF:6.0,
content_003.m4s
...| 태그 | 효과 |
|---|---|
#EXT-X-DISCONTINUITY | 다음 segment에서 디코더 완전 reset (codec/해상도 변경 가능) |
#EXT-X-MAP | 새 init segment 지정 |
#EXT-X-PROGRAM-DATE-TIME | wall-clock 표시 (광고 추적용) |
→ DASH의 Period 경계와 동일 개념. CMAF에서는 Period 경계 = HLS의 Discontinuity 경계가 자연스럽게 일치한다.
#EXT-X-DISCONTINUITY-SEQUENCE
라이브에서 sliding window가 굴러가다 과거 discontinuity가 잘려 나가면, 플레이어가 누적 discontinuity를 잃는다 → EXT-X-DISCONTINUITY-SEQUENCE가 그 누적 카운트.
What — segment 파일 명명과 운영 주의
명명 규칙 (운영 친화적)
private/{contentId}/hls/
├── master.m3u8
├── 1080p/
│ ├── index.m3u8
│ ├── init.mp4
│ ├── seg_00001.m4s
│ ├── seg_00002.m4s
│ └── ...
├── 720p/
│ ├── index.m3u8
│ ├── init.mp4
│ └── seg_*.m4s
└── ...변형 1: master_variant_360p_00001.ts — 한 디렉터리에 평탄화 (AWS MediaConvert HLS group의 default).
변형 2: 위처럼 resolution별 디렉터리 — 운영 시 s3 ls 직관적.
핵심 contract (운영 측면)
- Variant naming convention은 후속 파이프라인의 contract가 된다. 예: thumbnail sprite를 360p variant에서만 추출한다면 —
master_variant_360p_*.ts라는 prefix가 코드의 가정이 된다. 이름 규칙을 바꾸면 후속 단계가 깨진다. - init segment URL은 한 영상의 평생 동안 불변이어야 한다. CDN 캐시 정책에 영구 캐시 + immutable 권장.
- segment URL은 한 영상의 평생 동안 불변. cache-control: max-age=31536000 (1년).
- 매니페스트 URL은 짧은 캐시. cache-control: max-age=2
10 (라이브) / max-age=3003600 (VOD).
What — 매니페스트 캐시 정책 (CDN 측)
| 자원 | TTL | 이유 |
|---|---|---|
master.m3u8 (VOD) | 1h ~ 1d | 한 번 만들어지면 거의 안 바뀜 |
master.m3u8 (Live) | 2~5s | sliding window |
index.m3u8 (VOD variant) | 1h ~ 1d | 동일 |
index.m3u8 (Live variant) | 2~5s | sliding window |
init.mp4 | immutable, 1y | 평생 불변 |
seg_*.m4s (VOD) | immutable, 1y | 한 번 발행 후 불변 |
seg_*.m4s (Live) | immutable, 1y | 발행 후 불변 (window에서 빠질 뿐) |
CloudFront/Akamai 모두 매니페스트만 짧은 TTL, segment는 영원에 가까운 TTL.
What-if — 잘못 만들면 어떻게 깨지는가
| 증상 | 원인 | 처방 |
|---|---|---|
| ABR 전환 시 화면 깜빡 | GOP 미정렬 (variant마다 IDR 위치 다름) | 모든 variant에 동일한 force_key_frames |
| 시크가 segment 경계로 튐 | INDEPENDENT-SEGMENTS 플래그 없음 | 매니페스트에 #EXT-X-INDEPENDENT-SEGMENTS 추가 |
| 마지막 segment가 짧아 가끔 stall | EXTINF 정수 반올림 | EXTINF를 소수점 6자리까지 정확히 |
| 광고 삽입 후 디코더 죽음 | Discontinuity 누락 | #EXT-X-DISCONTINUITY + 새 #EXT-X-MAP |
| 라이브 sliding window 갱신 시 segment URL 충돌 | sequence 카운터 reset | 절대 reset 금지, monotonic 유지 |
| segment 길이 다양 → buffer 예측 어려움 | 가변 GOP (scene change IDR) | -sc_threshold 0 |
| CDN miss 폭주 | 매니페스트 TTL이 너무 짧고 변경이 잦음 | 매니페스트 갱신 주기 = TTL 일치 |
| light variant init이 재발행되어 4xx 폭주 | 인코딩 재시작 시 init MD5 변경 | init은 한 번 발행 후 재인코딩하지 않음 |
Insight — 흥미로운 이야기
“왜 6초가 default인가”
Apple HLS Authoring Spec(2009 초기)이 10초로 시작 → 2016년 6초로 권장 변경. 이유는 셋:
- 모바일 기기의 디코더 성능이 좋아져 IDR을 자주 박아도 압축 비용 부담이 줄었다.
- 시크 응답이 더 중요해졌다 (모바일 short-form의 시크 빈도 ↑).
- 라이브 지연이 더 중요해졌다 (스포츠 OTT 대두). 4초 가도 되지만 압축 효율 손실이 6초 대비 명백.
“GOP-aligned는 단순한 contract지만 깨면 우주가 무너진다”
GOP 정렬 한 번 잘못되면 — ABR 전환 깜빡, 시크 실패, 광고 삽입 디코더 크래시, 자막 sync 어긋남 — 모든 증상이 한 원인에서 나온다. 운영 incident의 근본 원인 중 가장 흔한 것.
“광고 삽입은 사실 Discontinuity로 짠 곡예다”
SCTE-35 marker가 광고 삽입 시점을 알려주면, manifest manipulator(server-side)가 그 시점에
#EXT-X-DISCONTINUITY를 박고 광고 segment를 끼워넣는다. 이게 server-side ad insertion (SSAI). 반대로 client-side ad insertion (CSAI)은 플레이어가 영상 일시정지하고 광고 따로 재생. SSAI가 광고 차단 우회에 강한 이유는 광고와 본편이 같은 stream에 섞여 있어 구분이 어렵기 때문.
“Frame-accurate seek는 단순한 게 아니다”
segment 경계는 IDR이지만 그 사이는 P/B-frame이다. 플레이어가 segment 중간 시각에 시크하려면 segment를 처음부터 디코딩해서 그 시각까지 progress해야 한다. 그래서 시크의 최악 지연은 segment 길이만큼. 6초 segment면 최악 6초 디코딩이 필요. 빠른 시크가 중요하면 segment를 짧게 — 또는 trick play용 I-frame only playlist(
#EXT-X-I-FRAME-STREAM-INF)를 추가.
한 단락 요약 + Mermaid
Segmentation은 길이와 경계 정렬의 두 결정이다. 길이는 4~6초가 default — 짧으면 지연/오버헤드, 길면 효율/지연 trade-off. 경계는 반드시 모든 variant에서 IDR로 정렬 — 이 한 가정이 ABR/시크/광고 삽입 전체의 contract. 그리고 segment URL은 영원히 불변, 매니페스트만 짧은 TTL — 캐시 정책의 핵심.
→ 다음 파일 07-drm.md: 만들어진 segment를 누가 풀어주는가.