Summary
GET /events/past (EventsController#past) takes a median 466ms per render (max 17.3s) and issues 33 queries (median db_runtime 80ms, view_runtime 118ms). The action sets an ETag via fresh_when, so conditional requests are cheap, but non-conditional clients (first visit, stale cache, most API clients) pay the full render every time.
The render path:
paginated_events runs a UNION ALL across Workshops, Meetings, and Events, counts at the DB level, and returns only the 20 rows for the current page
load_events runs three eager_load queries (workshops, meetings, events) for those rows
fetch_past_events groups the rows by date and wraps each in a presenter (EventPresenter.decorate_collection)
- The view renders
EventCardComponent.with_collection — each card is fragment-cached (event_card_component/* keys in Solid Cache)
33 queries is well over the handful that steps 1–2 need, so something in decoration or rendering loads associations the current eager loads don't cover.
Measured
3h window on 2026-09-16/17, from production canonical logs: 127 requests, median 466ms, max 17.3s. Stale by the time you read this — re-measure before starting.
Suggested directions
- Capture the 33 queries locally (Bullet or Prosopite against a production dump) and identify the associations touched during decoration and card rendering beyond the current
eager_load calls
- Add the missing associations to the eager loads or batch them into a single preload, preserving the UNION order
- Check the hit rate on the per-event fragments before adding more caching — they may already absorb most of the view time
Verify
Query count and median duration drop; ETag behaviour unchanged.
Related
Summary
GET /events/past(EventsController#past) takes a median 466ms per render (max 17.3s) and issues 33 queries (mediandb_runtime80ms,view_runtime118ms). The action sets an ETag viafresh_when, so conditional requests are cheap, but non-conditional clients (first visit, stale cache, most API clients) pay the full render every time.The render path:
paginated_eventsruns a UNION ALL across Workshops, Meetings, and Events, counts at the DB level, and returns only the 20 rows for the current pageload_eventsruns threeeager_loadqueries (workshops, meetings, events) for those rowsfetch_past_eventsgroups the rows by date and wraps each in a presenter (EventPresenter.decorate_collection)EventCardComponent.with_collection— each card is fragment-cached (event_card_component/*keys in Solid Cache)33 queries is well over the handful that steps 1–2 need, so something in decoration or rendering loads associations the current eager loads don't cover.
Measured
3h window on 2026-09-16/17, from production canonical logs: 127 requests, median 466ms, max 17.3s. Stale by the time you read this — re-measure before starting.
Suggested directions
eager_loadcallsVerify
Query count and median duration drop; ETag behaviour unchanged.
Related
/events/upcomingaction, same code path; share findings