perf(db): add covering index for title-sorted song listings

Deep pagination over songs sorted by title (WHERE missing/library_id,
ORDER BY order_title LIMIT/OFFSET - the shape Jellyfin clients use to
enumerate the library, and non-admin native/Subsonic song lists share)
walked media_file_order_title and fetched the table row for every
skipped entry just to evaluate the filter and the annotation/bookmark
join keys: offset+limit random reads, seconds per page on cold spinning
disks.

The new (missing, library_id, order_title, id) index makes the offset
skip fully index-resident: filter columns and the join key come from the
index, and only the emitted page touches table rows. Measured on a
96K-track library: offset 50000 drops from ~6.5s (poisoned stats) /
~100ms (good stats, warm) to 24ms, and cold deep pages on NAS hardware
from ~7s to ~0.5s.
This commit is contained in:
Deluan 2026-07-06 21:39:09 -04:00
parent 42d4363f61
commit ad566d0cb6

View file

@ -0,0 +1,22 @@
-- +goose Up
-- +goose StatementBegin
-- Covering index for the title-sorted, library-scoped song listing:
-- WHERE missing = ? AND library_id = ? ORDER BY order_title LIMIT n OFFSET m
-- (Jellyfin clients page through the whole library this way; non-admin native and
-- Subsonic song lists produce the same shape.)
--
-- Without it, SQLite walks media_file_order_title and must fetch the table row for
-- every *skipped* entry just to evaluate the WHERE, so a deep page costs offset+limit
-- random row reads (seconds on cold spinning disks). With the filter columns in the
-- index the skip is index-only. `id` is included because the annotation/bookmark
-- LEFT JOINs run per candidate row and need the join key; without it each skipped
-- entry still triggers a row fetch.
create index if not exists media_file_missing_library_order_title
on media_file(missing, library_id, order_title, id);
-- +goose StatementEnd
-- +goose Down
-- +goose StatementBegin
drop index if exists media_file_missing_library_order_title;
-- +goose StatementEnd