perf: cross-request health cache, asset cache headers, build-time warmup#74
Open
ndandan wants to merge 4 commits into
Open
perf: cross-request health cache, asset cache headers, build-time warmup#74ndandan wants to merge 4 commits into
ndandan wants to merge 4 commits into
Conversation
statusFor()'s 10s memo lived in a per-object array, which classic (non-worker) FrankenPHP discards with the request — every topbar or dashboard health poll, from every open tab, re-pinged all configured services with sequential blocking curl. Verdicts now go through an injected cache.app pool (nullable-last ctor arg, so legacy positional test constructors are untouched) with the same 10s TTL: the whole install performs at most one probe sweep per window. invalidate() rotates a generation token so admin Test connection / settings save still re-probes instantly without having to enumerate per-instance keys. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
AssetMapper-compiled /assets/* files carry a content digest in the filename, so they are safe to cache forever (a new build mints a new URL): public, max-age=31536000, immutable. The unfingerprinted /static/* vendor bundles (Tabler, Chart.js) and /img/* get one day — enough to stop re-negotiating ~600 KB of CSS/JS on every page load without pinning a stale UI past an image upgrade (Caddy's ETag revalidates after expiry). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Without a baked cache the first request after every container (re)start pays the 1-3s container/route/Twig compile. cache:warmup runs after asset-map:compile; env vars stay runtime-resolved placeholders in the compiled container, so the boot-generated APP_SECRET and the user's DATABASE_URL don't invalidate it. init.sh only chowns var/ and the volume covers var/data alone, so the warmed cache survives into the running container. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…eaders and build-time warmup Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
ndandan
added a commit
to ndandan/Prismarr
that referenced
this pull request
Jul 6, 2026
…ests, SABnzbd, Plex activity, Prowlarr), add Deluge coverage, update pending-PR list (Shoshuo#74/Shoshuo#75/Shoshuo#76) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Three small, independent performance fixes bundled as one slice — they share a theme (cut repeat work between requests) but touch disjoint files.
1. Service-health pings cached across requests
HealthService::statusFor()'s 10 s memo lives in a per-object array. Classic (non-worker) FrankenPHP throws the object away with each request, so every topbar/dashboard health poll — from every open tab — re-pinged all configured services with sequential blocking calls.The verdicts are now shared through
cache.app(same 10 s TTL), so the whole install performs at most one probe sweep per window:invalidate()(admin "Test connection" / settings save) rotates a generation token mixed into every status key — the next read mints a new generation, orphaning all previous entries at once, so a stale verdict can never outlive a reconfiguration.Covered by a new
HealthServiceSharedCacheTest(cross-"request" reuse, TTL expiry, invalidation via generation rotation).2. Real browser-cache headers for static assets
Caddy now serves:
/assets/*(content-hashed filenames) withCache-Control: public, max-age=31536000, immutable/static/*vendor bundles (Tabler, Chart.js) and/img/*with a one-day TTLRepeat page loads stop re-negotiating ~600 KB of CSS/JS.
3. Prod cache pre-warmed at image build
The Dockerfile runs
cache:warmupafterasset-map:compile, so the first request after a container (re)start no longer pays the 1–3 s container/route/Twig compile. Env vars stay runtime-resolved placeholders, so the boot-generatedAPP_SECRETdoesn't invalidate the baked cache.Verification
make check(lint + Twig lint + full PHPUnit suite) green on this branch.CHANGELOG entries included under
[Unreleased]→ Performance.🤖 Generated with Claude Code