Perf: cache the ontology list, removing ~97% of /search response time#245
Open
mdorf wants to merge 2 commits into
Open
Perf: cache the ontology list, removing ~97% of /search response time#245mdorf wants to merge 2 commits into
mdorf wants to merge 2 commits into
Conversation
Loading all ~1,200 ontologies from the backend on every request is ~97% of /search response time (876 ms of 899 ms avg in production tracer data). The list changes on the scale of days, so cache it per-process with a configurable TTL (LinkedData::OntologiesAPI.settings.ontology_list_cache_ttl, default 300 s, 0 disables caching). Returns a defensive copy on every read because some callers filter the array destructively. Load failures propagate and are never cached. Both cache methods are added to the New Relic tracer registry so hits (~0 ms) and misses are visible in production segments. Tests invalidate per test method since they create ontologies at will.
Both branches of restricted_ontologies reloaded the full ontology list per request: the scoped path (98% of /search traffic) via ontology_objects_from_params plus a second access-control query, the site-wide path directly with a Goo viewOf filter. All of them now read from OntologyListCache, which preloads the attribute union those callers need; views are excluded in Ruby, and per-request slice/user/ACL filtering is unchanged. The acronym/URI maps also read from the cache instead of issuing their own per-request query.
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## develop #245 +/- ##
===========================================
+ Coverage 78.60% 78.76% +0.15%
===========================================
Files 67 68 +1
Lines 3754 3777 +23
===========================================
+ Hits 2951 2975 +24
+ Misses 803 802 -1
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Production tracer data from #243 (deployed in v7.4.3) shows
restricted_ontologiesaverages 876 ms of the 899 ms/searchpipeline (~97%), and thesearch_scopedattribute revealed why both request styles pay it: the scoped path (98% of traffic) loads all ~1,200 ontologies with full attributes viaontology_objects_from_paramsand selects the requested ones in Ruby, while the site-wide path loads them all directly. Full baseline: #244 (comment).This PR introduces
OntologyListCache, a process-level TTL cache of the loaded ontology list, and points the shared helpers at it. Expected effect:/searchaverage drops from ~900 ms to roughly the 15-60 ms the rest of the pipeline costs; annotator, recommender, mappings, analytics, and properties search shed the same per-request tax.Addresses #244.
Design (agreed with @mdorf)
Ontologyobjects; no Redis (goo objects don't marshal cleanly; rebuilding objects re-pays the materialization this removes).LinkedData::OntologiesAPI.settings.ontology_list_cache_ttl, default 300 s;0disables caching entirely (debug kill switch). Override per environment in the deployment config if needed.filter_access) is still computed per request; only the raw list is cached.dupbecauseget_term_search_queryfilters the array destructively. Cached objects are shared within a worker:bringon them is safe, attribute assignment is not (documented in the module).viewOf), which lets the scoped branch drop its second query and moves the site-wide view filter into Ruby.Observability
OntologyListCache.all(per-request, ~0 ms on hit) and.load_ontologies(fires only on miss) are added to the #243 tracer registry, so cache behavior is directly visible in production segments; the registry regression test covers them automatically.Evidence
restricted_ontologies876 ms avg; scoped 962 ms / site-wide 878 ms transaction averages.Test plan
test/lib/test_ontology_list_cache.rb(8 tests): caching across calls, defensive-copy isolation against destructive callers, TTL expiry (deterministic clock swap), TTL=0 bypass, default value, explicit invalidation, failed-load-not-cached, and a real-loader test asserting all consumer-needed attributes are preloaded.test/test_case.rb).Rollback / kill switches
Set
ontology_list_cache_ttl = 0(config + restart) to disable the cache without reverting;NEWRELIC_CUSTOM_TRACERS=offremains available for the tracers themselves.