음악 스트리밍 파이프라인 만들기 - 08. HLS 스트리밍, 온디맨드 패키징이 기각당한 이유
선택 모듈 — 오디오를 실제로 흘려보내기
메타데이터와 집계만 있던 파이프라인에 실제 재생 경로를 붙였다.
제약: 기존 코드 무변경 — streaming/ 패키지와 audio_asset 테이블만 추가.
원본 음원은 ffmpeg 합성 사인파다. 저작권 문제가 없고, 길이를 track.duration_ms
와 정확히 일치시킬 수 있어 검증이 깨끗하며, 저장소엔 바이너리 대신 재현 방법을
버전관리한다. 트랙마다 주파수를 trackId 에서 유도해서 “다른 트랙인데 같은 소리” 같은
배선 오류를 귀로 잡을 수 있게 했다.
HLS 구조 — RFC 8216 을 코드에 대응시키기
media-store/{trackId}/
├─ master.m3u8 마스터 플레이리스트: "무엇을 고를 수 있나" (EXT-X-STREAM-INF)
├─ v64k/playlist.m3u8 미디어 플레이리스트: "어떤 순서로 재생하나" (EXTINF, ENDLIST)
├─ v64k/seg_000.ts ... MPEG-TS 세그먼트 (AAC)
└─ v128k/...variant 인코딩·분할은 ffmpeg 에 맡기고 마스터 플레이리스트는 직접 썼다 —
BANDWIDTH(비트레이트 + TS 오버헤드)와 CODECS(“mp4a.40.2” = AAC-LC)가 어디서
오는지 코드에 남는다. 플레이어는 이 두 값으로 다운로드 전에 variant 를 고른다.
ABR 의 선택 근거가 마스터에 선언돼 있다는 게 HLS 구조의 핵심이다.
검증은 ffprobe 교차 검증 — 우리가 만든 산출물을 우리가 아닌 도구가 “AAC + MPEG-TS, 길이 N초”로 읽는지 확인한다. 생성기와 검증기가 같으면 순환 논증이다.
Range Request(206) 와 HLS 의 관계
같은 문제(“파일 일부만 전송”)를 자르는 주체가 다르다.
| Range (프로그레시브) | HLS | |
|---|---|---|
| 자르는 주체 | 클라이언트, 바이트 단위 | 서버, 미디어 단위(미리) |
| 탐색 | Range: bytes=N- 재요청 |
다른 세그먼트 요청 |
| 화질 전환 | 표현 불가 | variant 의 같은 시점 세그먼트 |
| CDN | 부분 요청이라 비효율 | 세그먼트 = 완결된 정적 파일 |
Spring 은 ResponseEntity<Resource> 를 반환하면 Range 를 자동 처리한다 —
세그먼트에 bytes=0-99 를 보내면 206 + Content-Range 가 온다. HLS 플레이어는 안
쓰지만 두 방식의 차이를 설명하는 살아 있는 데모가 된다.
사전 생성 vs 온디맨드 — 측정이 가설을 기각한 네 번째 사례
패키징을 입수 후 미리 할 것인가, 첫 재생 요청 때 할 것인가. 측정을 위해
audio_asset 테이블에 packaging_ms/storage_bytes 컬럼을 스키마 단계에 넣었다.
실카탈로그 21트랙(평균 220초) 측정:
| 지표 | 값 |
|---|---|
| 패키징 시간 | 평균 5.4초 (2.6~7.9초) |
| 스토리지 | 5.51 MB/트랙 (비트레이트 역산과 일치) |
| 5만 트랙 외삽 | 269 GB, 8워커 병렬 약 9.4시간 |
가설은 “온디맨드도 1초쯤이면 쓸 만하다”였다. 실측 5.4초로 기각. 음악 재생의 기대 반응시간은 백 ms 대다. Zipf 분포로 “롱테일은 안 들으니 아끼자”는 온디맨드 옹호 논리도 뒤집으면 “롱테일 청취자만 5초 페널티”다. 사전 생성을 기본으로, 온디맨드는 같은 준비 경로를 쓰므로 한계 비용 0 으로 보조·복구용으로 남겼다.
재사용된 패턴들
새 도메인인데 새 개념이 거의 없었다.
- 동시 준비 선점:
UNIQUE(track_id)INSERT 원자성 (M1 재처리 claim 패턴) - 상태 머신: PREPARING → READY | FAILED (ingest_failure 와 동일)
- ffmpeg 실행 중 트랜잭션을 잡지 않는다 (M1 IngestService 와 동일 — 외부 프로세스가 커넥션 풀을 고갈시킨다)
- path traversal 방어: normalize + startsWith (M1 입수 API 와 동일)
패턴의 재사용이 곧 설계 일관성이라는 게 이 모듈의 결론이다.