템플릿 둘러보기/스포티파이 (Spotify)

스포티파이 (Spotify)

아티스트 프로필, 앨범 발매, 오디오 특징이 포함된 트랙 카탈로그, 사용자 및 에디토리얼 플레이리스트, 소셜 팔로우, 청취 기록, 좋아요 표시한 곡, 팟캐스트 통합, 기기 관리 및 재생 대기열을 다루는 스포티파이 스타일의 음악 스트리밍 플랫폼을 위한 종합 데이터베이스 스키마입니다.

PostgreSQL18개 테이블웹 앱
musicstreamingplaylistpodcastsocial
ERD Studio로 제작

이 스키마에 대하여

여기서 트랙은 앨범에 속하지 않습니다. tracks 가 primary_artist_id 를 들고 독립해 있고, album_tracks 가 디스크 번호와 위치로 둘을 잇습니다. 같은 녹음이 싱글에도, 앨범에도, 컴필레이션에도 세 번 저장되지 않고 실릴 수 있는 이유입니다.

추천을 가능하게 하는 것은 track_features 입니다 — tempo, energy, danceability, valence 등이 트랙마다 한 행. 분석 작업이 한 번 쓰고 추천이 읽는 값이라, 재생할 때마다 건드리는 행에 얹혀 있을 이유가 없어서 따로 뒀습니다.

팔로우는 다형 테이블 하나가 아니라 follows_artists 와 follows_playlists 둘입니다. 가리키는 대상이 다르고 읽는 화면이 다르며 서로를 알 필요가 없어서, 합쳐 봐야 타입 컬럼만 생기고 얻는 것이 없습니다.

눈여겨볼 관계

tracks ↔ albums (album_tracks)
disc_number 와 position 이 연결 테이블에 있습니다. 트랙이 아니라 그 앨범에서의 자리이기 때문입니다. 같은 트랙이 한 발매에서는 3번, 다른 발매에서는 11번일 수 있습니다.
tracks → track_features
트랙마다 한 행, 분석 작업이 씁니다. 떼어 두어야 카탈로그 행이 좁게 유지되고, 특징 벡터를 다시 계산할 때 그 행을 건드리지 않습니다.
artists ↔ users (artist_members)
아티스트 프로필을 계정들이 역할과 함께 관리합니다. 밴드는 한 프로필에 접근하는 여러 사람이고, 레이블 담당자는 또 다른 사람입니다.
playlists → playlist_tracks
added_by 가 항목에 있어서 is_collaborative 가 의미를 갖습니다 — 공동 플레이리스트에 누가 무엇을 넣었는지 보입니다.
users → listening_history
context_type·context_id 가 재생이 어디서 시작됐는지를 적습니다 — 앨범, 플레이리스트, 라디오. 없으면 재생은 숫자일 뿐이고, 있으면 사람들이 실제로 무엇을 끝까지 듣는지에 대한 증거가 됩니다.

설계 판단

트랙은 앨범의 자식이 아니다

트랙에 album_id 컬럼을 두는 쪽이 단순하지만, 한 곡이 싱글로 나왔다가 앨범에 실리거나 몇 년 뒤 컴필레이션에 들어가는 순간 틀립니다. 실릴 때마다 중복 행이 생기고 각자의 재생 수와 좋아요를 갖게 됩니다. 디스크와 위치를 가진 연결 테이블이면 녹음 하나가 인기 하나를 갖고, 앨범은 그것이 실린 여러 자리 중 하나가 됩니다.

오디오 특징은 자기 테이블

재생 경로가 하나도 읽지 않는 숫자 컬럼 열한 개가, 모든 재생과 검색과 목록이 건드리는 행에 있을 이유가 없습니다. 나눠 두면 분석 작업이 특징 벡터를 다시 쓸 때 재생과 같은 행을 두고 경쟁하지도 않습니다.

청취 기록은 맥락을 남긴다

맥락 없는 재생은 "이 곡이 들렸다" 만 알려 줍니다. 플레이리스트에서 시작됐다는 것을 아는 재생은 "그 플레이리스트가 먹힌다" 를 알려 줍니다. context_type·context_id 는 컬럼 둘로 로그를 제품 결정의 근거로 바꿉니다. 외래키가 아니라서 싸기도 합니다 — 맥락이 테이블 없는 라디오일 수도 있으니까요.

좋아요와 플레이리스트는 다른 것

liked_songs 는 플레이리스트 행이 아닙니다. 순서가 없고 사용자 외의 주인이 없으며 공유할 수 없고, 순열이 아니라 집합으로 읽힙니다. 플레이리스트로 모델링하면 모든 플레이리스트 질의가 "진짜 플레이리스트가 아닌 하나" 를 예외 처리해야 합니다.

이 템플릿의 테이블

users사용자
9 cols
artists아티스트
9 cols
artist_members아티스트 멤버
5 cols
genres장르
4 cols
albums앨범
10 cols
tracks곡 (트랙)
11 cols
album_tracks앨범 수록곡 연결
6 cols
track_genres곡 장르 연결
4 cols
track_features곡 오디오 특징
12 cols
playlists플레이리스트
11 cols
playlist_tracks플레이리스트 수록곡
6 cols
liked_songs좋아요 표시한 곡
4 cols
listening_history청취 기록
7 cols
follows_artists아티스트 팔로우
4 cols
follows_playlists플레이리스트 팔로우
4 cols
podcasts팟캐스트
9 cols
podcast_episodes팟캐스트 에피소드
9 cols
devices재생 기기
8 cols

자주 묻는 것

tracks 에 album_id 가 왜 없나요?
한 녹음이 여러 발매에 실릴 수 있기 때문입니다 — 싱글, 정규, 컴필레이션, 디럭스. 컬럼으로 두면 실릴 때마다 중복 행이 생기고, 같은 곡의 재생 수와 좋아요가 사본마다 쪼개집니다.
오디오 특징을 왜 따로 두나요?
분석 작업이 한 번 쓰고 추천만 읽기 때문입니다. 아무도 필요로 하지 않는 컬럼 열한 개를 자주 읽히는 카탈로그 행에서 빼 두면 그 행이 좁게 유지되고, 특징을 독립적으로 다시 계산할 수 있습니다.
팟캐스트는 음악과 어떻게 같이 두나요?
타입을 가진 트랙이 아니라 podcasts·podcast_episodes 라는 자기 테이블로 둡니다. 음악과 공유하는 것이 거의 없습니다 — 앨범도 없고 아티스트 크레딧도 없고 오디오 특징도 없으며 진행 개념도 다릅니다. 한 테이블에 두면 타입 컬럼과 많은 null 이 남습니다.
listening_history 의 context_type 은 무엇에 쓰나요?
재생이 어디서 시작됐는지를 적습니다 — 앨범, 플레이리스트, 라디오. "이 곡이 인기다" 와 "이 플레이리스트가 먹힌다" 를 가르는 값이고, 대부분의 청취 로그가 필요해지기 전까지 빠뜨리는 신호입니다.
liked_songs 를 그냥 플레이리스트로 두면 안 되나요?
닮아 보이지만 순서가 없고, 사용자 외의 주인이 없고, 공유나 공동 편집이 안 됩니다. 합치면 모든 플레이리스트 질의가 진짜 플레이리스트가 아닌 한 행을 예외 처리하게 되는데, 작은 테이블 하나보다 코드가 많아집니다.