Skip to content

perf(segcache): load catalog in background to avoid blocking startup#757

Merged
javi11 merged 7 commits into
javi11:mainfrom
evulhotdog:perf/segcache-async-catalog-load
Jul 7, 2026
Merged

perf(segcache): load catalog in background to avoid blocking startup#757
javi11 merged 7 commits into
javi11:mainfrom
evulhotdog:perf/segcache-async-catalog-load

Conversation

@evulhotdog

Copy link
Copy Markdown
Contributor

Problem: Startup hangs while loadCatalog() stats every cached segment on disk synchronously (one os.Stat per ~750KB segment, can be tens of thousands of calls).

Fix: Moved catalog loading from the NewSegmentCache constructor into Manager.Start as a background goroutine. Startup no longer blocks; the cache serves misses from Usenet until the catalog hydrates. Merge-under-lock prevents a Put during load from being clobbered.

loadCatalog was called synchronously in NewSegmentCache, statting every cached segment on disk before the constructor returned. On a large cache this blocked the entire serve startup path for seconds to minutes.

Move the load into Manager.Start as a third wg-tracked goroutine (mirroring the existing cleanupLoop/catalogFlushLoop split). NewSegmentCache now constructs only; LoadCatalog is exported so tests can call it synchronously.

The merge loop now skips keys already present (a Put during load already wrote a newer entry and .seg file) instead of clobbering c.items, fixing the race that the old overwrite would have introduced.
The old message "Segment cache initialized" implied the catalog was warm. Now that the load runs in a background goroutine, reword to "Segment cache started (catalog loads in background)".
@javi11

javi11 commented Jul 3, 2026

Copy link
Copy Markdown
Owner

@evulhotdog nice one, but don't you think that blocking the put while loading can cause cpu leak if there is a lot of put waiting to succed? maybe we should ignore the put while loading

@evulhotdog

Copy link
Copy Markdown
Contributor Author

@javi11 yeah, making it a no-op is probably easier to handle and reduces complexity. I was just trying to think about continuity and ensuring that we caching wherever we can. In general, altmount is a long running process, so this should be an edge case where you're streaming right away.

I was just tired of waiting 5 minutes to load my cache each time, since it stat's each file!

I simplified things, let me know what you think.

Comment thread internal/nzbfilesystem/segcache/manager.go Outdated
@evulhotdog

Copy link
Copy Markdown
Contributor Author

@javi11 ready for ya again.

Comment thread internal/nzbfilesystem/segcache/cache.go Outdated
Comment thread internal/nzbfilesystem/segcache/cache.go
Per review: the loading flag's lifecycle (set on entry, cleared on exit)
now lives entirely in LoadCatalog. NewSegmentCache no longer pokes at
load-state, so a cache can no longer be left permanently gating Puts if
a future caller forgets to call LoadCatalog.
@evulhotdog evulhotdog requested a review from javi11 July 7, 2026 20:02
@javi11 javi11 merged commit 36c24a1 into javi11:main Jul 7, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants