Skip to content

Serve /events/past from a page-level fragment on cache hits - #2979

Merged
mroderick merged 2 commits into
masterfrom
perf/past-events-page-fragment
Oct 1, 2026
Merged

mroderick merged 2 commits into
masterfrom
perf/past-events-page-fragment

Conversation

@mroderick

Copy link
Copy Markdown
Collaborator

Warm hits of /events/past now serve the whole page from one cached fragment. On a hit the controller skips the COUNT, the eager loads, the decoration and the render; only the MAX(updated_at) etag query runs. The benchmarked prototype in #2976 measured an 84% cut in per-request allocations for the warm path (43,426 -> 7,104 GC objects) and 6 queries down to 1.

The key is [:events_past_page, I18n.locale, page, MAX(updated_at).to_f] across workshops, meetings, events and members. Any tracked row change rotates every key, so edits appear on the next request. The page number is clamped the same way the fetch pipeline clamps it, so ?page=0 and ?page=1 share one fragment.

Ref #2976

Review notes

Focus areas, in order:

  • Cache key design: parity between the controller's read_fragment and the view's cache write (skip_digest: true), and the .to_f encoding of MAX(updated_at) — a raw Time stringifies differently between write and read (same convention as sitemaps/show.xml.builder).
  • Staleness semantics: deletions do not bump MAX(updated_at), so a deleted event stays visible for up to 30 minutes (expires_in). The unchanged ETag path keeps its pre-existing exposure: a client holding a warm If-None-Match gets a 304 until any tracked row changes. skip_digest also means deploys serve old markup for up to 30 minutes.
  • Cache-flood gate: keys are minted only for pages that exist (requested_page <= @pagy.pages). Without the gate, bogus page numbers mint unlimited fragment keys, and Solid Cache evicts its oldest entries globally once over max_size.

Deliberately not done: the fragment covers /events/past only; /events/upcoming keeps its current path.

Post-Deploy Monitoring & Validation

Intended behaviour: steady-state /events/past requests drop to a single query (the MAX(updated_at) etag), visible in Scout APM query counts for the endpoint; pages keep rendering cards and pagination correctly across deploys. Regression signals: user reports of content stale beyond the 30-minute horizon while tracked tables are changing, or Solid Cache errors on the endpoint. Validation window: first 24-48 hours after deploy; owner: codebar maintainers.

A warm hit now skips the COUNT, the eager loads, the decoration and the
render, serving the whole page from one fragment keyed on locale, clamped
page and MAX(updated_at) across the tracked tables. A 30-minute expiry
bounds staleness that MAX(updated_at) alone cannot see (record deletions).
Keys are minted only for pages that exist, so bogus page numbers cannot
flood the cache store.
@mroderick
mroderick force-pushed the perf/past-events-page-fragment branch from 1c71f4d to 23f7a37 Compare October 1, 2026 11:39
@mroderick
mroderick marked this pull request as ready for review October 1, 2026 12:00
@mroderick
mroderick requested a review from olleolleolle October 1, 2026 12:00
@mroderick
mroderick merged commit 979df42 into master Oct 1, 2026
11 checks passed
@mroderick
mroderick deleted the perf/past-events-page-fragment branch October 1, 2026 13:11
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.

2 participants