Skip to content

perf: cross-request health cache, asset cache headers, build-time warmup#74

Open
ndandan wants to merge 4 commits into
Shoshuo:mainfrom
ndandan:pr/perf-health-cache
Open

perf: cross-request health cache, asset cache headers, build-time warmup#74
ndandan wants to merge 4 commits into
Shoshuo:mainfrom
ndandan:pr/perf-health-cache

Conversation

@ndandan

@ndandan ndandan commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

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:

  • The in-process array stays as a first-level memo; the pool is the second level.
  • 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.
  • The pool is a nullable trailing constructor arg — legacy test constructors and DI configs without it keep working (in-process behaviour is unchanged when no pool is wired).

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:

  • AssetMapper-compiled /assets/* (content-hashed filenames) with Cache-Control: public, max-age=31536000, immutable
  • unfingerprinted /static/* vendor bundles (Tabler, Chart.js) and /img/* with a one-day TTL

Repeat page loads stop re-negotiating ~600 KB of CSS/JS.

3. Prod cache pre-warmed at image build

The Dockerfile runs cache:warmup after asset-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-generated APP_SECRET doesn't invalidate the baked cache.

Verification

  • make check (lint + Twig lint + full PHPUnit suite) green on this branch.
  • Running live on our instance for several days.

CHANGELOG entries included under [Unreleased] → Performance.

🤖 Generated with Claude Code

ndandan and others added 4 commits July 6, 2026 13:25
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>
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