Skip to content

perf(library): pre-warm songs cache + off-thread fetch#165

Merged
ghenry22 merged 1 commit into
masterfrom
perf/songs-library-prewarm
Jun 2, 2026
Merged

perf(library): pre-warm songs cache + off-thread fetch#165
ghenry22 merged 1 commit into
masterfrom
perf/songs-library-prewarm

Conversation

@ghenry22
Copy link
Copy Markdown
Owner

@ghenry22 ghenry22 commented Jun 2, 2026

Summary

Fixes the ~1s hang on the first browse to the Library Songs segment on a big library.

Root cause: the songs list was fetched synchronously on the JS thread during render (getAllSync + row→Child mapping), and the existing one-shot startup pre-warm was invalidated by the post-startup album-detail walk advancing songIndexStore.mutationCounter — re-arming the cold fetch before the user reached the segment.

Changes

  • Off-thread async fetchfetchAllSongsByTitleAsync() reads via expo-sqlite getAllAsync (background native thread) with the row mapping chunked (2000-row slices + setTimeout(0) yields), so neither stage blocks the JS thread. Sync variant kept for sync callers/tests; InternalDb gains getAllAsync.
  • Non-blocking hookuseAllSongsByTitle returns the warm cache synchronously/instantly; a cold miss returns [] + loading and fills async, re-rendering when ready. In-flight fetches are shared (a cold mount racing the auto-warmer awaits the same fetch).
  • Resilient auto-warmerstartSongLibraryCacheAutoWarm(): initial warm after startup interactions, then a debounced (2s) re-warm on every mutationCounter change, run on idle. Survives the album-detail walk that invalidated the old warm. Started from runDeferredStartup; removed the queueMicrotask warm in rehydrate.ts.
  • UI — Songs list spinner now reflects the hook's loading.

Testing

  • tsc --noEmit clean
  • Full jest green (7012 tests, 360 suites; +4 async-fetch tests)
  • On-device (big library, tablet/phone): first Songs browse instant after sync settles; brief spinner instead of freeze if browsed mid-sync; filters + pull-to-refresh intact

First browse to the Songs segment on a big library hung ~1s: the cold
fetch ran synchronously during render, and the one-shot startup pre-warm
was invalidated by the post-startup album-detail walk advancing
songIndexStore.mutationCounter.

- Async fetch (getAllAsync, chunked mapping) so the SQLite read and row
  mapping never block the JS thread.
- Hook returns a loading flag and fills the cache async on a cold miss;
  warm hits stay synchronous/instant. In-flight fetches are shared.
- Resilient auto-warmer: initial warm after interactions, then a
  debounced re-warm on every song-index mutation, run on idle. Replaces
  the invalidated queueMicrotask warm in rehydrate.
@ghenry22 ghenry22 merged commit 0e232eb into master Jun 2, 2026
1 check passed
@ghenry22 ghenry22 deleted the perf/songs-library-prewarm branch June 2, 2026 13:15
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.

1 participant