perf: precompute mangas.sort_author for the author sort
?sort=author used a correlated min(lower(a.name)) subquery as the ORDER BY key, evaluated per filter-matching row before LIMIT. Materialize it into mangas.sort_author (indexed by mangas_sort_author_idx), maintained by triggers on manga_authors and backfilled in the migration, so the sort is a plain indexed column read. Author names are immutable, so only join changes matter. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -168,16 +168,11 @@ pub async fn list(pool: &PgPool, query: &ListQuery) -> AppResult<(Vec<Manga>, Op
|
||||
SortField::Created => "created_at",
|
||||
SortField::Updated => "updated_at",
|
||||
SortField::Title => "lower(title)",
|
||||
// Sorts on the alphabetically-first attached author. As an ORDER BY
|
||||
// key this correlated subquery is evaluated per filter-matching row
|
||||
// before LIMIT applies, so it scales worse than the indexed date/title
|
||||
// sorts — revisit with a LATERAL join or precomputed sort-name column
|
||||
// if the library grows large.
|
||||
SortField::Author => {
|
||||
"(SELECT min(lower(a.name)) \
|
||||
FROM manga_authors ma JOIN authors a ON a.id = ma.author_id \
|
||||
WHERE ma.manga_id = mangas.id)"
|
||||
}
|
||||
// Sorts on the alphabetically-first attached author. Precomputed into
|
||||
// `mangas.sort_author` (migration 0037, maintained by triggers on
|
||||
// manga_authors) and index-backed by `mangas_sort_author_idx`, so this
|
||||
// is a plain column read rather than a per-row correlated subquery.
|
||||
SortField::Author => "sort_author",
|
||||
};
|
||||
let dir = match query.order {
|
||||
SortOrder::Asc => "ASC",
|
||||
|
||||
Reference in New Issue
Block a user