fix(db): make album ReplayGain backfill scale to large libraries

The windowed-CTE backfill was compiled as a correlated scalar subquery
re-evaluated once per album (a full media_file group-by + window sort each
time), which never finished on a large library. Stage the ReplayGain-bearing
rows into an indexed temp table once and update only the affected albums:
~14s on a 727MB / 6945-album library, vs. effectively never.
This commit is contained in:
Deluan 2026-07-18 22:59:53 -04:00
parent 4efd92cf83
commit b27d6f61ae

View File

@ -12,28 +12,25 @@ func init() {
}
func upAddAlbumReplaygain(ctx context.Context, tx *sql.Tx) error {
// Backfill the most-frequent non-null value per album, matching MediaFiles.ToAlbum. A plain
// max() would pick a minority outlier, and GetTouchedAlbums never re-derives an unchanged album,
// so a wrong value would persist. The CTEs group media_file once instead of scanning it per album.
// Backfill the most-frequent value per album (matching MediaFiles.ToAlbum), staging RG-bearing rows
// into an indexed temp table — a correlated subquery over a windowed CTE re-scans media_file per album.
_, err := tx.ExecContext(ctx, `
ALTER TABLE album ADD COLUMN rg_album_gain real;
ALTER TABLE album ADD COLUMN rg_album_peak real;
WITH gain_mode AS (
SELECT album_id, rg_album_gain AS val,
row_number() OVER (PARTITION BY album_id ORDER BY count(*) DESC, rg_album_gain) AS rn
FROM media_file WHERE rg_album_gain IS NOT NULL
GROUP BY album_id, rg_album_gain
),
peak_mode AS (
SELECT album_id, rg_album_peak AS val,
row_number() OVER (PARTITION BY album_id ORDER BY count(*) DESC, rg_album_peak) AS rn
FROM media_file WHERE rg_album_peak IS NOT NULL
GROUP BY album_id, rg_album_peak
)
CREATE TEMP TABLE _rg_backfill AS
SELECT album_id, rg_album_gain, rg_album_peak FROM media_file
WHERE rg_album_gain IS NOT NULL OR rg_album_peak IS NOT NULL;
CREATE INDEX _rg_backfill_album ON _rg_backfill(album_id);
UPDATE album SET
rg_album_gain = (SELECT val FROM gain_mode WHERE gain_mode.album_id = album.id AND gain_mode.rn = 1),
rg_album_peak = (SELECT val FROM peak_mode WHERE peak_mode.album_id = album.id AND peak_mode.rn = 1);
rg_album_gain = (SELECT rg_album_gain FROM _rg_backfill WHERE _rg_backfill.album_id = album.id AND rg_album_gain IS NOT NULL
GROUP BY rg_album_gain ORDER BY count(*) DESC, rg_album_gain LIMIT 1),
rg_album_peak = (SELECT rg_album_peak FROM _rg_backfill WHERE _rg_backfill.album_id = album.id AND rg_album_peak IS NOT NULL
GROUP BY rg_album_peak ORDER BY count(*) DESC, rg_album_peak LIMIT 1)
WHERE album.id IN (SELECT album_id FROM _rg_backfill);
DROP TABLE _rg_backfill;
`)
return err
}