Когда объем локальной медиатеки достигает сотен тысяч треков, прокрутка начальных экранов обычно сохраняет плавность, тогда как глубокая пагинация, навигация по буквам и глобальный поиск сталкиваются с ощутимыми задержками. На эталонном наборе из 500 500 треков мы замерили длительность выполнения основных сценариев чтения и реализовали механизм локального зеркала чтения на SQLite, запланированный для версии 7.0.
Примечание к версии: в версии 6.0 выполнен комплекс базовых оптимизаций. Описанное здесь высокопроизводительное зеркало чтения разрабатывается для версии 7.0 и проходит внутреннее тестирование. Все показатели получены в средах внутренней сборки.
Архитектура формирует на устройстве производную копию медиатеки на базе SQLite в режиме только для чтения. При индексации ключи сортировки, параметры фильтрации и буквенные якоря A-Z вычисляются заранее и сохраняются на диск. Основное хранилище системы сохраняет статус первоисточника, а зеркало чтения берет на себя поисковые запросы и поддерживает независимую перестройку или очистку.
Результаты измерений производительности
Замеры проводились на Mac mini M4 с тестовым набором на 500 500 треков. Все показатели подтверждены попольным контролем целостности:
| Сценарий чтения | Масштаб | Базовое хранилище | Зеркало | Ускорение |
|---|---|---|---|---|
| Построение снимка списка песен | 10k | 3030 мс | 21 мс | ~143× |
| Постраничная навигация (с фильтром) | 10k | 1984 мс | 8,2 мс | ~242× |
| Переход по буквенному якорю A-Z | 10k | ~2050 мс | 0,4–7,6 мс | 274–5100× |
| Глубокая страница (смещение 450k) | 500k | ~28 мин/стр | ~0,9 с | ~1800× |
| Проверка наличия строк без совпадений | 100k | 98,1 с | 46 мс | ~2100× |
| Многокритериальные выборки смарт-плейлистов | 100k | 6,9 с | 1,27 с | ~5,4× |
| Страница плейлиста / детализация артиста / первый рендер | 50k | – | – | 4,5–7,3× |
При объеме в 500 000 треков стандартный путь чтения использует последовательное сканирование всей таблицы, из-за чего задержка растет пропорционально глубине смещения: загрузка одной страницы в районе 400 000-го трека занимала в среднем 1668 секунд. Зеркало чтения снижает время выборки на этой глубине до 0,8–1,4 секунды, обеспечивая ровный отклик при любых смещениях.
Теги сортировки и совместимость представлений
В классической музыке и джазе слушатели часто задействуют теги сортировки (Sort Tags), чтобы группировать исполнителей вроде «The Beatles» на букву B («Beatles, The»). Зеркало сохраняет два независимых набора ключей сортировки для пяти поддерживаемых полей. Переключение тегов сортировки меняет запрашиваемый столбец в SQL-запросе без перестройки индекса, а алфавитная панель A-Z обновляется синхронно.
Сводные экраны альбомов, исполнителей и композиторов также обращаются к запросам через зеркало. Если на этих экранах активны теги сортировки, запросы перенаправляются к основному системному хранилищу для сохранения корректности тегов уровня сущностей. Создание материализованных сводных таблиц на стороне записи внесено в план разработки.
Потребление ресурсов и баланс параметров
Поддержание локального зеркала чтения требует времени на построение индекса и локального дискового пространства:
- Время построения: индексация выполняется в фоновом режиме. Первичное создание индекса для 500 000 треков занимает около 32 минут, при этом навигация по библиотеке в стандартном режиме остается доступной; для 50 000 треков построение завершается примерно за 35 секунд.
- Объем хранилища: файл зеркала для 500 000 треков занимает около 2,1 ГБ. Раздел настроек отражает фактический размер; при выключении режима данные сохраняются для быстрого повторного включения, а также доступно полное удаление.
- Накладные расходы записи: сохранение метаданных синхронно обновляет зеркало, добавляя в наших замерах около 20 мс на групповую фиксацию изменений.
Ввиду указанных требований к ресурсам функция по умолчанию выключена и включается пользователем в настройках при наличии крупной медиатеки.
Ключевые механизмы ускорения
- Проекция легковесных структур: стандартный путь чтения создает управляемые объекты со всеми зависимостями жизненного цикла; зеркало извлекает строки SQLite и проецирует их в легковесные структуры, снижая нагрузку на память и процессор.
- Спуск предикатов в БД: фильтры, сортировка и дедупликация выполняются ядром базы данных до загрузки записей в оперативную память приложения.
- Предварительный расчет ключей и секций: нормализованные ключи сортировки и символы индекса A-Z рассчитываются при сохранении. Выборка сочетает постраничную навигацию по ключам (Keyset Pagination) и сканирование индексов, сохраняя стабильную скорость на глубоких страницах.
Целостность данных и детали реализации
Для подтверждения идентичности результатов между зеркалом и основным хранилищем тестовый стенд проверил комбинации 19 колонок сортировки, прямых и обратных направлений, состояний тегов и составных фильтров. В ходе интеграции было учтено несколько граничных условий:
- Обработка пустых значений и пустых строк: логика тегов сортировки использует тег при наличии и переходит к отображаемому имени при его отсутствии. Поскольку операторы Swift и строковые функции SQL по-разному работают с пустыми строками, зеркало разносит ключи по отдельным столбцам и явно указывает нужный столбец при составлении запроса.
- Совместимость сортировки длительности: длительность треков исторически сравнивалась посимвольно как строка (например, «120.0» перед «90.0»). Для сохранения привычного порядка строковые поля получили специализированные текстовые ключи, а числовые индексы закреплены за строгими числовыми типами.
- Синхронизация наборов пробельных символов: состав пробельных символов в Swift (включая неразрывные пробелы, символы табуляции и перевода строки) отличается от стандартных функций обрезки в SQL. На стороне записи добавлены статусные флаги, гарантирующие единую проверку пустоты данных.
- Пакетная обработка при масштабной сборке: на 500 000 записей триггеры полнотекстового поиска выполняют запись в несколько индексов, доводя суммарную продолжительность до 32 минут. Процесс разбит на фрагменты и поддерживает возобновление после прерываний.
- Координация параллельных записей: если во время перестроения индекса редактируются метаданные, каждой операции присваивается монотонно возрастающий номер последовательности, что позволяет правилам разрешения конфликтов сохранять актуальное состояние.
Все замеры получены на отладочных сборках; журналы тестов и проверочные сценарии сохранены в репозитории.
