セルフホスト型の音楽配信サーバーでは、曲数が膨大になると深い位置でのページネーションや特定の集計クエリで処理遅延が急増しやすくなります。大規模な音楽ライブラリの実運用負荷を正確に把握するため、500,500曲の検証用データセットを用いて PureStream の Subsonic 読み取り処理を網羅的に計測し、実測値を記録しました。
検証範囲とデータセットの構築
愛好家のローカルライブラリは数十万曲規模に達することがあります。Subsonic プロトコルの代表的なエンドポイント(search3 列挙、getMusicDirectory ディレクトリ参照、getAlbumList2 アルバム一覧)は、データ量によって負荷の性質が大きく異なります。カバリングインデックスが有効なクエリもあれば、テーブル全体の重複排除や集計を要するクエリも存在します。今回の測定では以下の作業を実施しました:
- [x] 検証用データセットの生成:約4.2万枚のアルバム、2000名のアーティスト、100のジャンルを含む50万件のメディア情報を生成し、マイグレーション処理を経て本番同等の SQLite データベースを構築
- [x] ジャンル検索用インデックスの追加:ジャンルクエリ向けにカバリングインデックスを追加し、スキーマ整合性検証を導入してインデックスの欠落を防止
- [x] ベースライン測定値の記録:主要14経路の読み取り処理について P50 および P95 の応答時間を計測し、ベースラインとして集約
主要読み取りパスの実測データ
| エンドポイント | 10万件 | 50万件 | 判定 |
|---|---|---|---|
| search3 メイン列挙 | 53µs | 69µs | 安定 |
| search3 深いページ(offset=25万) | — | 25ms | 安定 |
| getMusicDirectory 単一ページ | 1.3ms | 1.4ms | 安定 |
| getAlbumList2 3つのページング | 8–82ms | 86–333µs | 安定 |
| getSongsByGenre | 未測定 | 113µs | インデックス有効 |
| FTS テキスト検索 | 24.8ms | 137ms | 線形増加 |
| getArtists 重複排除列挙 | 3.4ms | 543ms | 集計負荷あり |
| getGenres 集約 | 未測定 | 600ms | 想定範囲内 |
| 単一巨大ディレクトリの深いページ | — | 734ms | 境界条件 |
主な測定結果の分析:
- メイン列挙の処理時間(69µs):search3 のデフォルトクエリは、50万件の規模でもマイクロ秒単位の応答を維持しています。有効なレコードのみを対象とする部分カバリングインデックスを採用しているため、バックグラウンドでのスキャン処理が進行中であっても読み取り性能が維持されます。
- 深い位置のページング(25ms):25万曲目のオフセット位置から単一ページを取得する処理時間は 25 ミリ秒となり、大きなオフセットであってもミリ秒単位の応答性を保っています。
- ジャンル別検索(113µs):追加したカバリングインデックスにより、実行計画が全表走査からインデックススキャンへ変更され、処理時間が大幅に短縮されました。
ボトルネック箇所の分析
計測を通じ、計算コストが高い2つの集計処理の挙動が明確になりました:
- getArtists 重複排除列挙(543ms):メインアーティストと参加アーティストの2項目を統合してグループ集約を行うため、全テーブル走査が発生します。50万曲規模でも1秒未満を維持しているため、現時点では経過観察対象としています。
- getGenres 集約処理(600ms):ジャンル集約処理は
COUNT(DISTINCT album)によるユニークアルバム数の計算を伴うため、50万曲規模では約600ミリ秒を要します。
今回の改善内容
- [x] 50万曲規模における14の主要読み取りエンドポイントの P50/P95 性能データを集録
- [x] ジャンル読み取り専用インデックスの実装とスキーマ契約への登録、実行計画アサーションの追加
- [x] 重複排除および集計処理における計算負荷の定量化と今後の改善方針の整理
今後の対応予定
- [ ] プロトコル層のページング改善:offset パラメータを持たないエンドポイントにカーソル方式を導入し、深い走査の繰り返し負荷を軽減
- [ ] 配信処理におけるストリーミングセッション検証の集約最適化
