Skip to content

perf: cache library payload and batch the per-page *arr calls#56

Open
ndandan wants to merge 1 commit into
Shoshuo:mainfrom
ndandan:pr/library-perf-rework
Open

perf: cache library payload and batch the per-page *arr calls#56
ndandan wants to merge 1 commit into
Shoshuo:mainfrom
ndandan:pr/library-perf-rework

Conversation

@ndandan

@ndandan ndandan commented Jun 17, 2026

Copy link
Copy Markdown

Summary

Speeds up the Radarr/Sonarr library pages (/medias/{slug}/films and /series) by caching the normalised library payload per instance and fetching the per-page *arr endpoints concurrently instead of sequentially.

Motivation

MediaController::films() / series() re-fetched and re-normalised the full getMovies() / getSeries() payload on every visit, and fetched status, queue, indexers, health and calendar one after another. So a navigate-away-and-back paid the full cost again, and a slow or unreachable instance stacked one timeout per call.

What changed

  • MediaLibraryCache — a 45 s per-instance cache of the normalised library payload. Empty results are not cached (a transient failure isn't pinned for the window), and library mutations write-through-invalidate so user changes still show immediately.
  • RadarrClient::multiGet() / SonarrClient::multiGet() — fetch the per-page endpoints in a single curl_multi batch instead of sequentially, with the same per-handle semantics as get(): SSRF protocol guard, CURLOPT_REDIR_PROTOCOLS, connect/total timeouts, and the per-instance circuit breaker.
  • MediaController rewired to the cache + batch; the existing public normalizers are reused on the batch payloads, so there's no behaviour change to the rendered page.

Performance

In local testing (LAN, single run), the cold first load is unchanged, but a warm revisit within the cache window is ~3× faster (≈5.0 s → ≈1.4 s on my library). A slow/unreachable instance now costs one timeout window for the whole page instead of stacking one per call.

Tests

  • MediaLibraryCacheTest — TTL, empty-not-cached, write-through invalidation.
  • RadarrClientMultiGetTest — batch shape + fail-open semantics.
  • MediaLibraryCacheInvalidationTest — mutations invalidate the entry.
  • MediaLibraryPageTest — smoke test: the page renders cleanly when the *arr is unreachable (no 500, no hang).
  • Existing MediaController tests updated for the new constructor dependency.

make check (lint + Twig + full PHPUnit) is green.

Definition of Done

  • Works end-to-end; no leftover debug; English comments; no credentials; no unused imports
  • Unit tests for the new logic; make check 100% green
  • No schema/migration changes
  • Read-only GETs; SSRF guard preserved on the batched calls; no leaked exception messages
  • CHANGELOG [Unreleased] updated

Happy to split further, adjust naming, or tune the 45 s TTL / make it configurable if you'd prefer.

Radarr/Sonarr library pages re-fetched and re-normalised the full getMovies()/getSeries() payload on every visit, and fetched status, queue, indexers, health and calendar sequentially.

- MediaLibraryCache: 45s per-instance cache of the normalised payload; empty results not cached; write-through invalidation on library mutations. - RadarrClient/SonarrClient multiGet(): one curl_multi batch for the per-page endpoints (same SSRF guard, timeouts and circuit breaker as get()). - MediaController films()/series() rewired to cache + batch; public normalizers reused on the batch payloads.

Revisits ~3x faster; a slow/unreachable instance costs one timeout window instead of stacking. Covered by MediaLibraryCacheTest, MediaLibraryCacheInvalidationTest, RadarrClientMultiGetTest and a MediaLibraryPageTest smoke test (renders when the arr is unreachable).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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