Design a Music Streaming System
Problem Design a music streaming service supporting playlists, adding songs, searching the catalog, and streaming audio to clients.
Functional requirements
- Search the catalog by song, artist or album.
- Create/rename/delete playlists; add, remove and reorder songs in a playlist.
- Read a playlist with its song metadata.
- Stream a song's audio, with seek support.
- Track play counts for royalties and recommendations.
Non-functional requirements
- ~500M users, ~100M DAU; ~100M songs in the catalog.
- ~50k playlist reads/sec at peak, ~5k playlist writes/sec, ~20k searches/sec.
- ~4M concurrent streams at peak; a 3-minute song at 160 kbps ≈ 3.5 MB → ~2 Tbps of egress, so CDN offload is mandatory, not optional.
- Playback start (time-to-first-audio) p95 < 200 ms; search p99 < 150 ms.
- Catalog is read-mostly; playlist data is per-user and write-light but read-heavy.
Key components
- Catalog service: Song, Artist, Album metadata in a relational store, replicated widely and heavily cached — it barely changes.
- Playlist service: Users, Playlists, PlaylistSongs (join table carrying position), sharded by user_id.
- Search service: inverted index (Elasticsearch/Lucene) over song/artist/album text with typo tolerance and prefix matching for search-as-you-type.
- Audio storage + CDN: encoded audio in an object store, segmented for adaptive bitrate, served from CDN edges. The API only issues signed URLs and never touches the bytes.
- Play-event pipeline: play/skip events → Kafka → offline aggregation for counts, royalties and recommendations.
- Cache layer: Redis for hot playlists and catalog rows.
Deep dives / trade-offs
- Schema and normalization: PlaylistSongs as a join table avoids duplicating song metadata across millions of playlists — one metadata fix propagates everywhere, and an artist rename doesn't require a mass update. The cost is a join on every playlist read at 50k QPS. Denormalizing minimal song metadata into a playlist read-model (or a cached materialized playlist document) removes the join at the price of an invalidation path when catalog metadata changes. Discuss which fields are safe to denormalize (title, duration — rarely change) versus which are not (availability by region — changes constantly).
- Playlist ordering: an integer position column forces renumbering the tail on every insert. Discuss fractional/lexicographic ranks or a linked-list representation, and what happens with concurrent reorders from two devices.
- Relational vs search store: the same song data needs transactional integrity (catalog) and fuzzy ranked retrieval (search). You need both, synced via CDC — explain why you don't run search off LIKE queries in the primary DB.
- Streaming path: never proxy audio through the application tier. Signed, expiring CDN URLs plus adaptive bitrate segments; discuss prefetching the next track to hide latency and offline caching for downloads.
- Play counts at this volume are an append-only stream, not a counter UPDATE — a hot row on a viral song would serialize every write.
asked …