From b27d6f61ae7813d47eb4479637187bf26ab35adb Mon Sep 17 00:00:00 2001 From: Deluan Date: Sat, 18 Jul 2026 22:59:53 -0400 Subject: [PATCH] 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. --- .../20260719005427_add_album_replaygain.go | 31 +++++++++---------- 1 file changed, 14 insertions(+), 17 deletions(-) diff --git a/db/migrations/20260719005427_add_album_replaygain.go b/db/migrations/20260719005427_add_album_replaygain.go index 79c8b8b3e..7f5665947 100644 --- a/db/migrations/20260719005427_add_album_replaygain.go +++ b/db/migrations/20260719005427_add_album_replaygain.go @@ -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 }