{
    "componentChunkName": "component---src-templates-blog-template-js",
    "path": "/Music_Pipeline_09/",
    "result": {"data":{"cur":{"id":"d43844ee-35e5-550c-8c4b-84d8b83c50da","html":"<h2 id=\"어느-날-애플리케이션이-뜨지-않았다\" style=\"position:relative;\"><a href=\"#%EC%96%B4%EB%8A%90-%EB%82%A0-%EC%95%A0%ED%94%8C%EB%A6%AC%EC%BC%80%EC%9D%B4%EC%85%98%EC%9D%B4-%EB%9C%A8%EC%A7%80-%EC%95%8A%EC%95%98%EB%8B%A4\" aria-label=\"어느 날 애플리케이션이 뜨지 않았다 permalink\" class=\"anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>어느 날 애플리케이션이 뜨지 않았다</h2>\n<p>M3 작업 중 bootRun 이 <code class=\"language-text\">Unknown database 'music_pipeline'</code> 으로 실패했다.\n어제까지 멀쩡했던 로컬 DB 다. 안에 들어가 보니:</p>\n<div class=\"gatsby-highlight\" data-language=\"sql\"><pre class=\"language-sql\"><code class=\"language-sql\"><span class=\"token keyword\">SHOW</span> <span class=\"token keyword\">DATABASES</span><span class=\"token punctuation\">;</span>\n<span class=\"token comment\">-- RECOVER_YOUR_DATA      ← ???</span>\n<span class=\"token comment\">-- information_schema</span>\n<span class=\"token comment\">-- mysql</span>\n<span class=\"token comment\">-- ...</span>\n<span class=\"token comment\">-- music_pipeline 이 없다</span></code></pre></div>\n<p><code class=\"language-text\">RECOVER_YOUR_DATA</code> 데이터베이스 안에는 이런 내용이 있었다.</p>\n<blockquote>\n<p>All your data was backed up by us. You must pay 0.013 bitcoin to …\nor in 48 hours, your data will be publicly disclosed and deleted.</p>\n</blockquote>\n<p>노출된 MySQL/MariaDB 를 스캔해서 약한 비밀번호로 로그인하고, DB 를 삭제한 뒤\n랜섬 노트를 남기는 자동화 봇의 전형적인 수법이다.</p>\n<h2 id=\"뚫린-경로\" style=\"position:relative;\"><a href=\"#%EB%9A%AB%EB%A6%B0-%EA%B2%BD%EB%A1%9C\" aria-label=\"뚫린 경로 permalink\" class=\"anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>뚫린 경로</h2>\n<p>거창한 취약점이 아니다. 조합이 문제였다.</p>\n<div class=\"gatsby-highlight\" data-language=\"yaml\"><pre class=\"language-yaml\"><code class=\"language-yaml\"><span class=\"token comment\"># docker-compose.yml (사고 전)</span>\n<span class=\"token key atrule\">ports</span><span class=\"token punctuation\">:</span>\n  <span class=\"token punctuation\">-</span> <span class=\"token string\">\"3307:3306\"</span>          <span class=\"token comment\"># = 0.0.0.0:3307, IPv6 [::]:3307 까지 바인딩</span>\n<span class=\"token key atrule\">environment</span><span class=\"token punctuation\">:</span>\n  <span class=\"token key atrule\">MARIADB_ROOT_PASSWORD</span><span class=\"token punctuation\">:</span> root   <span class=\"token comment\"># 개발용 \"임시\" 비밀번호</span></code></pre></div>\n<p>Docker 포트 매핑의 기본값은 <strong>모든 인터페이스</strong>(<code class=\"language-text\">0.0.0.0</code>)다. “로컬 개발용이니까”라고\n쓴 root/root 와 만나는 순간, 네트워크에서 접근 가능한 무인 DB 가 된다.\n공유기 NAT 뒤라고 안심할 수 없다 — IPv6 는 공인 주소가 직접 붙는 경우가 많고,\n같은 네트워크의 감염된 기기가 경유지가 되기도 한다.</p>\n<h2 id=\"대응\" style=\"position:relative;\"><a href=\"#%EB%8C%80%EC%9D%91\" aria-label=\"대응 permalink\" class=\"anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>대응</h2>\n<ol>\n<li><strong>몸값은 지불하지 않는다.</strong> 이런 봇은 대부분 백업 없이 삭제만 한다. 그리고 잃은 것은\n전부 재생성 가능한 로컬 샘플 데이터였다 — 결과적으로 피해 0.</li>\n<li><strong>모든 포트를 루프백 바인딩으로 변경.</strong> MariaDB 만이 아니라 Kafka(무인증 PLAINTEXT),\nRedis(무인증 — 노출 시 RCE 까지 가능) 전부.</li>\n</ol>\n<div class=\"gatsby-highlight\" data-language=\"yaml\"><pre class=\"language-yaml\"><code class=\"language-yaml\"><span class=\"token key atrule\">ports</span><span class=\"token punctuation\">:</span>\n  <span class=\"token punctuation\">-</span> <span class=\"token string\">\"127.0.0.1:3307:3306\"</span>\n  <span class=\"token punctuation\">-</span> <span class=\"token string\">\"127.0.0.1:9092:9092\"</span>\n  <span class=\"token punctuation\">-</span> <span class=\"token string\">\"127.0.0.1:16379:6379\"</span></code></pre></div>\n<ol start=\"3\">\n<li>오염된 볼륨은 폐기하고 재생성 — 공격자가 root 로 만졌던 데이터는 신뢰하지 않는다.\nFlyway 가 스키마를, 샘플 생성기 + 입수 API 가 데이터를 복원했다 (5,000 릴리스 / 50,000 트랙).</li>\n<li><code class=\"language-text\">mysql.user</code> 에 주입된 계정이 없는지 확인.</li>\n</ol>\n<p>복원이 몇 분에 끝난 것은 우연이 아니라 <strong>“파일이 아니라 재현 방법을 버전관리한다”</strong>\n원칙 덕이었다. 데이터가 전부 스크립트에서 재생성되는 구조면 랜섬은 협박이 못 된다.</p>\n<h2 id=\"배운-것\" style=\"position:relative;\"><a href=\"#%EB%B0%B0%EC%9A%B4-%EA%B2%83\" aria-label=\"배운 것 permalink\" class=\"anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>배운 것</h2>\n<ul>\n<li><strong>로컬 개발 환경도 공격 표면이다.</strong> “어차피 로컬”이라는 가정이 0.0.0.0 바인딩과\n기본 비밀번호를 정당화하는 순간 뚫린다.</li>\n<li>compose 로 띄우는 개발용 인프라의 기본기: 루프백 바인딩, 그리고 그걸 <strong>왜</strong> 하는지\n주석으로 남기기 (다음 사람이 편의로 되돌리지 않게).</li>\n<li>사고를 숨기지 않기로 했다 — 프로젝트 README 에 경위를 그대로 적었다.\n“무엇을 만들었나”만큼 “무엇이 뚫렸고 어떻게 복구했나”도 기록할 가치가 있다.</li>\n</ul>\n<hr>\n<div class=\"table-of-contents\">\n<ul>\n<li><a href=\"#%EC%96%B4%EB%8A%90-%EB%82%A0-%EC%95%A0%ED%94%8C%EB%A6%AC%EC%BC%80%EC%9D%B4%EC%85%98%EC%9D%B4-%EB%9C%A8%EC%A7%80-%EC%95%8A%EC%95%98%EB%8B%A4\">어느 날 애플리케이션이 뜨지 않았다</a></li>\n<li><a href=\"#%EB%9A%AB%EB%A6%B0-%EA%B2%BD%EB%A1%9C\">뚫린 경로</a></li>\n<li><a href=\"#%EB%8C%80%EC%9D%91\">대응</a></li>\n<li><a href=\"#%EB%B0%B0%EC%9A%B4-%EA%B2%83\">배운 것</a></li>\n</ul>\n</div>","excerpt":"어느 날 애플리케이션이 뜨지 않았다 M3 작업 중 bootRun 이  으로 실패했다.\n어제까지 멀쩡했던 로컬 DB 다. 안에 들어가 보니:  데이터베이스 안에는 이런 내용이 있었다. All your data was backed up by us. You must pay 0.013 bitcoin to …\nor in 48 hours, your data will be publicly disclosed and deleted. 노출된 MySQL/MariaDB 를 스캔해서 약한 비밀번호로 로그인하고, DB 를 삭제한 뒤\n랜섬 노트를 남기는 자동화 봇의 전형적인 수법이다. 뚫린 경로 거창한 취약점이 아니다. 조합이 문제였다. Docker 포트 매핑의 기본값은 모든 인터페이스()다. “로컬 개발용이니까”라고\n쓴 root/root 와 만나는 순간, 네트워크에서 접근 가능한 무인 DB 가 된다.\n공유기 NAT 뒤라고 안심할 수 없다 — IPv6 는 공인 주소가 직접 붙는 경우가 많고,\n같은 네트워크의…","frontmatter":{"date":"July 24, 2026","title":"음악 스트리밍 파이프라인 만들기 - 09. 번외, 로컬 Docker DB가 랜섬 공격당했다","categories":"Music_Pipeline","author":"shim8934","emoji":"🚨"},"fields":{"slug":"/Music_Pipeline_09/"}},"next":{"id":"aabb9630-b1e7-5303-8dd0-3c3828c61149","html":"<h2 id=\"선택-모듈--오디오를-실제로-흘려보내기\" style=\"position:relative;\"><a href=\"#%EC%84%A0%ED%83%9D-%EB%AA%A8%EB%93%88--%EC%98%A4%EB%94%94%EC%98%A4%EB%A5%BC-%EC%8B%A4%EC%A0%9C%EB%A1%9C-%ED%9D%98%EB%A0%A4%EB%B3%B4%EB%82%B4%EA%B8%B0\" aria-label=\"선택 모듈  오디오를 실제로 흘려보내기 permalink\" class=\"anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>선택 모듈 — 오디오를 실제로 흘려보내기</h2>\n<p>메타데이터와 집계만 있던 파이프라인에 실제 재생 경로를 붙였다.\n제약: 기존 코드 무변경 — <code class=\"language-text\">streaming/</code> 패키지와 <code class=\"language-text\">audio_asset</code> 테이블만 추가.</p>\n<p>원본 음원은 <strong>ffmpeg 합성 사인파</strong>다. 저작권 문제가 없고, 길이를 <code class=\"language-text\">track.duration_ms</code>\n와 정확히 일치시킬 수 있어 검증이 깨끗하며, 저장소엔 바이너리 대신 <strong>재현 방법</strong>을\n버전관리한다. 트랙마다 주파수를 trackId 에서 유도해서 “다른 트랙인데 같은 소리” 같은\n배선 오류를 귀로 잡을 수 있게 했다.</p>\n<h2 id=\"hls-구조--rfc-8216-을-코드에-대응시키기\" style=\"position:relative;\"><a href=\"#hls-%EA%B5%AC%EC%A1%B0--rfc-8216-%EC%9D%84-%EC%BD%94%EB%93%9C%EC%97%90-%EB%8C%80%EC%9D%91%EC%8B%9C%ED%82%A4%EA%B8%B0\" aria-label=\"hls 구조  rfc 8216 을 코드에 대응시키기 permalink\" class=\"anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>HLS 구조 — RFC 8216 을 코드에 대응시키기</h2>\n<div class=\"gatsby-highlight\" data-language=\"text\"><pre class=\"language-text\"><code class=\"language-text\">media-store/{trackId}/\n├─ master.m3u8          마스터 플레이리스트: \"무엇을 고를 수 있나\" (EXT-X-STREAM-INF)\n├─ v64k/playlist.m3u8   미디어 플레이리스트: \"어떤 순서로 재생하나\" (EXTINF, ENDLIST)\n├─ v64k/seg_000.ts ...  MPEG-TS 세그먼트 (AAC)\n└─ v128k/...</code></pre></div>\n<p>variant 인코딩·분할은 ffmpeg 에 맡기고 <strong>마스터 플레이리스트는 직접 썼다</strong> —\n<code class=\"language-text\">BANDWIDTH</code>(비트레이트 + TS 오버헤드)와 <code class=\"language-text\">CODECS</code>(“mp4a.40.2” = AAC-LC)가 어디서\n오는지 코드에 남는다. 플레이어는 이 두 값으로 다운로드 전에 variant 를 고른다.\nABR 의 선택 근거가 마스터에 선언돼 있다는 게 HLS 구조의 핵심이다.</p>\n<p>검증은 <strong>ffprobe 교차 검증</strong> — 우리가 만든 산출물을 우리가 아닌 도구가\n“AAC + MPEG-TS, 길이 N초”로 읽는지 확인한다. 생성기와 검증기가 같으면 순환 논증이다.</p>\n<h2 id=\"range-request206-와-hls-의-관계\" style=\"position:relative;\"><a href=\"#range-request206-%EC%99%80-hls-%EC%9D%98-%EA%B4%80%EA%B3%84\" aria-label=\"range request206 와 hls 의 관계 permalink\" class=\"anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>Range Request(206) 와 HLS 의 관계</h2>\n<p>같은 문제(“파일 일부만 전송”)를 <strong>자르는 주체가 다르다.</strong></p>\n<table>\n<thead>\n<tr>\n<th></th>\n<th>Range (프로그레시브)</th>\n<th>HLS</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>자르는 주체</td>\n<td>클라이언트, 바이트 단위</td>\n<td>서버, 미디어 단위(미리)</td>\n</tr>\n<tr>\n<td>탐색</td>\n<td><code class=\"language-text\">Range: bytes=N-</code> 재요청</td>\n<td>다른 세그먼트 요청</td>\n</tr>\n<tr>\n<td>화질 전환</td>\n<td>표현 불가</td>\n<td>variant 의 같은 시점 세그먼트</td>\n</tr>\n<tr>\n<td>CDN</td>\n<td>부분 요청이라 비효율</td>\n<td>세그먼트 = 완결된 정적 파일</td>\n</tr>\n</tbody>\n</table>\n<p>Spring 은 <code class=\"language-text\">ResponseEntity&lt;Resource></code> 를 반환하면 Range 를 자동 처리한다 —\n세그먼트에 <code class=\"language-text\">bytes=0-99</code> 를 보내면 206 + <code class=\"language-text\">Content-Range</code> 가 온다. HLS 플레이어는 안\n쓰지만 두 방식의 차이를 설명하는 살아 있는 데모가 된다.</p>\n<h2 id=\"사전-생성-vs-온디맨드--측정이-가설을-기각한-네-번째-사례\" style=\"position:relative;\"><a href=\"#%EC%82%AC%EC%A0%84-%EC%83%9D%EC%84%B1-vs-%EC%98%A8%EB%94%94%EB%A7%A8%EB%93%9C--%EC%B8%A1%EC%A0%95%EC%9D%B4-%EA%B0%80%EC%84%A4%EC%9D%84-%EA%B8%B0%EA%B0%81%ED%95%9C-%EB%84%A4-%EB%B2%88%EC%A7%B8-%EC%82%AC%EB%A1%80\" aria-label=\"사전 생성 vs 온디맨드  측정이 가설을 기각한 네 번째 사례 permalink\" class=\"anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>사전 생성 vs 온디맨드 — 측정이 가설을 기각한 네 번째 사례</h2>\n<p>패키징을 입수 후 미리 할 것인가, 첫 재생 요청 때 할 것인가. 측정을 위해\n<code class=\"language-text\">audio_asset</code> 테이블에 <code class=\"language-text\">packaging_ms</code>/<code class=\"language-text\">storage_bytes</code> 컬럼을 <strong>스키마 단계에</strong> 넣었다.</p>\n<p>실카탈로그 21트랙(평균 220초) 측정:</p>\n<table>\n<thead>\n<tr>\n<th>지표</th>\n<th align=\"right\">값</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>패키징 시간</td>\n<td align=\"right\">평균 <strong>5.4초</strong> (2.6~7.9초)</td>\n</tr>\n<tr>\n<td>스토리지</td>\n<td align=\"right\">5.51 MB/트랙 (비트레이트 역산과 일치)</td>\n</tr>\n<tr>\n<td>5만 트랙 외삽</td>\n<td align=\"right\">269 GB, 8워커 병렬 약 9.4시간</td>\n</tr>\n</tbody>\n</table>\n<p>가설은 “온디맨드도 1초쯤이면 쓸 만하다”였다. <strong>실측 5.4초로 기각.</strong> 음악 재생의 기대\n반응시간은 백 ms 대다. Zipf 분포로 “롱테일은 안 들으니 아끼자”는 온디맨드 옹호 논리도\n뒤집으면 “롱테일 청취자만 5초 페널티”다. <strong>사전 생성을 기본</strong>으로, 온디맨드는 같은\n준비 경로를 쓰므로 한계 비용 0 으로 보조·복구용으로 남겼다.</p>\n<h2 id=\"재사용된-패턴들\" style=\"position:relative;\"><a href=\"#%EC%9E%AC%EC%82%AC%EC%9A%A9%EB%90%9C-%ED%8C%A8%ED%84%B4%EB%93%A4\" aria-label=\"재사용된 패턴들 permalink\" class=\"anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>재사용된 패턴들</h2>\n<p>새 도메인인데 새 개념이 거의 없었다.</p>\n<ul>\n<li>동시 준비 선점: <code class=\"language-text\">UNIQUE(track_id)</code> INSERT 원자성 (M1 재처리 claim 패턴)</li>\n<li>상태 머신: PREPARING → READY | FAILED (ingest_failure 와 동일)</li>\n<li>ffmpeg 실행 중 트랜잭션을 잡지 않는다 (M1 IngestService 와 동일 — 외부 프로세스가 커넥션 풀을 고갈시킨다)</li>\n<li>path traversal 방어: normalize + startsWith (M1 입수 API 와 동일)</li>\n</ul>\n<p><strong>패턴의 재사용이 곧 설계 일관성</strong>이라는 게 이 모듈의 결론이다.</p>\n<p>상세: <a href=\"https://github.com/Shim8934/media-project/blob/main/docs/adr/0006-hls-pregeneration-vs-ondemand.md\">ADR-0006</a>,\n<a href=\"https://github.com/Shim8934/media-project/blob/main/docs/learning/02-hls-implementation-notes.md\">HLS 구현 노트</a></p>\n<hr>\n<div class=\"table-of-contents\">\n<ul>\n<li><a href=\"#%EC%84%A0%ED%83%9D-%EB%AA%A8%EB%93%88--%EC%98%A4%EB%94%94%EC%98%A4%EB%A5%BC-%EC%8B%A4%EC%A0%9C%EB%A1%9C-%ED%9D%98%EB%A0%A4%EB%B3%B4%EB%82%B4%EA%B8%B0\">선택 모듈 — 오디오를 실제로 흘려보내기</a></li>\n<li><a href=\"#hls-%EA%B5%AC%EC%A1%B0--rfc-8216-%EC%9D%84-%EC%BD%94%EB%93%9C%EC%97%90-%EB%8C%80%EC%9D%91%EC%8B%9C%ED%82%A4%EA%B8%B0\">HLS 구조 — RFC 8216 을 코드에 대응시키기</a></li>\n<li><a href=\"#range-request206-%EC%99%80-hls-%EC%9D%98-%EA%B4%80%EA%B3%84\">Range Request(206) 와 HLS 의 관계</a></li>\n<li><a href=\"#%EC%82%AC%EC%A0%84-%EC%83%9D%EC%84%B1-vs-%EC%98%A8%EB%94%94%EB%A7%A8%EB%93%9C--%EC%B8%A1%EC%A0%95%EC%9D%B4-%EA%B0%80%EC%84%A4%EC%9D%84-%EA%B8%B0%EA%B0%81%ED%95%9C-%EB%84%A4-%EB%B2%88%EC%A7%B8-%EC%82%AC%EB%A1%80\">사전 생성 vs 온디맨드 — 측정이 가설을 기각한 네 번째 사례</a></li>\n<li><a href=\"#%EC%9E%AC%EC%82%AC%EC%9A%A9%EB%90%9C-%ED%8C%A8%ED%84%B4%EB%93%A4\">재사용된 패턴들</a></li>\n</ul>\n</div>","frontmatter":{"date":"July 24, 2026","title":"음악 스트리밍 파이프라인 만들기 - 08. HLS 스트리밍, 온디맨드 패키징이 기각당한 이유","categories":"Music_Pipeline","author":"shim8934","emoji":"🎧"},"fields":{"slug":"/Music_Pipeline_08/"}},"prev":null,"site":{"siteMetadata":{"siteUrl":"https://shim8934.github.io","comments":{"utterances":{"repo":"Shim8934/shim8934.github.io"}}}}},"pageContext":{"slug":"/Music_Pipeline_09/","nextSlug":"/Music_Pipeline_08/","prevSlug":""}},
    "staticQueryHashes": ["1073350324","1956554647","2938748437"]}