From 8a138797ddb6dda805e0fc760be88e50985d1f77 Mon Sep 17 00:00:00 2001 From: Caleb Allen Date: Sun, 29 Mar 2026 21:56:52 -0400 Subject: [PATCH] fix: mark search index ready at service creation, not just during build MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The search index ready flag is an in-memory bool that resets to false on every app restart. It was only set to true inside build(), which runs in a goroutine after SoftScanAllLibraries completes. If the user searched before the build goroutine started, IsReady() returned false and the search took the slow path (LB popularity + cross-ref: ~2.3s) even though the SQLite index had all the data from the previous build. Now MarkReadyIfPopulated() is called eagerly in NewExploreService — the index is queryable as soon as the service is constructed, before any goroutines launch. If the explore_index table has rows, ready=true immediately. --- backend/explore/explore.go | 1 + backend/explore/searchindex.go | 7 +++++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/backend/explore/explore.go b/backend/explore/explore.go index c43536c..9a91ad3 100644 --- a/backend/explore/explore.go +++ b/backend/explore/explore.go @@ -48,6 +48,7 @@ func NewExploreService(logger *slog.Logger, db *database.DB) *Service { db, cache, mbBackgroundLimiter, logger.WithGroup("artist-image"), ) index := NewSearchIndex(db, lb, artistImg, logger.WithGroup("search-index")) + index.MarkReadyIfPopulated() // make index queryable immediately if data exists libMBID := NewLibraryMBIDIndex(db) logger.Info("explore service created") diff --git a/backend/explore/searchindex.go b/backend/explore/searchindex.go index 32ffa1c..e402c73 100644 --- a/backend/explore/searchindex.go +++ b/backend/explore/searchindex.go @@ -508,7 +508,7 @@ func (si *SearchIndex) build(ctx context.Context) { si.logger.Info("search index build starting") // Mark ready from existing rows so search works during the build. - si.markReadyIfPopulated() + si.MarkReadyIfPopulated() indexLimiter := NewRateLimiterN(indexerRate) indexLB := NewListenBrainzClient(indexLimiter, si.lb.cache, si.logger.WithGroup("indexer")) @@ -1609,7 +1609,10 @@ func (si *SearchIndex) setMeta(key, value string) { } } -func (si *SearchIndex) markReadyIfPopulated() { +// MarkReadyIfPopulated sets the index as ready for querying if it +// already contains data from a previous build. Called eagerly at +// service creation so the index is queryable before StartBuild runs. +func (si *SearchIndex) MarkReadyIfPopulated() { rows, err := si.db.QueryContext("SELECT COUNT(*) FROM explore_index") if err != nil { return