When a local music library reaches hundreds of thousands of songs, the initial screen typically scrolls smoothly, while deep pagination, letter navigation, and global searches experience significant latency. Using a 500,500-track benchmark dataset, we measured query durations across core read paths and introduced a local read-only mirror mechanism planned for version 7.0.
Release note: Version 6.0 introduced initial performance enhancements. The high-performance read mirror detailed here is planned for version 7.0 and is currently in internal testing. All metrics are collected from internal development builds.
The architecture generates a derived read-only SQLite copy of the library locally. Precomputed sort keys, filter attributes, and A-Z index anchors are written to disk during index creation. Primary storage retains canonical data authority, while the read-only mirror handles query workloads and supports independent rebuilds and cleanup on demand.
Benchmark Results
Measurements were conducted on an M4 Mac mini using the 500,500-track dataset, with all queries validated by field-level consistency assertions:
| Read path | Scale | SwiftData | Mirror | Speedup |
|---|---|---|---|---|
| Song list snapshot build | 10k | 3030 ms | 21 ms | ~143× |
| List paging (filtered) | 10k | 1984 ms | 8.2 ms | ~242× |
| A-Z anchor jump | 10k | ~2050 ms | 0.4–7.6 ms | 274–5100× |
| Deep page (offset 450k) | 500k | ~28 min/page | ~0.9 s | ~1800× |
| “Any visible rows?” zero-hit scan | 100k | 98.1 s | 46 ms | ~2100× |
| Smart-playlist multi-condition candidates | 100k | 6.9 s | 1.27 s | ~5.4× |
| Playlist page / artist drill-down / first paint | 50k | – | – | 4.5–7.3× |
At 500,000 tracks, the native read path relies on sequential full-table scans, with execution time scaling with offset depth: fetching a single page near track 400,000 took approximately 1,668 seconds. The read-only mirror reduces query duration at that depth to between 0.8 and 1.4 seconds, maintaining consistent performance regardless of offset distance.
Sort Tags and View Compatibility
For classical and jazz collections, listeners often enable sort tags to organize “The Beatles” under “B” (filed as “Beatles, The”). The mirror materializes two distinct sort key sets for the five tag-aware fields. Enabling or disabling sort tags selects the corresponding column directly in SQL queries, keeping the A-Z index in sync without index regeneration.
Album, artist, and composer aggregation views also leverage mirror queries. When sort tags are active in aggregation views, queries route to the primary storage path to ensure accurate entity-level tag mapping. Write-side materialized aggregation tables are scheduled for future releases to extend mirror acceleration to this scenario.
Resource Overhead and Tradeoffs
Operating a local read mirror involves distinct storage and index creation tradeoffs:
- Build Duration: Full index creation runs as a background task. Initial indexing for 500,000 tracks took approximately 32 minutes while standard library browsing remained active; a 50,000-track library completes in approximately 35 seconds.
- Disk Usage: The 500,000-track mirror occupies approximately 2.1GB of local storage. The Settings interface displays actual footprint metrics, and disabling high-performance mode preserves index files for immediate reactivation while offering a complete purge option.
- Write Overhead: Metadata updates write synchronously to the mirror, adding approximately 20ms per batch commit in our benchmarks.
Because of these resource characteristics, the feature remains disabled by default, allowing users to enable it based on library scale.
Core Optimization Mechanisms
- Lightweight Value Mapping: The native path instantiates managed object graphs for each row. The mirror reads raw SQLite columns and projects lightweight value types, minimizing object lifecycle overhead.
- Predicate Pushdown: Filter expressions, sort definitions, and deduplication logic execute directly within the database engine, pruning data prior to memory ingestion.
- Precomputed Sorting and Indexing: Normalized sort keys and A-Z section letters are calculated during indexing. Runtime retrieval pairs keyset pagination with index scans, securing steady response times on deep pages.
Consistency and Implementation Details
To verify query parity between the mirror and primary storage, the test suite exercised permutations of 19 sort columns, ascending and descending directions, sort tag states, and compound filter conditions. Several implementation details required explicit handling:
- Null Coalescing and Blank Strings: Sort tags prioritize the tag value and fall back to the display name when absent. Swift optional coalescing evaluates nil, while SQL string functions handle empty strings differently. The mirror materializes tag keys and display keys into separate columns and selects them explicitly during query construction.
- Field Ordering Compatibility: Track duration in existing libraries is evaluated via lexicographical string comparison (placing “120.0” before “90.0”). To preserve existing behavior, text-based columns materialize dedicated text sort keys, reserving numeric indexes for true numeric attributes.
- Whitespace Character Set Alignment: Swift whitespace definitions (including full-width spaces, tabs, and newlines) differ slightly from standard SQL trimming functions. A dedicated write-side status flag normalizes emptiness checks across both runtime layers.
- Chunked Background Processing: At 500,000 rows, full-text triggers perform multi-index writes, bringing total build time to approximately 32 minutes. The build routine executes in resumable chunks to maintain steady background execution.
- Concurrent Write Arbitration: When metadata modifications occur during an active rebuild, every update attaches a monotonic sequence counter, allowing database conflict handlers to enforce update order accurately.
Benchmark figures reflect debug build metrics; detailed performance traces and reproduction harnesses are archived in the repository.
