Skip to content

Cache the wall of fame body with a daily key - #2978

Merged
mroderick merged 4 commits into
masterfrom
perf/wall-of-fame-cache
Oct 1, 2026
Merged

mroderick merged 4 commits into
masterfrom
perf/wall-of-fame-cache

Conversation

@mroderick

@mroderick mroderick commented Oct 1, 2026 •

Copy link
Copy Markdown
Collaborator

Caches the rendered wall of fame body (DashboardController#wall_of_fame, /coaches) in Solid Cache under a daily key. Closes #2974.

What changes

  • The controller caches the rendered template body, without the layout, expiring after 24 hours. A cache hit costs one cache read plus a fresh layout render.
  • The page param is coerced with pagy's own coercion before it enters the key, so arbitrary strings cannot expand the key space, and pagy links keep only the year and page params, so the first request to fill a key cannot freeze unrelated query params into the cached links.
  • The layout renders on every request, so fingerprinted asset URLs and meta tags never go stale — the same body-without-layout approach the sponsors page took in Cache the rendered /sponsors page body — recurring 1.3s render stalls dyno threads #2885.

Measured impact

Benchmarks against master on the local production dump (production mode, Solid Cache, real data: 3,575 attended coaches), median of 5 warm requests:

Path master this branch change
/coaches 0.097s, 5 queries, 13.9K allocs 0.014s, 0 queries, 4.5K allocs −86% time, −68% allocs
/coaches?page=2 0.069s, 5 queries, 13.0K allocs 0.006s, 0 queries, 4.6K allocs −91% time, −65% allocs
/coaches?year=2024 0.086s, 5 queries, 14.7K allocs 0.007s, 0 queries, 4.6K allocs −92% time, −69% allocs

A cache miss costs the same as an uncached render plus two cache writes, and only the first request per key per day pays it. Applied to the drain figures in #2974 (42.7M GC allocations/week), the warm-hit reduction projects to roughly 28M allocations/week saved.

Staleness

The list changes only when a coach attends a workshop, so a 24-hour window is acceptable for this page. The controller comment documents that the key version (v2) must be bumped when the view or its partials change.

Only the current year's data changes with new attendance, so past-year keys drop the date segment and carry no explicit expiry: entries age out via Solid Cache's max_age (2 weeks by default) instead of rotating at midnight. On the production dump that leaves ~39 dated keys rotating daily and ~861 past-year keys (year × page × 3 locales) always warm. Profile edits on old coach cards propagate within that 2-week window rather than within 24 hours. Junk years keep the dated, expiring key so arbitrary requests cannot create immortal entries.

Reviewer notes

Focus on the cache key design and the staleness tradeoff:

  • Key: coaches/wall_of_fame/v2/#{date}/#{year}/#{page}/#{locale} with expires_in: 24.hours; past years (2013..current−1) drop the date segment and expiry. The date segment means a New Year rollover rotates the current year's keys automatically.
  • Coach cards for the current year can be up to 24 hours out of date; past years up to 2 weeks (Solid Cache max_age). If that proves too coarse, shorten the expiry — no key-format change needed.
Detail
  • From the issue's production measurements: 3,411 requests/7 days, 42.7M GC allocations (~6% of controller churn), p95 14.1K allocations per request.
  • The issue's sketch cached render_to_string including the layout; this PR deliberately renders the layout fresh instead. Caching fingerprinted asset URLs for 24 hours would serve stale asset links after a deploy, the reason Cache the rendered /sponsors page body — recurring 1.3s render stalls dyno threads #2885 cached only the partial.
  • The cache key normalises page to the integer pagy will render, and strips everything but year and pagy's own page key from link URLs. A pre-existing, unrelated bug in page_year? (application_helper.rb:82, Integer-vs-String comparison, so the active year button never highlights) is out of scope here; fixing it will require a key-version bump since the button state is inside the cached body.

Cache the rendered wall of fame template in Solid Cache, keyed on the
UTC date, selected year, page number, and locale, with a 24-hour
expires_in. Warm requests skip the distinct coach count, the grouped
top-coach query, and the coach card render, which accounted for ~6% of
all controller allocation churn on the site (issue #2974).

The layout renders fresh on every request so fingerprinted asset URLs
and meta tags are never stale; bump the v1 key segment when the view or
its partials change.
…ached links

Apply ce-code-review findings on the wall of fame cache:
- Coerce page to an integer with pagy's own coercion so arbitrary strings
  cannot expand the cache key space.
- Keep only the year (and pagy's page key) in pagy links so the filling
  request's junk query params are never frozen into the cached body.
…otation

Past years' coach attendance is fixed, so only the current year needs a
daily cache rotation. Past-year keys drop the date segment and carry no
explicit expiry: entries age out via Solid Cache's max_age (2 weeks by
default), so profile edits on old coach cards still propagate, just
slower. Junk years stay on the dated, expiring path so they cannot
create immortal entries.
@mroderick
mroderick marked this pull request as ready for review October 1, 2026 11:32
@mroderick
mroderick requested a review from olleolleolle October 1, 2026 11:32
Comment thread app/controllers/dashboard_controller.rb Outdated
@mroderick
mroderick merged commit 310fce6 into master Oct 1, 2026
11 checks passed
@mroderick
mroderick deleted the perf/wall-of-fame-cache branch October 1, 2026 12:18
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.

Cache the wall of fame body with a daily key (42.7M allocations/week, 361K max per request)

3 participants