음악 스트리밍 파이프라인 만들기 - 01. 프로젝트 소개와 세 가지 원칙
왜 이 프로젝트인가
MyBatis/MariaDB 중심으로 4~5년을 일했고, JPA·Kafka·Redis 는 “써봤다”와 “설계할 수 있다” 사이 어딘가에 있었다. 그 간극을 메우려고 음악 스트리밍 서비스의 백엔드 데이터 파이프라인을 축소 구현했다.
두 개의 큰 흐름으로 구성된다.
- 음원 메타데이터 입수 — 유통사가 보내는 DDEX 스타일 XML 을 스트리밍 파싱·검증해 배치 적재
- 재생 이벤트 처리 — Kafka 로 초당 수백 건의 재생 이벤트를 수집, 멱등하게 집계해 실시간 차트 제공
이 도메인을 고른 이유는 두 흐름의 실패 모델이 정반대라서다. 입수는 “파일 하나가 크고 실패가 부분적” — 트랜잭션 경계 문제. 이벤트는 “건수가 많고 중복이 정상” — 멱등성 문제. 한 프로젝트 안에서 서로 다른 정합성 전략을 비교할 수 있다.
- 저장소: github.com/Shim8934/media-project
- 스택: Java 21, Spring Boot 3.4, JPA + JdbcTemplate, MariaDB, Kafka, Redis, Testcontainers, k6, ffmpeg
마일스톤 지도
| 마일스톤 | 내용 | 시리즈 글 |
|---|---|---|
| M1 | 도메인 모델링, 배치 insert 최적화, 실패 기록·재처리 | 02, 03 |
| M2 | Kafka 파이프라인 — 멱등 집계, DLQ, poison pill | 04, 05 |
| M3 | 실시간 TOP 100 차트 + Redis 캐싱 | 06 |
| M4 | 성능 검증 — N+1 수치 비교, 트랜잭션 반증, k6 부하 | 07 |
| M5 | HLS 오디오 스트리밍 (선택 모듈) | 08 |
| 번외 | 로컬 Docker DB 가 랜섬 공격당한 이야기 | 09 |
프로젝트를 관통한 세 가지 원칙
1. 결정은 ADR 로 남긴다
ADR(Architecture Decision Record, 아키텍처 의사결정 기록)은 Michael Nygard 가 제안한 문서 형식이다. 아키텍처에 영향을 주는 결정 하나마다 “상황(Context) → 결정(Decision) → 결과(Consequences)” 구조의 짧은 문서를 번호 붙여 남기고, 결정이 바뀌면 문서를 고치는 게 아니라 새 ADR 이 이전 것을 대체(supersede)한다고 기록한다 — 코드에 깃 히스토리가 있듯 결정에도 이력을 남기는 것이다.
이 프로젝트는 여기에 “검토한 대안들”과 “재검토 트리거”를 더해 6편을 썼다. 코드가 보여주지 못하는 것은 “왜 그렇게 했나”보다 “무엇을 하지 않기로 했나” 다. 기각안의 기각 사유가 없으면 6개월 뒤에 같은 논의를 반복한다.
2. 측정 없이 믿지 않는다 — 기각된 가설도 기록한다
이 프로젝트에서 사전 예측이 측정으로 뒤집힌 것이 네 번이다.
- “JDBC batch 가 제일 빠를 것” → 아니었다 (수행하는 작업이 다르다)
- “fetch join 이 N+1 의 정답” → 쿼리는 줄지만 메모리 페이징이라는 숨은 비용
- “DB 가 느려서 캐시가 필요” → 저부하에선 캐시 없이도 p95 5ms
- “온디맨드 패키징도 1초쯤” → 실측 5.4초
틀린 예측이 왜 틀렸는지가 맞은 결정보다 배운 게 많았다. 각 글에서 자세히 다룬다.
3. 깨뜨려서 증명한다
“두 번 호출해도 결과가 같더라”는 함수의 성질 확인일 뿐이다. 실제로 문제 상황을 만들어야 증명이 된다.
- 멱등성 → Kafka 오프셋을 0으로 되감아 전량 재소비시킨다
- 트랜잭션 경계 → 기각한 설계(REQUIRED)를 일부러 재현해서 실패 기록이 사라지는 걸 보여준다
- 캐시 stale → 30초를 기다리는 대신 생성 시각을 과거로 조작한 캐시를 주입한다
- Redis 장애 → 컨테이너를 멈추는 게 아니라 “아무도 안 듣는 포트”를 바라보는 클라이언트를 조립한다
다음 글부터 마일스톤 순서로 간다. 첫 주제는 JPA 로 넘어온 MyBatis 개발자가 가장 먼저 부딪히는 벽 — 배치 insert 가 41배 느려지는 이유다.