Skip to content

Memoize Site.instance to fix N+1-style DB round-trips - #687

Closed
maxkadel wants to merge 2 commits into
mainfrom
site-instance-memoization
Closed

Memoize Site.instance to fix N+1-style DB round-trips#687
maxkadel wants to merge 2 commits into
mainfrom
site-instance-memoization

Conversation

@maxkadel

@maxkadel maxkadel commented Jul 29, 2026

Copy link
Copy Markdown
Member

Summary

  • Site.instance (upstream Hyku, hyrax-webapp/app/models/site.rb) uses first_or_create, which is never memoized — every call is a fresh database round-trip.
  • Profiling a single /catalog page load (see pals-stress-tests for the full writeup) found Site.instance called ~650-680 times per page render, almost entirely through the delegate :account, ..., to: :instance line — i.e. the ubiquitous Site.account convenience accessor, not one obvious N+1 loop.
  • Adds app/models/site_decorator.rb memoizing it per-request via RequestStore (already a transitive dependency, no new gem needed) — the same pattern already used for current_account in ApplicationController/HykuHelper. RequestStore clears automatically at each request boundary, lining up with when Apartment re-resolves the tenant, so there's no risk of a stale Site leaking across tenants on a reused Puma thread.
  • Written as a decorator rather than editing the vendored hyrax-webapp submodule directly, so this can be cleanly contributed back upstream to Hyku later.

Test plan

  • Added spec/models/site_decorator_spec.rb first — confirmed it fails against unmemoized Site.instance (first_or_create called 2x instead of 1x, different objects returned across calls) before adding the decorator.
  • Confirmed green after adding site_decorator.rb, including that the existing global-tenant (NilSite) branch and post-RequestStore.clear! behavior are both unaffected.
  • Verified locally via docker compose (removed/restored the decorator file to directly confirm the red→green transition, not just trust the diff).
  • Deployed to staging and confirmed live: total SQL query count for one /catalog render dropped from ~1510 to 249 (~84%), the two dominant query patterns from the profiling are completely gone.
  • Re-ran the same tuned stress-test load before/after this fix on staging: query count dropped as expected, but overall page wall-time was flat to very slightly worse (14.8s vs 13.8s p50 on catalog) — the database round-trip was already cheap thanks to Rails' query cache, so this fix removes real, measurable overhead but isn't the fix for the page's overall slowness. Full writeup in the pals-stress-tests README.

🤖 Generated with Claude Code

@github-actions

github-actions Bot commented Jul 29, 2026

Copy link
Copy Markdown

Test Results

      4 files  ±    0        4 suites  ±0   1m 7s ⏱️ -1s
106 642 tests +1 580  106 639 ✅ +1 580  3 💤 ±0  0 ❌ ±0 
    401 runs  +    6      398 ✅ +    6  3 💤 ±0  0 ❌ ±0 

Results for commit 3d57ab1. ± Comparison against base commit 0d1c3a4.

♻️ This comment has been updated with latest results.

maxkadel and others added 2 commits July 29, 2026 15:38
Site.instance (upstream Hyku) uses first_or_create with no memoization,
so every call is a fresh DB round-trip. Profiling a single /catalog
page load found it called ~650-680 times via the `delegate :account,
..., to: :instance` line alone (see pals-stress-tests repo for the
full profiling writeup). This test asserts the intended fix's
behavior -- currently fails against unmemoized Site.instance.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Site.instance (upstream Hyku) uses first_or_create with no
memoization -- every call is a fresh DB round-trip. Profiling found a
single /catalog page load calls it ~650-680 times, almost entirely
through the `delegate :account, ..., to: :instance` line (the
ubiquitous Site.account accessor), not through one obvious N+1 loop.

Memoizes via RequestStore (already a transitive dependency), matching
how current_account is already memoized in ApplicationController and
HykuHelper. RequestStore clears automatically at each request
boundary, lining up with when Apartment re-resolves the tenant -- no
risk of a stale Site leaking across tenants on a reused Puma thread.

Verified locally (docker compose): confirmed genuine red (2 of 4
examples fail) with this file removed, and green with it restored.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@maxkadel
maxkadel force-pushed the site-instance-memoization branch from 78572dc to 3d57ab1 Compare July 29, 2026 13:38
@maxkadel
maxkadel changed the base branch from pals-stress-test-staging-nginx-repro to main July 29, 2026 13:39
@maxkadel
maxkadel marked this pull request as draft July 29, 2026 14:32
@maxkadel
maxkadel marked this pull request as ready for review July 29, 2026 14:54
@maxkadel

Copy link
Copy Markdown
Member Author

I'm not sure why it's not showing up on the main PR, but CI is running here - https://github.com/notch8/palni_palci_knapsack/actions/runs/30469565277

@maxkadel maxkadel closed this Jul 31, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: No status

Development

Successfully merging this pull request may close these issues.

1 participant