🏢 Spotify 기술 스택 분석
스포티파이가 전 세계 5억 명에게 8,300만 곡을 스트리밍하는 기술 스택 심층 분석 - 마이크로서비스, 머신러닝, 그리고 음악 추천의 비밀
🏢 Spotify 기술 스택 분석
매일 10억 개 이상의 스트림을 처리하며 개인화된 음악 경험을 제공하는 Spotify의 기술 스택을 심층 분석합니다.
"Music for everyone" - 기술로 만드는 글로벌 음악 플랫폼
📊 회사 개요
서비스 규모
- 월간 활성 사용자: 5.74억 명 (프리미엄 2.26억)
- 콘텐츠: 1억+ 트랙, 500만+ 팟캐스트
- 일일 스트림: 10억+ 재생
- 서비스 지역: 184개 시장
- 플레이리스트: 40억+ 사용자 생성
엔지니어링 조직
- 엔지니어: 2,000명+ (전체 직원 9,000+)
- Squad 모델: 자율적인 소규모 팀
- Chapter & Guild: 기술 공유 조직
- 오픈소스: 400+ 프로젝트
기술적 도전과제
- 개인화: 5억 명을 위한 맞춤 추천
- 저지연 스트리밍: 전 세계 실시간 음악 전송
- 콘텐츠 관리: 1억+ 트랙 메타데이터
- 크리에이터 도구: 아티스트 분석 플랫폼
🧩 기술 스택 상세
Backend 기술
마이크로서비스 아키텍처
// Spotify의 Apollo 프레임워크 기반 서비스
@Service
public class PlaylistService {
private final UserService userService;
private final TrackService trackService;
private final RecommendationService recommendationService;
@RequestMapping("/playlists/{id}")
public CompletableFuture<Playlist> getPlaylist(
@PathVariable String id,
@RequestContext Context context) {
return userService.getUser(context.userId())
.thenCompose(user -> {
// 병렬로 데이터 가져오기
CompletableFuture<PlaylistMetadata> metadata =
getPlaylistMetadata(id);
CompletableFuture<List<Track>> tracks =
trackService.getTracks(id);
CompletableFuture<List<Recommendation>> recommendations =
recommendationService.getForPlaylist(id, user);
return CompletableFuture.allOf(metadata, tracks, recommendations)
.thenApply(v -> buildPlaylist(
metadata.join(),
tracks.join(),
recommendations.join()
));
});
}
// Circuit breaker 패턴
@HystrixCommand(
fallbackMethod = "getDefaultRecommendations",
commandProperties = {
@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "3000")
}
)
private CompletableFuture<List<Track>> getPersonalizedTracks(String userId) {
return mlService.getRecommendations(userId);
}
}
Backend 기술 스택:
- 언어: Java (60%), Python (25%), Go (10%), C++ (5%)
- 프레임워크: Spring Boot, Apollo (자체), Flask
- API: GraphQL Federation, REST
- 메시징: Google Pub/Sub, Kafka (일부)
- 서비스 메시: Kubernetes + Istio
데이터 파이프라인
# Apache Beam 기반 데이터 처리
import apache_beam as beam
from apache_beam.options.pipeline_options import PipelineOptions
class SpotifyStreamingPipeline:
def __init__(self):
self.pipeline_options = PipelineOptions([
'--runner=DataflowRunner',
'--project=spotify-data',
'--region=europe-west1',
'--temp_location=gs://spotify-temp/dataflow',
'--streaming'
])
def build_pipeline(self):
with beam.Pipeline(options=self.pipeline_options) as p:
# 실시간 스트림 이벤트 수집
events = (p
| 'Read from Pub/Sub' >> beam.io.ReadFromPubSub(
topic='projects/spotify-data/topics/play-events'
)
| 'Parse JSON' >> beam.Map(json.loads)
)
# 사용자별 집계
user_stats = (events
| 'Extract User Data' >> beam.Map(
lambda x: (x['user_id'], {
'track_id': x['track_id'],
'duration': x['duration'],
'timestamp': x['timestamp']
})
)
| 'Window' >> beam.WindowInto(
beam.window.FixedWindows(60) # 1분 윈도우
)
| 'Group by User' >> beam.GroupByKey()
| 'Calculate Stats' >> beam.Map(self.calculate_user_stats)
)
# BigTable에 저장
user_stats | 'Write to Bigtable' >> beam.io.WriteToBigTable(
project_id='spotify-data',
instance_id='user-analytics',
table_id='streaming_stats'
)
# 추천 시스템 업데이트
user_stats | 'Update ML Features' >> beam.Map(
self.update_recommendation_features
)
Frontend 기술
Web Player (React)
// Spotify Web Player 컴포넌트
import { useEffect, useState } from "react";
import { useSpotifySDK } from "@spotify/web-api-sdk";
interface PlayerProps {
trackUri: string;
onTrackEnd: () => void;
}
export function SpotifyPlayer({ trackUri, onTrackEnd }: PlayerProps) {
const [player, setPlayer] = useState<Spotify.Player | null>(null);
const [deviceId, setDeviceId] = useState<string>("");
const sdk = useSpotifySDK();
useEffect(() => {
// Web Playback SDK 초기화
const script = document.createElement("script");
script.src = "https://sdk.scdn.co/spotify-player.js";
script.async = true;
document.body.appendChild(script);
window.onSpotifyWebPlaybackSDKReady = () => {
const player = new Spotify.Player({
name: "Spotify Web Player",
getOAuthToken: (cb) => {
sdk.getAccessToken().then(cb);
},
volume: 0.5,
});
// 이벤트 리스너
player.addListener("ready", ({ device_id }) => {
console.log("Ready with Device ID", device_id);
setDeviceId(device_id);
});
player.addListener("player_state_changed", (state) => {
if (state.position === 0 && state.paused) {
onTrackEnd();
}
// 재생 통계 전송
sendPlaybackAnalytics({
track_id: state.track_window.current_track.id,
position: state.position,
duration: state.duration,
context: state.context,
});
});
player.connect();
setPlayer(player);
};
return () => {
player?.disconnect();
};
}, []);
const play = async () => {
await sdk.player.play({
device_id: deviceId,
uris: [trackUri],
});
};
return (
<div className="player-container">
<AudioVisualizer />
<PlayerControls
onPlay={play}
onPause={() => player?.pause()}
onNext={() => player?.nextTrack()}
/>
<VolumeControl onChange={(volume) => player?.setVolume(volume)} />
</div>
);
}
Frontend 기술:
- Web: React, TypeScript, Redux
- Mobile: Native (Swift/Kotlin) + React Native (일부)
- Desktop: Electron + CEF (Chromium Embedded)
- TV/콘솔: C++ 커스텀 플레이어
- 빌드: Webpack, Bazel
Data & ML Infrastructure
추천 시스템
# Spotify의 추천 엔진 (간소화)
class CollaborativeFilteringModel:
def __init__(self):
self.user_embeddings = None
self.item_embeddings = None
self.model = self._build_model()
def _build_model(self):
# Two-tower 모델
user_input = keras.Input(shape=(USER_FEATURES,))
item_input = keras.Input(shape=(ITEM_FEATURES,))
# User tower
user_tower = keras.Sequential([
keras.layers.Dense(256, activation='relu'),
keras.layers.Dropout(0.2),
keras.layers.Dense(128, activation='relu'),
keras.layers.Dense(64)
])
# Item tower
item_tower = keras.Sequential([
keras.layers.Dense(256, activation='relu'),
keras.layers.Dropout(0.2),
keras.layers.Dense(128, activation='relu'),
keras.layers.Dense(64)
])
user_embedding = user_tower(user_input)
item_embedding = item_tower(item_input)
# Dot product for similarity
similarity = keras.layers.Dot(axes=1)([user_embedding, item_embedding])
model = keras.Model(
inputs=[user_input, item_input],
outputs=similarity
)
return model
def get_recommendations(self, user_id, context):
# 사용자 특성 추출
user_features = self.extract_user_features(user_id)
# 컨텍스트 고려 (시간, 장소, 기기)
context_features = self.extract_context_features(context)
# 후보 생성
candidates = self.retrieve_candidates(user_features)
# 순위 매기기
scores = self.model.predict([
np.repeat(user_features, len(candidates)),
candidates
])
# 다양성 추가
diversified = self.apply_diversity(candidates, scores)
return diversified[:100] # Top 100
Feature Store
// Spotify의 Feature Store (Feathr 기반)
object FeatureStore {
case class UserFeatures(
userId: String,
totalListeningTime: Long,
topGenres: Seq[String],
topArtists: Seq[String],
listeningPatterns: Map[Int, Float], // hour -> probability
devicePreferences: Map[String, Float],
lastUpdated: Timestamp
)
case class TrackFeatures(
trackId: String,
audioFeatures: AudioFeatures,
popularity: Float,
releaseDate: Date,
genres: Seq[String],
collaborativeSignal: Float,
contentSignal: Float
)
case class AudioFeatures(
danceability: Float,
energy: Float,
key: Int,
loudness: Float,
mode: Int,
speechiness: Float,
acousticness: Float,
instrumentalness: Float,
liveness: Float,
valence: Float,
tempo: Float,
duration: Int
)
def getUserFeatures(userId: String): Future[UserFeatures] = {
// BigTable에서 특성 조회
bigtableClient.get(s"user_features:$userId").map { row =>
UserFeatures(
userId = userId,
totalListeningTime = row.getLong("total_time"),
topGenres = row.getList("genres"),
topArtists = row.getList("artists"),
listeningPatterns = row.getMap("patterns"),
devicePreferences = row.getMap("devices"),
lastUpdated = row.getTimestamp("updated")
)
}
}
}
데이터 인프라:
- 데이터 레이크: Google Cloud Storage
- 실시간 처리: Google Pub/Sub + Dataflow
- 배치 처리: Apache Beam, Scio (Scala)
- 데이터베이스: Bigtable, Spanner, PostgreSQL
- 분석: BigQuery, Databricks
- ML 플랫폼: Kubeflow, TensorFlow Extended
Infrastructure & DevOps
Backstage 플랫폼
# Backstage 서비스 카탈로그
apiVersion: backstage.io/v1alpha1
kind: Component
metadata:
name: playlist-service
description: Manages user playlists and collaborative playlists
tags:
- java
- backend
- tier1
annotations:
github.com/project-slug: spotify/playlist-service
backstage.io/techdocs-ref: dir:.
spotify.com/squad-owner: playlist-experience
spec:
type: service
lifecycle: production
owner: playlist-squad
system: playlist-platform
dependsOn:
- resource:playlist-db
- component:track-service
- component:user-service
providesApis:
- playlist-api
Golden Path
# Spotify의 Golden Path CLI
$ spotify create service playlist-service --lang java --type rest
✓ Created service scaffold
✓ Set up GitHub repository
✓ Configured CI/CD pipelines
✓ Created Kubernetes manifests
✓ Registered in Backstage
✓ Set up monitoring dashboards
✓ Created runbooks
Your service is ready!
Local development: cd playlist-service && ./gradlew bootRun
Deploy to staging: git push origin main
인프라 기술:
- 클라우드: Google Cloud Platform (주력)
- 컨테이너: Kubernetes (자체 관리)
- CI/CD: Jenkins + Spinnaker
- 모니터링: Prometheus + Grafana
- 로깅: ELK Stack
- 개발자 포털: Backstage
💡 핵심 기술 인사이트
1. Squad 모델 & 자율성
// Squad의 자율적 서비스 운영
interface SquadOwnership {
squad: string;
mission: string;
services: string[];
kpis: {
metric: string;
target: number;
current: number;
}[];
dependencies: {
upstream: string[];
downstream: string[];
};
}
// 예시: Playlist Squad
const playlistSquad: SquadOwnership = {
squad: "playlist-experience",
mission: "Enable users to organize and discover music through playlists",
services: [
"playlist-service",
"collaborative-playlist-service",
"playlist-recommendation-service",
],
kpis: [
{ metric: "playlist_creation_rate", target: 1000000, current: 1200000 },
{
metric: "collaborative_playlist_mau",
target: 50000000,
current: 62000000,
},
{ metric: "p99_latency_ms", target: 200, current: 187 },
],
dependencies: {
upstream: ["user-service", "track-service", "ml-platform"],
downstream: ["mobile-app", "web-player", "home-feed"],
},
};
2. 음악 추천 알고리즘
# Spotify의 하이브리드 추천 시스템
class HybridRecommendationEngine:
def __init__(self):
self.collaborative_filter = CollaborativeFiltering()
self.content_based = ContentBasedFiltering()
self.nlp_engine = NLPEngine()
self.contextual_bandit = ContextualBandit()
def generate_discover_weekly(self, user_id):
# 1. 협업 필터링 (유사 사용자 기반)
collaborative_tracks = self.collaborative_filter.get_recommendations(
user_id,
method='matrix_factorization',
n_tracks=1000
)
# 2. 콘텐츠 기반 필터링 (음악적 특성)
user_taste_profile = self.get_user_taste_profile(user_id)
content_tracks = self.content_based.find_similar_tracks(
user_taste_profile,
audio_features=['energy', 'valence', 'danceability'],
n_tracks=1000
)
# 3. NLP 기반 추천 (가사, 리뷰 분석)
nlp_tracks = self.nlp_engine.get_semantic_recommendations(
user_id,
sources=['lyrics', 'reviews', 'social_media'],
n_tracks=500
)
# 4. 앙상블 & 다양성 최적화
candidate_pool = self.merge_recommendations([
(collaborative_tracks, 0.4),
(content_tracks, 0.3),
(nlp_tracks, 0.3)
])
# 5. Contextual Bandit으로 최종 선택
final_playlist = self.contextual_bandit.select_tracks(
candidate_pool,
context={
'day_of_week': datetime.now().weekday(),
'time_of_day': datetime.now().hour,
'user_activity': self.get_recent_activity(user_id),
'global_trends': self.get_trending_tracks()
},
n_tracks=30
)
return self.ensure_diversity(final_playlist)
3. 실시간 오디오 분석
// C++ 기반 오디오 특성 추출
class AudioAnalyzer {
private:
std::unique_ptr<FFTProcessor> fft;
std::unique_ptr<ChromaExtractor> chroma;
std::unique_ptr<RhythmAnalyzer> rhythm;
public:
AudioFeatures analyzeTrack(const AudioBuffer& buffer) {
AudioFeatures features;
// 스펙트럼 분석
auto spectrum = fft->process(buffer);
features.spectralCentroid = calculateSpectralCentroid(spectrum);
features.spectralRolloff = calculateSpectralRolloff(spectrum, 0.85);
// 크로마 벡터 (화성 분석)
auto chromaVector = chroma->extract(buffer);
features.key = estimateKey(chromaVector);
features.mode = estimateMode(chromaVector);
// 리듬 특성
auto tempogram = rhythm->extractTempogram(buffer);
features.tempo = estimateTempo(tempogram);
features.timeSignature = estimateTimeSignature(tempogram);
// 고급 특성
features.danceability = calculateDanceability(features.tempo,
features.energy,
features.rhythm_strength);
features.energy = calculateEnergy(spectrum);
features.valence = estimateValence(chromaVector, features.tempo);
// 음성/악기 분리
auto separated = separateSources(buffer);
features.instrumentalness = calculateInstrumentalness(separated);
features.speechiness = calculateSpeechiness(separated);
return features;
}
};
4. Backstage 개발자 경험
# 개발자 생산성 도구
apiVersion: scaffolder.backstage.io/v1beta3
kind: Template
metadata:
name: spotify-microservice
title: Spotify Microservice
description: Create a new microservice following Spotify standards
spec:
owner: platform-team
type: service
parameters:
- title: Service Details
required:
- name
- description
- squad
properties:
name:
title: Name
type: string
pattern: "^[a-z0-9-]+$"
description:
title: Description
type: string
squad:
title: Squad Owner
type: string
enum: ${squads}
- title: Technical Choices
properties:
language:
title: Programming Language
type: string
enum: ["java", "python", "go", "typescript"]
database:
title: Database
type: string
enum: ["bigtable", "spanner", "postgresql", "none"]
messaging:
title: Messaging
type: string
enum: ["pubsub", "kafka", "none"]
steps:
- id: fetch
name: Fetch Skeleton
action: fetch:template
input:
url: ./skeleton/${{ parameters.language }}
- id: publish
name: Publish to GitHub
action: publish:github
input:
repoUrl: github.com?owner=spotify&repo=${{ parameters.name }}
- id: register
name: Register in Backstage
action: catalog:register
- id: setup-ci
name: Setup CI/CD
action: jenkins:create-job
📈 성과 & 지표
기술적 성과
- API 응답시간: P50 < 50ms, P99 < 200ms
- 가용성: 99.95% (연간 다운타임 < 4.5시간)
- 배포 빈도: 일일 2,000+ 배포
- 추천 정확도: 30% 향상 (2020년 대비)
- 스트리밍 품질: 320kbps (Premium)
비즈니스 영향
- 사용자 참여: 평균 일일 사용 2.5시간
- Discover Weekly: 40% 사용자가 매주 청취
- 개인화 효과: 추천 트랙 80%+ 긍정 반응
- 개발자 생산성: Golden Path로 50% 시간 단축
🎓 Spotify에서 배울 점
✅ 적용 가능한 패턴
- Squad 모델: 자율적 팀 운영
- Golden Path: 표준화된 개발 경로
- Backstage: 개발자 포털 플랫폼
- Feature Store: ML 특성 관리
- 하이브리드 추천: 다양한 알고리즘 앙상블
❌ 주의사항
- 복잡한 조직 구조: Squad 모델은 문화가 중요
- 높은 자율성: 표준화와 균형 필요
- 기술 다양성: 너무 많은 언어/프레임워크
- 데이터 규모: 대규모 인프라 필요
📚 추천 리소스
🔮 미래 전망
현재 집중 분야
- AI DJ: 개인화된 라디오 경험
- 팟캐스트 기술: 음성 콘텐츠 혁신
- 라이브 오디오: 실시간 방송 플랫폼
- 자동차 경험: 차량용 최적화
- 창작자 도구: 아티스트 분석 플랫폼
기술 투자 영역
- 생성형 AI: 음악 생성, 리믹스
- 공간 오디오: 3D 사운드 경험
- 블록체인: 로열티 분배 투명성
- 엣지 컴퓨팅: 오프라인 최적화
- 그린 컴퓨팅: 탄소 중립 스트리밍
"우리는 세계 최고의 오디오 플랫폼이 되기 위해 기술의 한계를 계속 넓혀가고 있습니다."
- Spotify Engineering
마지막 업데이트: 2025-01-28
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ Clients │────►│ Backend │────►│ ML Platform│
│(Mobile/Web) │ │ for FE(BFF)│ │ (Kubeflow) │
└─────────────┘ └─────────────┘ └─────────────┘
│ │ │
│ ┌─────▼─────┐ ┌─────▼─────┐
│ │ API Gateway│ │ Feature │
│ │ (Nginx) │ │ Store │
│ └───────────┘ └───────────┘
│ │ │
└────────────────────┤ │
┌─────▼─────┐ ┌─────▼─────┐
│Microservices│ │ Data Lake │
│(100s of them)│ │ (GCS) │
└───────────┘ └───────────┘
핵심 설계 원칙
- Autonomous Teams: Squad 모델로 독립적 개발
- Golden Path: 표준화된 개발 경로
- Data-Driven: 모든 결정은 데이터 기반
- Fail Fast: 빠른 실험과 학습
- Platform Thinking: 내부 플랫폼 우선
기술 진화
- 2006-2008: Python 모놀리스
- 2009-2012: 마이크로서비스 전환
- 2013-2016: 자체 인프라 → 클라우드
- 2017-2020: ML 플랫폼 구축
- 2021-현재: Backstage, Golden Path