Music_Pipeline
9 posts
음악 스트리밍 파이프라인 만들기 - 09. 번외, 로컬 Docker DB가 랜섬 공격당했다

어느 날 애플리케이션이 뜨지 않았다 M3 작업 중 bootRun 이 으로 실패했다. 어제까지 멀쩡했던 로컬 DB 다. 안에 들어가 보니: 데이터베이스 안에는 이런 내용이 있었다. All your data was backed up by us. You must pay 0.013 bitcoin to … or in 48 hours, your data will be publicly disclosed and deleted. 노출된 MySQL/MariaDB 를 스캔해서 약한 비밀번호로 로그인하고, DB 를 삭제한 뒤 랜섬 노트를 남기는 자동화 봇의 전형적인 수법이다. 뚫린 경로 거창한 취약점이 아니다. 조합이 문제였다. Docker 포트 매핑의 기본값은 모든 인터페이스()다. “로컬 개발용이니까”라고 쓴 root/root 와 만나는 순간, 네트워크에서 접근 가능한 무인 DB 가 된다. 공유기 NAT 뒤라고 안심할 수 없다 — IPv6 는 공인 주소가 직접 붙는 경우가 많고, 같은 네트워크의…

July 24, 2026
Music_Pipeline
음악 스트리밍 파이프라인 만들기 - 08. HLS 스트리밍, 온디맨드 패키징이 기각당한 이유

선택 모듈 — 오디오를 실제로 흘려보내기 메타데이터와 집계만 있던 파이프라인에 실제 재생 경로를 붙였다. 제약: 기존 코드 무변경 — 패키지와 테이블만 추가. 원본 음원은 ffmpeg 합성 사인파다. 저작권 문제가 없고, 길이를 와 정확히 일치시킬 수 있어 검증이 깨끗하며, 저장소엔 바이너리 대신 재현 방법을 버전관리한다. 트랙마다 주파수를 trackId 에서 유도해서 “다른 트랙인데 같은 소리” 같은 배선 오류를 귀로 잡을 수 있게 했다. HLS 구조 — RFC 8216 을 코드에 대응시키기 variant 인코딩·분할은 ffmpeg 에 맡기고 마스터 플레이리스트는 직접 썼다 — (비트레이트 + TS 오버헤드)와 (“mp4a.40.2” = AAC-LC)가 어디서 오는지 코드에 남는다. 플레이어는 이 두 값으로 다운로드 전에 variant 를 고른다. ABR 의 선택 근거가 마스터에 선언돼 있다는 게 HLS 구조의 핵심이다. 검증은 ffprobe 교차 검증 — 우리가 만든 산출…

July 24, 2026
Music_Pipeline
음악 스트리밍 파이프라인 만들기 - 07. N+1, 쿼리 수만 세면 fetch join에 속는다

문제를 일부러 만들다 이전 직장에서 N+1 로 60~90초 걸리던 조회를 개선한 경험이 있는데, MyBatis 환경이었다. JPA 환경에서 같은 문제를 재현하고 개선 전략을 수치로 비교하고 싶었다. 앨범 목록 API(앨범 + 대표 아티스트명 + 트랙 목록)를 만들었다. Album→Artist(ToOne), Album→Tracks(OneToMany) 모두 LAZY. 리포지토리는 하나 — 코드 어디에도 반복 조회가 없다. 그런데: 쿼리는 리포지토리가 아니라 DTO 변환 중(지연 로딩 접근)에 나간다. 이게 N+1 이 늦게 발견되는 이유다. MyBatis 는 resultMap 의 collection 쿼리가 XML 에 보이는데, JPA 는 한 줄이 조용히 SELECT 를 만든다. 그리고 응답은 완전히 정확하다 — N+1 은 틀린 결과가 아니라 느린 결과라서 기능 테스트로는 절대 안 잡힌다. 측정은 Hibernate Statistics()로 했다. “느린 것 같다”가 아니라 쿼리 수를 …

July 24, 2026
Music_Pipeline
음악 스트리밍 파이프라인 만들기 - 06. Redis 캐시와 스탬피드, 그리고 캐시가 무의미했던 순간

문제 집계 테이블 기반 “실시간 TOP 100 차트” API. 모든 사용자가 같은 것을 본다 — 전형적인 읽기 편중 + 핫키 하나 워크로드다. 결정이 세 겹이었다. 결정 1 — 차트를 어디서 만드나: Redis Sorted Set 기각 “Redis 차트면 ZINCRBY + ZREVRANGE 지” 싶었는데, 기각했다. 결정타는 멱등성이다. 컨슈머가 같은 배치를 재처리하면 DB 는 로 걸러내지만 ZINCRBY 는 그대로 두 번 더해진다. 04편에서 세운 멱등성 체계 밖에 비원자 이중 쓰기 경로를 만드는 구조다. Redis 에도 중복 판정을 만들면 되지만, 그건 같은 체계를 두 번 구현하는 것이다. DB 집계(TOP 100 쿼리) + 캐시로 갔다. 설계 결정은 독립적이지 않다 — 앞의 결정이 뒤의 선택지를 제약한다. 결정 2 — Look-aside Write-through 는 기각: 쓰기 주체(컨슈머, 초당 수백 건)와 읽기 주체(차트 API)가 달라서, 아무도 안 읽는 사이에도 집계…

July 24, 2026
Music_Pipeline
음악 스트리밍 파이프라인 만들기 - 05. DLQ와 poison pill, 그리고 세 번의 삽질

문제 깨진 JSON 메시지 하나가 파티션에 들어오면 무슨 일이 벌어지나. 무한 재시도 설정이라면 그 메시지가 파티션 전체를 막는다(head-of-line blocking). 뒤에 쌓인 정상 메시지 수만 건이 한 건 때문에 처리되지 않는다. 이게 poison pill 이다. 설계 — 실패를 두 종류로 가른다 M1 입수에서 “재시도 가능(DB 장애) vs 불가(검증 실패)“를 갈랐던 원칙을 그대로 재적용했다. 실패 유형 판단 처리 역직렬화 실패 백 번 다시 읽어도 같은 바이트는 같은 실패 재시도 없이 즉시 DLQ 처리 실패 (DB 등) 일시적일 수 있다 지수 backoff(0.5s→1s→2s) 3회 → DLQ 가 역직렬화 실패를 예외가 아니라 “값”(null + 원인 헤더)으로 바꿔준다 — 리스너까지 도달해야 우리가 제어권을 갖는다. 고정 간격 재시도는 기각 — DB 가 과부하로 느려진 상황에서 같은 속도로 계속 두드리면 회복을 방해한다. 지수 backoff 는 시스템에 숨 쉴 틈을…

July 24, 2026
Music_Pipeline
음악 스트리밍 파이프라인 만들기 - 04. Kafka 컨슈머 멱등성, 중복은 버그가 아니라 전제다

문제 재생 이벤트(초당 수백 건)를 소비해 곡별 재생수를 집계한다. 재생수는 카운터다 — 두 번 더하면 그냥 틀리고, 틀렸다는 사실조차 알기 어렵다. 그리고 Kafka 는 at-least-once 다. 리밸런싱, 오프셋 커밋 전 종료, 운영자의 오프셋 리셋, 초과 — 컨슈머는 정상 동작 중에도 같은 메시지를 다시 받는다. 중복 소비는 장애가 아니라 전제 조건이다. 흔한 오해부터 ” 켰으니 안전하다”는 말이 자주 나온다. 아니다. 설정 막는 것 못 막는 것 프로듀서 멱등성 프로듀서 재시도로 인한 브로커 측 중복 저장 컨슈머 재소비 Kafka 트랜잭션(EOS) Kafka→Kafka 경로의 중복 외부 DB 쓰기 컨슈머가 DB 에 쓰는 순간, Kafka 가 주는 보장은 끝난다. 멱등성은 우리 몫이다. 대안 비교와 선택 대안 판단 A. eventId UNIQUE + 신규분만 집계 ✅ 채택 B. 오프셋을 DB 에 저장, 집계와 한 트랜잭션 이론상 가장 정확하지만 기각 C. Kafka EOS…

July 24, 2026
Music_Pipeline
음악 스트리밍 파이프라인 만들기 - 03. 트랜잭션 경계, 전량 롤백이 정답이 아닌 이유

문제 5,000건짜리 입수 파일 처리 중 3,001번째에서 실패하면 어떻게 해야 하나. 가장 쉬운 답은 “파일 전체를 한 트랜잭션으로 묶고 실패하면 전량 롤백”이다. 깨끗해 보이지만, 이 프로젝트에서 이 답을 일부러 구현해서 깨뜨렸다. 결론부터: 전량 롤백은 데이터만 되돌리는 게 아니라 실패 원인 기록까지 지운다. 그 파일을 다시 넣으면 같은 지점에서 같은 실패를 반복한다. 경계 설계 4가지 작업 전파 속성 이유 Delivery 등록 (즉시 커밋) messageId UNIQUE 가 중복 실행 차단 장치 — 커밋돼야 다른 트랜잭션에 보인다 검증 실패 기록 롤백에 휩쓸리면 안 된다. 실패 기록은 재처리의 입력이다 청크(500건) 적재 커밋 단위. 건별 커밋은 fsync 비용, 파일 전체는 undo/락 비용 사후 검증 실패 보상 삭제 이미 커밋된 것은 롤백으로 못 되돌린다 보상 삭제가 필요한 구조적 이유 스트리밍 파싱(StAX)이라 헤더의 와 실제 개수가 맞는지는 파일 끝까지 읽…

July 24, 2026
Music_Pipeline
음악 스트리밍 파이프라인 만들기 - 02. JPA 배치 insert, IDENTITY가 41배 느린 이유

문제 음원 메타데이터 파일 하나에 Release 5,000건, Track 50,000건이 들어온다. MyBatis 시절엔 로 multi-value insert 를 쓰면 끝이었는데, JPA 로 오니 이 건별 INSERT 를 날리고 있었다. 을 넣어도 달라지지 않았다. 경고도 로그도 없이. 원인 — IDENTITY 는 batch 를 조용히 끈다 영속성 컨텍스트는 엔티티를 ID 를 키로 관리한다. 그런데 는 AUTO_INCREMENT 라서 INSERT 를 실제로 실행해야 ID 를 알 수 있다. Hibernate 의 batch 는 write-behind(쓰기 지연) 위에서 동작한다 — INSERT 를 모았다가 flush 에서 묶어 보낸다 IDENTITY 는 시점에 ID 가 필요하므로 즉시 INSERT 할 수밖에 없다 그래서 Hibernate 는 IDENTITY 엔티티의 batch 를 예외 없이 포기한다 코드 차이는 애노테이션 한 줄인데, 이 동작 차이는 어디에도 표시되지 않는다.…

July 24, 2026
Music_Pipeline
음악 스트리밍 파이프라인 만들기 - 01. 프로젝트 소개와 세 가지 원칙

왜 이 프로젝트인가 MyBatis/MariaDB 중심으로 4~5년을 일했고, JPA·Kafka·Redis 는 “써봤다”와 “설계할 수 있다” 사이 어딘가에 있었다. 그 간극을 메우려고 음악 스트리밍 서비스의 백엔드 데이터 파이프라인을 축소 구현했다. 두 개의 큰 흐름으로 구성된다. 음원 메타데이터 입수 — 유통사가 보내는 DDEX 스타일 XML 을 스트리밍 파싱·검증해 배치 적재 재생 이벤트 처리 — Kafka 로 초당 수백 건의 재생 이벤트를 수집, 멱등하게 집계해 실시간 차트 제공 이 도메인을 고른 이유는 두 흐름의 실패 모델이 정반대라서다. 입수는 “파일 하나가 크고 실패가 부분적” — 트랜잭션 경계 문제. 이벤트는 “건수가 많고 중복이 정상” — 멱등성 문제. 한 프로젝트 안에서 서로 다른 정합성 전략을 비교할 수 있다. 저장소: github.com/Shim8934/media-project 스택: Java 21, Spring Boot 3.4, JPA + JdbcTemplat…

July 24, 2026
Music_Pipeline