メインコンテンツにスキップ
2026-09-23PurePlayerパフォーマンス実測大規模ライブラリ

PurePlayer 高速読み取りミラー実測:50万曲の大規模ライブラリで深いページネーションを1秒未満に短縮

PurePlayer は大規模ライブラリ向けに SQLite ベースの読み取り専用ミラーを導入しました。50万曲のテスト環境において、深いページネーションの応答時間を1秒未満に短縮し、A-Zインデックスジャンプを1桁ミリ秒で完了します。初回インデックス生成には約30分と約2GBのローカルストレージが必要ですが、設定画面からいつでも管理や初期化が可能です。

PurePlayer 高速読み取りミラー実測:50万曲の大規模ライブラリで深いページネーションを1秒未満に短縮

ライブラリの曲数が数十万曲規模に達すると、先頭付近のスクロールは滑らかでも、深い位置のページング、頭文字ジャンプ、全体検索で大きな遅延が発生しやすくなります。500,500曲の検証用データセットを用いて主要な読み取りパスの所要時間を測定し、7.0向けにローカル読み取り専用ミラー機構を実装しました。

バージョンについて:6.0 では基本的な性能改善を実施済みです。本稿で紹介する高速読み取りミラーは 7.0 向けに設計された機能であり、現在は内部テスト段階にあります。記載の数値はすべて内部ビルド環境における実測値です。

本構成では、ローカルの SQLite データベース上に曲情報の読み取り専用派生データを保持します。インデックス構築時にソートキー、絞り込み条件、A-Z 索引アンカーを一括で事前計算してディスクに保存します。正規データはシステム標準ストレージで保持し、読み取り専用ミラーは検索クエリの処理に専念することで、必要に応じた再構築や消去に対応します。

性能実測データ

M4 Mac mini と 500,500 曲のデータセットを使用し、全件でフィールド単位の同一性検証を通過した測定結果です:

読み取りパス 規模 従来(SwiftData) ミラー 高速化倍率
曲リストのスナップショット構築 10k 3030 ms 21 ms ~143×
リストのページング(フィルタあり) 10k 1984 ms 8.2 ms ~242×
A-Z アンカージャンプ 10k ~2050 ms 0.4–7.6 ms 274–5100×
深いページ(offset 450k) 500k ~28 分/ページ ~0.9 s ~1800×
「表示行があるか?」のゼロヒットスキャン 100k 98.1 s 46 ms ~2100×
スマートプレイリストの複数条件候補 100k 6.9 s 1.27 s ~5.4×
プレイリストページ / アーティスト掘り下げ / 初回描画 50k – – 4.5–7.3×

50万曲規模の検証において、従来の読み取りパスは全件スキャンに依存しており、深いページほど処理時間が増加していました。40万曲付近の1ページを取得するベースライン処理には約1668秒を要していました。読み取り専用ミラーでは同位置の取得時間を 0.8〜1.4 秒に抑え、オフセットの深さに関わらず一定の応答速度を維持します。

ソートタグと表示の整合性

クラシックやジャズなどを整理する際、ソートタグ(Sort Tags)を有効にして “The Beatles” を B(“Beatles, The”)として扱う設定がよく使われます。ミラー側ではタグ対応の5項目について2種類のソートキーをあらかじめ格納しており、ソートタグの切り替え時にはクエリ内で参照列を変更するだけで対応できます。インデックスの再構築は不要で、A-Z バーも連動します。

アルバム、アーティスト、作曲家の集計画面でもミラーを活用した検索を行いますが、ソートタグが有効な場合は正規ストレージの読み取りパスに切り替わります。集計行から各エンティティのタグ値を直接展開できない仕様によるもので、表示結果の整合性を最優先しています。書き込み側の集計テーブル化は今後のロードマップで対応予定です。

リソース消費とトレードオフ

読み取り専用ミラーの運用には、インデックス構築時間とストレージ容量の消費が伴います:

  • 構築時間:全件インデックス生成はバックグラウンド処理で実行されます。50万曲のライブラリでは初回構築に約32分を要しますが、構築中も標準モードでの閲覧が可能です。5万曲規模であれば約35秒で完了します。
  • ストレージ容量:50万曲のミラーデータは約2.1GBの空き容量を使用します。設定画面で正確な使用量を確認でき、機能を無効化した際もデータを保持して即座の再開に備えるほか、完全削除も選択できます。
  • 書き込みオーバーヘッド:曲のメタデータを保存する際にミラーも同期更新されるため、バッチ保存1回あたり約20ミリ秒の処理が追加されます。

このような特性を踏まえ、本機能はデフォルトで無効化されており、ライブラリ規模に応じて設定画面から任意に有効化できます。

コア最適化の仕組み

  • 軽量値へのマッピング:従来のパスでは取得した各レコードについてオブジェクトコンテキストの管理下でインスタンス化を行いますが、ミラーでは SQLite から直接取得した値を軽量な構造体に投影し、ライフサイクル管理の負荷を抑制します。
  • 述語プッシュダウン:絞り込み条件、ソート順序、重複排除の処理をデータベースエンジン側で実行し、メモリ上に読み込む前の段階でデータを削減します。
  • ソートキーと索引の事前計算:正規化されたソートキーと A-Z インデックス文字を保存時に算出済みです。実行時はキーセットページネーションとインデックススキャンを組み合わせることで、深いページでも安定した検索速度を維持します。

データ整合性と実装詳細

ミラーと正規ストレージの検索結果が完全に一致することを確認するため、19種類のソート列、昇順・降順、ソートタグ状態、複合フィルタ条件を組み合わせた検証テストを実施しました。実装着手にあたり、いくつかの境界条件に対応しています:

  • nil 値と空文字列のフォールバック:ソートタグの規則は「タグがあればタグを使用し、なければ表示名を採用する」動作となります。Swift の nil 合体演算子と SQL 関数の空文字列に対する挙動差異を考慮し、ミラー側ではタグキーと表示名キーをそれぞれ別列として保持し、クエリ生成時に参照先を明示的に指定しています。
  • 特定項目のソート互換性:既存のライブラリでは曲の再生時間を文字列辞書順で比較していました(例:“120.0” が “90.0” より前)。既存の並び順を保つため、テキスト扱いの項目には専用のテキストソートキーを生成し、真の数値項目のみに数値インデックスを適用しています。
  • 空白文字セットの定義統一:Swift の空白定義(全角スペース、タブ、改行を含む)と標準 SQL トリミング関数の判定範囲の違いに対応するため、書き込み側に空判定フラグを設け、判定基準を統一しています。
  • 大規模データ構築の分割処理:50万曲の全件構築では、全文検索トリガーによる複数インデックス書き込みの影響で約32分を要します。処理を複数チャンクに分割し、中断後の再開が可能な構造としています。
  • 並行書き込みの調停:インデックス再構築中にメタデータ編集が発生した場合、各書き込みに単調増加のシーケンス番号を付与し、競合制御によって最新の変更が正しく反映されるようにしています。

測定データはすべてデバッグビルドで収集したものです。詳細な検証ログと再現コードはリポジトリ内に保管されています。