Perf: instrument /search request path with New Relic method tracers#243
Merged
Conversation
GET /search averages ~890 ms, of which ~830 ms (92.9%) appears as a single opaque controller segment in New Relic; all I/O combined (Redis, Solr, AllegroGraph) accounts for under 60 ms. These tracers split the opaque block into named Custom/* segments covering query construction, the ontology access list load, per-document model building, and response serialization, so transaction traces name the method responsible for the time. Also records a search_scoped transaction attribute (the agent strips query strings from request.uri), enabling NRQL comparison of ontology-scoped vs site-wide searches. No behavior change: tracers are no-ops when the agent is disabled.
add_method_tracer silently no-ops when a target method is missing, so a rename or move could drop instrumentation without failing any test. Assert all nine traced methods are wrapped and that each wrapper is defined inside the newrelic_rpm gem.
Restructure config/newrelic_method_tracers.rb into a declarative
registry so instrumenting another endpoint is a one-line addition; the
regression test iterates the same registry, covering new entries
automatically.
Add tracers for the tree request path (LinkedData::Models::Class#tree,
tree_sorted, paths_to_root, defined in an ontologies_linked_data
concern but traceable from here), since GET .../classes/{cls}/tree is
the slowest transaction by average duration (1.61 s) and a known
future tuning target.
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## develop #243 +/- ##
===========================================
+ Coverage 78.59% 78.60% +0.01%
===========================================
Files 67 67
Lines 3752 3754 +2
===========================================
+ Hits 2949 2951 +2
Misses 803 803
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 was referenced Jul 23, 2026
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
GET /searchis the highest externally consumed endpoint and averages ~890 ms. New Relic attributes 92.9% of that (~830 ms) to a single opaque controller segment; all I/O combined is under 60 ms per request (Redis ~44 ms, Solr ~10 ms, AllegroGraph 0.02 calls/request). Production thread profiling cannot see inside the block either (workers are ~98% idle between requests, so a 5-minute sample catches almost no in-request frames).This PR closes that observability gap with twelve permanent method tracers plus one custom transaction attribute, registered from a declarative registry so future endpoints are one-line additions. After deploy, transaction traces and the breakdown table show named
Custom/*segments instead of one opaque block, naming the specific method responsible for the time.Evidence motivating this (last 24 h, 137,960 requests)
The dominant cost for 98.7% of traffic is in-process Ruby compute, not I/O. The rare > 5 s outliers are triple-store storms (separate, low-priority issue).
What is instrumented
New file
config/newrelic_method_tracers.rb(declarative registry, loaded fromapp.rbafterinit):GET /search pipeline
SearchHelper#process_search: whole pipeline; its self-time isolates the loop that builds read-onlyLinkedData::Models::Classobjects from Solr documentsSearchHelper#get_term_search_query: Solr query constructionSearchHelper#add_matched_fields,SearchHelper#filter_attrs_by_language: highlight mapping and language filtering (the latter runs once per returned document, ~50/request; flagged in a comment as the one removal candidate if traces get noisy)LinkedData::Models::Class.search: Solr call including Ruby-side response parsingGET /ontologies/{ontology}/classes/{cls}/tree pipeline (slowest transaction by average duration, 1.61 s; known future tuning target)
LinkedData::Models::Class#tree,#tree_sorted,#paths_to_root(defined in an ontologies_linked_data concern, traced from here without touching that repo)App-wide (every endpoint benefits)
ApplicationHelper#restricted_ontologies: loads the full ontology access list (prime suspect for /search)ApplicationHelper#reply: access control, slice/user filtering, serialization entryLinkedData::Serializer.build_response/.serialize: hypermedia link and JSON generationSearchHelper#process_searchalso records asearch_scopedtransaction attribute (the agent strips query strings fromrequest.uri), enabling NRQL comparison of ontology-scoped vs site-wide searches:Permanent, not temporary
These follow standard New Relic custom instrumentation practice. Per-call overhead is microseconds against an 870 ms transaction. With the agent disabled (development/test) they are no-ops. No logic, query, or response-format changes.
Kill switch:
NEWRELIC_CUSTOM_TRACERS=off(plus restart) skips all registration, to rule instrumentation out while debugging.Test plan
test/helpers/test_newrelic_method_tracers.rbiterates the registry and asserts every entry is wrapped by newrelic_rpm; fails if a traced method is renamed or registration silently no-ops. Registry-driven, so future entries are covered automatically.test/controllers/test_search_controller.rb: 13 tests, 147 assertions, 0 failures against the local backend stacktest/controllers/test_classes_controller.rb(exercises /tree): 20 tests, 4,194 assertions, 0 failures