asopi tech
asopi techIndie Developer

Asopitech With · 音楽配信

Spotify with Alopex DB

Spotifyは、音楽やポッドキャストを見つけて聴ける音楽配信のサービスです。Alopex DBで同じものを作るとしたら…?

更新: 2026/10/12

どんなサービスか

Spotifyのような音声配信のサービスでは、利用者が音楽、ポッドキャスト、オーディオブックを見つけ、管理し、楽しみます。扱うデータは、曲やアーティストのカタログ、プレイリスト、そして利用者ごとの再生の履歴です。おすすめは、このカタログと履歴から作ります。

データの置き場所

  • SQLテーブル:曲、アーティスト、アルバム、プレイリストと、その中の曲の並びをテーブルに持ちます。
  • ベクトル:曲ごとの埋め込みをベクトル列に持ちます。「この曲に似た曲」は、HNSW索引での近傍検索です。ジャンルや公開年の条件を、同じSQLで重ねます。
  • カラムナーテーブル:再生の履歴をカラムナーテーブルに、HTTPの取り込みでまとめて入れます。利用者ごとのよく聴くアーティストや、時間帯ごとの傾向を、SQLで集計します。

曲の埋め込みはアプリが作ります。おすすめは、集計で出した好みのアーティストを条件にして、近い曲をベクトルで探す、ひとつのSQLになります。

配置の形

Alopex DBのサーバーを立て、アプリのバックエンドからHTTPかgRPCでつなぎます。カタログ、履歴、ベクトルが同じデータベースにあるので、おすすめのためにデータを別のシステムへ写す手間がありません。

Alopex DBが合う理由

おすすめは、似ているもの(ベクトル)と、よく選ばれているもの(集計)を組み合わせて作ります。Alopex DBは、ベクトル検索とカラムナーの集計を、同じサーバーのSQLで扱います。

コードで見る

再生の履歴からよく聴くアーティストを集計し、その傾向に近い曲をベクトルでおすすめします。おすすめの中心だけを取り出したコードです。

import alopex
import numpy as np

db = alopex.Database.open("spotify.alopex")

db.execute_sql("""
    CREATE TABLE IF NOT EXISTS tracks (
        id INTEGER PRIMARY KEY,
        title TEXT,
        artist TEXT,
        embedding VECTOR(4)
    )
""")
db.execute_sql("CREATE TABLE IF NOT EXISTS plays (track_id INTEGER)")

tracks = [
    (1, "Song A", "Aqua", [0.9, 0.1, 0.0, 0.0]),
    (2, "Song B", "Aqua", [0.85, 0.15, 0.0, 0.0]),
    (3, "Song C", "Lofi", [0.0, 0.0, 0.9, 0.1]),
]
for tid, title, artist, emb in tracks:
    db.execute_sql("INSERT INTO tracks (id, title, artist, embedding) VALUES (?, ?, ?, ?)", [tid, title, artist, np.array(emb, dtype=np.float32)])
for track_id in [1, 1, 2, 3]:
    db.execute_sql("INSERT INTO plays (track_id) VALUES (?)", [track_id])

# よく聴くアーティストを、再生の履歴から集計する
top = db.execute_sql("""
    SELECT t.artist, COUNT(*) AS cnt
    FROM plays AS p
    JOIN tracks AS t ON t.id = p.track_id
    GROUP BY t.artist
    ORDER BY cnt DESC
    LIMIT 1
""")[0]
print("よく聴く:", top["artist"], top["cnt"])

# その傾向(Song A)に近い曲を、ベクトルでおすすめする
query = np.array([0.9, 0.1, 0.0, 0.0], dtype=np.float32)
rows = db.execute_sql("""
    SELECT title, artist, vector_similarity(embedding, ?, 'cosine') AS score
    FROM tracks
    WHERE id <> ?
    ORDER BY score DESC
    LIMIT 1
""", [query, 1])
for row in rows:
    print("おすすめ:", round(row["score"], 3), row["title"], row["artist"])

よく選ばれているもの(集計)と、似ているもの(ベクトル)を、同じサーバーのSQLで組み合わせられます。

似た曲のベクトルと再生の傾向のカラムナーテーブルを同じサーバーで扱えるAlopex DBで、おすすめの仕組みを試してみませんか。出発点はクイックスタートです。

関連リンク