From 63e7fe0925a8ddfb5d4b17fb9964ca5868cc98ac Mon Sep 17 00:00:00 2001 From: Deluan Date: Fri, 4 Sep 2026 23:02:09 -0400 Subject: [PATCH] perf(pglite): materialize the artist stats CTEs PostgreSQL inlines single-use CTEs, so the correlated subquery in the final UPDATE re-ran the whole aggregation for every library_artist row, rescanning all media files of the library three times per artist. On the 97k-track library one 1000-artist batch ran for more than five minutes. MATERIALIZED computes each CTE once per batch: 0.3 s versus 1.1 s locally, and orders of magnitude on the real plan. --- persistence/artist_repository.go | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/persistence/artist_repository.go b/persistence/artist_repository.go index 2bee08ff5..acc163fee 100644 --- a/persistence/artist_repository.go +++ b/persistence/artist_repository.go @@ -484,8 +484,10 @@ func (r *artistRepository) RefreshStats(allArtists bool) (int64, error) { // Template for the batch update with placeholder markers that we'll replace // This now calculates per-library statistics and stores them in library_artist.stats + // MATERIALIZED: the correlated subquery in the UPDATE would otherwise re-run the whole + // aggregation for every library_artist row (minutes per batch on a large library). batchUpdateStatsSQL := ` - WITH artist_role_counters AS ( + WITH artist_role_counters AS MATERIALIZED ( SELECT mfa.artist_id, mf.library_id, mfa.role, @@ -497,7 +499,7 @@ func (r *artistRepository) RefreshStats(allArtists bool) (int64, error) { WHERE mfa.artist_id IN (ROLE_IDS_PLACEHOLDER) -- Will replace with actual placeholders GROUP BY mfa.artist_id, mf.library_id, mfa.role ), - artist_total_counters AS ( + artist_total_counters AS MATERIALIZED ( SELECT mfa.artist_id, mf.library_id, 'total' AS role, @@ -509,7 +511,7 @@ func (r *artistRepository) RefreshStats(allArtists bool) (int64, error) { WHERE mfa.artist_id IN (ROLE_IDS_PLACEHOLDER) -- Will replace with actual placeholders GROUP BY mfa.artist_id, mf.library_id ), - artist_participant_counter AS ( + artist_participant_counter AS MATERIALIZED ( SELECT mfa.artist_id, mf.library_id, 'maincredit' AS role, @@ -522,14 +524,14 @@ func (r *artistRepository) RefreshStats(allArtists bool) (int64, error) { AND mfa.role IN ('albumartist', 'artist') GROUP BY mfa.artist_id, mf.library_id ), - combined_counters AS ( + combined_counters AS MATERIALIZED ( SELECT artist_id, library_id, role, album_count, count, size FROM artist_role_counters UNION ALL SELECT artist_id, library_id, role, album_count, count, size FROM artist_total_counters UNION ALL SELECT artist_id, library_id, role, album_count, count, size FROM artist_participant_counter ), - library_artist_counters AS ( + library_artist_counters AS MATERIALIZED ( SELECT artist_id, library_id, jsonb_object_agg(