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.
This commit is contained in:
Deluan 2026-09-04 23:02:09 -04:00
commit 63e7fe0925

View file

@ -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(