* perf(literaryworks): cut Postgres load on ebook↔audiobook auto-linking
Rescans and candidate matching were driving high Postgres CPU on book
libraries.
- AutoLinkContent now checks literary_work_items with a cheap indexed
lookup before GetMatchItem, so unchanged already-linked books skip the
heavy triple-lateral query on every rescan.
- ListMatchCandidates is split into an indexable ID-selection phase
(title / provider EXISTS / series EXISTS) and a hydration phase, so the
per-row lateral aggregates run only for the LIMIT candidates kept rather
than for every opposite-format book.
- Add a partial LOWER(title) index scoped to ebook/audiobook so the OR of
title/provider/series filters can be driven entirely by indexes.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(migrations): drop invalid leftover index before concurrent rebuild
An interrupted CREATE INDEX CONCURRENTLY (cancel/restart) can leave an
INVALID idx_media_items_books_title_lower; IF NOT EXISTS would then skip
the rebuild while Goose records success. Add the preflight invalid-index
drop used by the repo's other concurrent-index migrations.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>