들어가며: '음악'이라는 데이터로 개인화된 이야기를 만들다
매년 수억 명의 사용자가 기다리는 Spotify Wrapped. 2025년에는 한 걸음 더 나아갔습니다. 단순한 통계 요약이 아니라, 사용자의 청취 기록에서 '특별한 하루'를 찾아 LLM이 직접 창작한 개인화 스토리를 제공하는 'Wrapped Archive' 기능을 선보였죠.
이 기능은 단순히 '재미있는 기능'을 넘어, 350M 명의 사용자 각각에게 최대 5개, 총 14억 개의 리포트를 사전 생성하는 대규모 엔지니어링 과제였습니다. 이 글에서는 스포티파이 엔지니어링 블로그에 공개된 내용을 바탕으로, 그들이 어떻게 이 난제를 해결했는지 핵심 아키텍처와 의사결정을 분석합니다.
이 글은 Spotify Engineering Blog의 원문을 근거 자료로 분석하였습니다.
1. '특별한 하루'를 찾는 알고리즘: 휴리스틱 설계
모든 사용자에게 의미 있는 '하루'를 정의하는 것은 쉬운 일이 아닙니다. 스포티파이는 이를 위해 우선순위가 있는 휴리스틱 집합을 설계했습니다.
주요 발견 휴리스틱 예시
- 가장 많이 들은 날 (Biggest Music/Podcast Day): 단순히 청취 시간이 가장 긴 날
- 가장 큰 발견의 날 (Biggest Discovery Day): 처음 듣는 아티스트를 가장 많이 발견한 날
- 최애 아티스트의 날 (Biggest Top Artist Day): 특정 아티스트에게 가장 많은 시간을 할애한 날
- 가장 향수를 불러일으키는 날 (Most Nostalgic Day): 오래된 노래/회상 위주 청취가 급증한 날
- 가장 특이한 청취 패턴 (Most Unusual Listening Day): 평소 취향에서 가장 크게 벗어난 날
이러한 후보들을 서사적 잠재력과 통계적 강도로 순위를 매겨, 사용자당 최대 5일을 선정했습니다. 분산 데이터 파이프라인을 통해 수억 명의 사용자 데이터를 집계하고, 결과를 오브젝트 스토리지에 저장한 뒤 메시징 큐를 통해 비동기적으로 리포트 생성 단계로 전달했습니다.
2. 14억 개의 리포트를 위한 LLM: 프롬프트 엔지니어링과 모델 증류(Distillation)
프롬프트 설계: 일관성과 창의성의 균형
3개월 넘게 매일 프롬프트를 개선했다고 합니다. 핵심은 두 가지 레이어로 나뉩니다.
-
시스템 프롬프트: 창의적 계약 정의
- 데이터 기반 스토리텔링 (모든 인사이트는 실제 청취 데이터에 기반)
- 위트 있고 진솔하며 은근히 유쾌한 톤
- 약물, 폭력 등 민감 주제 절대 금지
-
유저 프롬프트: 모호성 제거
- 해당 날짜의 상세 청취 로그
- 요약된 통계 블록 (LLM은 숫자에 약하므로)
- 사용자의 전체 Wrapped 데이터
- 흥미로운 날의 카테고리
- 이전에 생성된 리포트 (반복 방지)
- 사용자 국가 (맞춤법/어휘)
모델 증류: 비용과 품질의 트레이드오프 해결
고성능 프론티어 모델로 14억 개 리포트를 생성하는 것은 경제적으로 불가능합니다. 스포티파이는 증류(Distillation) 파이프라인을 구축했습니다.
- 프론티어 모델로 고품질 참조 출력 생성
- 수작업 검토를 거친 '골드 데이터셋' 구축
- 더 작고 빠른 프로덕션 모델을 이 데이터로 파인튜닝
- Direct Preference Optimization (DPO) 적용: A/B 테스트된 인간 평가로 선호도 학습
결과적으로 작은 모델이 큰 모델과 강력한 선호도 동등성(Preference Parity) 을 달성했습니다.
3. 병렬 쓰기와 동시성: 데이터 모델링이 해결한 문제
14억 개의 리포트를 생성하고 저장하는 과정에서 가장 까다로운 문제 중 하나는 동시성 제어였습니다. 한 사용자에 대해 최대 5개의 리포트가 거의 동시에 생성되어 저장되어야 했죠.
스포티파이가 선택한 해결책은 놀랍도록 단순했습니다.
컬럼 기반 키-밸류 스토어 설계
- 각 사용자의 데이터는 하나의 로우(row)에 저장
- 각 '특별한 날'은 자체 컬럼 한정자(Column Qualifier) 를 가짐
- 날짜(YYYYMMDD)를 컬럼 한정자로 사용 (예: 20250315)
- 서로 다른 날짜의 쓰기는 같은 로우 내 완전히 다른 셀에 기록 → Lock, 트랜잭션, 읽기-수정-쓰기 사이클 불필요
# 개념적 데이터 모델 (Python 스타일 의사코드)
class UserWrappedData:
def __init__(self, user_id):
self.user_id = user_id
# 컬럼 패밀리: 'reports'
# 컬럼 한정자: YYYYMMDD (예: '20250315')
# 값: 리포트 내용
self.reports = {} # {'20250315': '...', '20250622': '...'}
# 컬럼 패밀리: 'metadata'
# 컬럼 한정자: 'completed_days'
# 값: 완료된 날짜 목록 (경량 메타데이터)
self.metadata = {}
def write_report(self, date, content):
# 1. 리포트 내용 먼저 쓰기
self.reports[date] = content
# 2. 그 다음 메타데이터 업데이트 (순서 보장)
self.metadata['completed_days'] = list(self.reports.keys())
# 이 순서 덕분에 사용자는 미완성 리포트를 볼 수 없음
핵심 인사이트: 복잡한 동시성 문제는 종종 데이터 모델링으로 해결됩니다. 애플리케이션 로직을 복잡하게 만드는 대신, 스키마를 '동시 쓰기에 안전한' 방식으로 설계한 것이 성공의 열쇠였습니다.
4. '빅뱅' 런칭을 위한 사전 스케일링과 합성 부하 테스트
Wrapped는 전 세계 동시 런칭입니다. 점진적 롤아웃이 없고, 1초 전에는 유휴 상태였다가 다음 순간 수백만 명이 몰립니다. 오토스케일링은 이 '스파이크'에 대응하기에 너무 느립니다.
스포티파이가 선택한 방법:
- 사전 스케일링: 런칭 몇 시간 전에 컴퓨트 파드와 데이터베이스 노드 용량을 미리 확보
- 모델 프로바이더 용량 사전 조정: 예상 트래픽에 맞춰 처리량 확보
- 합성 부하 테스트: 실제 사용자 트래픽이 도착하기 전에 모든 리전에서 실행
- 커넥션 풀과 캐시 워밍업
- 데이터베이스 노드의 태블릿 할당 및 블록 캐시 준비
결과: 실제 트래픽이 도착했을 때 '콜드 스타트'는 없었습니다. 모든 것이 준비되어 있었죠.
5. 대규모 평가 프레임워크: '판사'를 판단하는 시스템
14억 개 리포트에서 0.1%의 실패율도 140만 개의 '고장난' 스토리를 의미합니다. 수동 검토는 불가능에 가깝습니다.
LLM-as-a-Judge 평가 파이프라인
- 평가 차원: 정확성, 안전성, 톤, 포맷팅 (4개)
- 샘플링: 전체 코퍼스 대신 약 165,000개 리포트를 무작위 샘플링
- 다중 소규모 규칙 기반 쿼리: 하나의 거대한 프롬프트 대신 여러 개의 작은 쿼리로 병렬 평가
- 추론 전 점수: 판정 전에 판사가 추론 과정을 출력하도록 요구하여 일관성 향상
발견된 문제와 구조적 수정 루프
평가 과정에서 발견된 대표적인 버그: '가장 큰 발견의 날' 리포트가 잘못된 아티스트 발견 수를 자신있게 축하하는 문제
- 원인: 상류 데이터 파이프라인의 미묘한 타임존 버그
- 대응: SQL 쿼리와 정규식 패턴 매칭으로 유사 실패 탐색 → 배치 삭제 → 파이프라인 수정 → 안전하게 재생
교훈: 대규모 LLM 시스템에서 실패는 '발생 여부'가 아니라 '얼마나 빠르게 발견하고 복구하느냐'의 문제입니다.
6. 한국 개발 생태계에서의 적용 맥락
스포티파이의 이 사례는 한국의 대규모 데이터 기반 서비스(커머스, 소셜, 핀테크)에도 직접적인 시사점을 줍니다.
- 모델 증류: 한국어 특화 LLM의 고비용 문제를 해결하는 현실적 접근법입니다. 특히 고객 응대, 개인화 추천 등에서 바로 적용 가능합니다.
- 컬럼 기반 동시성 제어: 카산드라, HBase 등 NoSQL을 사용하는 환경에서 매우 유용한 패턴입니다. '쓰기 충돌' 문제로 고민 중이라면 데이터 모델링부터 다시 검토해보세요.
- 사전 스케일링: 국내에서도 '선착순 이벤트', '타임 세일' 등 트래픽 스파이크가 예상되는 서비스에 꼭 필요한 전략입니다. 오토스케일링에만 의존하지 마세요.
⚠️ 주의사항: 이 아키텍처는 스포티파이의 인프라(GCP, Bigtable 등)에 최적화되어 있습니다. 모든 환경에 그대로 적용하기보다는, 자신의 시스템 특성(데이터베이스 종류, 워크로드 패턴)에 맞게 조정이 필요합니다.
7. 이 기술의 한계와 주의사항
- 비용: 14억 개의 LLM 호출은 엄청난 비용입니다. 증류와 DPO로 최적화했지만, 여전히 상당한 인프라 비용이 발생합니다.
- 창의성의 한계: 프롬프트 엔지니어링으로 창의성을 제어할 수는 있지만, '진정한 창의성'을 완벽히 담보할 수는 없습니다. 특히 한국어처럼 맥락과 어조가 중요한 언어에서는 추가적인 튜닝이 필요할 수 있습니다.
- 평가의 어려움: LLM-as-a-Judge 방식은 평가 모델의 편향이 결과에 영향을 줄 수 있습니다. 인간 평가와의 지속적인 교차 검증이 필요합니다.
8. 다음 단계 학습 방향
이 글의 내용을 더 깊이 이해하고 싶다면 다음 주제를 공부해보세요.
- Direct Preference Optimization (DPO): RLHF의 대안으로 떠오르는 정렬 기법입니다.
- 모델 증류(Model Distillation): 큰 모델의 지식을 작은 모델로 전이하는 다양한 기법을 학습해보세요.
- 분산 키-밸류 스토어 설계: Bigtable, Cassandra, DynamoDB 등의 데이터 모델링 패턴을 공부하면 동시성 문제 해결에 큰 도움이 됩니다.
- LLM-as-a-Judge: 평가 프레임워크를 직접 구축해보며, 어떤 메트릭이 중요한지 경험해보세요.
결론: LLM 호출이 쉬운 부분이다
스포티파이의 사례가 주는 가장 큰 교훈은 이것입니다. 14억 번의 LLM 호출 자체는 쉬운 부분이었다. 진짜 엔지니어링 도전은 용량 계획, 재생 및 복구, 비용 규율, 안전 루프, 그리고 모든 것이 완벽하게 작동해야 하는 단 한 번의 런칭 순간을 준비하는 데 있었습니다.
함께 보면 좋은 글
