From 7922e93c756132b8745ee75fd719d7dffa69ff92 Mon Sep 17 00:00:00 2001 From: Deluan Date: Fri, 4 Sep 2026 22:48:39 -0400 Subject: [PATCH] perf(pglite): purge unused tags with EXCEPT, the NOT EXISTS form was still per-row The CTE placed inside NOT EXISTS is correlated and re-evaluated for every tag row, so it was as slow as NOT IN on the real library (still running after 10 minutes). id IN (SELECT id FROM tag EXCEPT ) builds the unused set once with a hash set-op: about 1 s at 1M tag references, with or without planner statistics. --- persistence/tag_repository.go | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/persistence/tag_repository.go b/persistence/tag_repository.go index dec9434d8..219f0b8af 100644 --- a/persistence/tag_repository.go +++ b/persistence/tag_repository.go @@ -82,20 +82,20 @@ func (r *tagRepository) GetAll(name model.TagName, options ...model.QueryOptions } func (r *tagRepository) purgeUnused() error { - // NOT EXISTS over a materialized set: NOT IN re-scans the ~1M-row subquery per tag (40x slower). + // EXCEPT builds the unused set once; NOT IN / a correlated NOT EXISTS re-evaluate the ~1M-row + // used-tags subquery per tag row (minutes instead of ~1 s). del := Delete(r.tableName).Where(` - not exists ( - with used(id) as materialized ( - select elem->>'id' - from album - cross join lateral jsonb_each(coalesce(album.tags, '{}'::jsonb)) as e(k, arr) - cross join lateral jsonb_array_elements(e.arr) as elem - union - select elem->>'id' - from media_file - cross join lateral jsonb_each(coalesce(media_file.tags, '{}'::jsonb)) as e(k, arr) - cross join lateral jsonb_array_elements(e.arr) as elem) - select 1 from used where used.id = ` + r.tableName + `.id) + id in (select id from ` + r.tableName + ` + except + select elem->>'id' + from album + cross join lateral jsonb_each(coalesce(album.tags, '{}'::jsonb)) as e(k, arr) + cross join lateral jsonb_array_elements(e.arr) as elem + except + select elem->>'id' + from media_file + cross join lateral jsonb_each(coalesce(media_file.tags, '{}'::jsonb)) as e(k, arr) + cross join lateral jsonb_array_elements(e.arr) as elem) `) c, err := r.executeSQL(del) if err != nil {