Repository navigation
SOLR-11994: Add SearchRequestListener hook to SearchHandler - #5238
sarkaramrit1993 wants to merge 13 commits into
Conversation
|
On a |
|
Hi Amrit, 🤖 AI text below 🤖 (reviewed by Nick Shanin) I found one remaining case where a successful client search produces no listener event: a v2-only handler, or an embedded I added three regression tests and a fix that preserves the originally selected handler at dispatch, including through delegation. This keeps ping, pathless internal searches, shard requests and nested subqueries excluded. The follow-up diff is one commit on your current head. At To take the tested changes as a fast-forward: git switch solr-11994-hook
git fetch https://github.com/nick-boss-tech/solr.git solr-11994-pr5238-submit
git merge --ff-only ede5646e2b2281bf4fc1483c2867f0f047088527 |
|
Thanks @nick-boss-tech, the v2-only and embedded I fixed it a bit differently. Instead of setting the handler in It also covers a case the path lookup missed: a package update swapping the handler instance while a search is still running. I wrote two smaller regression tests (v2-only handler, embedded |
|
Thanks, it looks good now. |
| @@ -0,0 +1,8 @@ | |||
| title: Add a SearchRequestListener hook so modules can observe each completed search a client sends, on the node that coordinated it. | |||
There was a problem hiding this comment.
Have you seen my UBI draft pr that also wants to see search query and its results? Could it hook in here?
There was a problem hiding this comment.
Partly, I think. The recording side of UBIComponent in #2452 (the query plus the doc ids it returned, written through the logging stream) could sit on this listener, since it gets rb and rsp once per client search on the coordinator, after every stage, so the merged results are already there. The ubi=true switch, the per-handler setup and the ubi section with query_id belong in a SearchComponent, because the listener is node-wide and isn't configured per handler. Happy to try moving the recording side onto it once this lands, so neither PR waits on the other.
There was a problem hiding this comment.
cool... yeah, at the time i did UBIComponent i was still learning (heck, still am!) how to do solr components, and a big thing I never quite grokked was how to make sure the storing of data doesn't impact the querying side of htings. I suspect that is osmething you are solving here!
There was a problem hiding this comment.
Not yet. The listener runs on the request thread today, so storing data from it would still slow the query. I'm moving listeners onto a bounded background queue so recording can't hold up a search, details in the PR comment above.
| Throwable failure, | ||
| boolean partialResults) { | ||
|
|
||
| static SearchRequestInfo from(SolrQueryRequest req, SolrQueryResponse rsp) { |
There was a problem hiding this comment.
How could a slow ingest be modeled?
There was a problem hiding this comment.
Like don't we want our architecture/approach cover both?
There was a problem hiding this comment.
I kept this one search-only on purpose. Ingest already has a per-request hook: the update processor chain. LogUpdateProcessorFactory uses it to log updates over slowUpdateThresholdMillis, the same way SolrCore does for slowQueryThresholdMillis, and anyone can add their own processor to a chain today. Search has no equivalent point that sees the whole request once it's done, which is the gap this fills. The fields here (hits, shards, partial results) are search-specific too. If we want a node-wide hook for updates later, it'd be a sibling with its own fields, and I can file a JIRA for it.
There was a problem hiding this comment.
makes sense... I am hoping that in th efuture... We have a way to balance query and index workloads intelligently... So you can maiximise what you get out of a node....
There was a problem hiding this comment.
Do you need custom solrcongrig?!!
There was a problem hiding this comment.
Good call, dropped both in aa03c95. The dispatch collection now uses cloud-minimal, and the test reshapes it with one Config API call: /select re-registered for v2 only (so EmbeddedSolrServer finds no v1 /select and falls back to the default handler), plus /v2-search as a v2-only handler. Both tests still fail on the old guard with expected:<1> but was:<0>. I also renamed the embedded test to testEmbeddedDefaultHandlerReported, since SolrJ strips qt and that's the path it actually takes.
| jetty.getCoreContainer().addSearchRequestListener(listener); | ||
| } | ||
|
|
||
| private static SolrClient nodeClient() { |
There was a problem hiding this comment.
Do we not have a existing helper?
There was a problem hiding this comment.
query() now uses SolrClient#query (aa03c95). I kept nodeClient() on purpose: it pins requests to node 0 so the tests can assert which node coordinated. cluster.getSolrClient() would land on a random node.
|
🤖 AI text below 🤖 (reviewed by Nick Shanin) Amrit, I exercised the preparation/recording split you described with a test-only example. A component prepares the query identity and response metadata; a listener copies query/result data for a recording destination. The only added file is The nine integration tests cover local and distributed result IDs matching the response, generated and supplied query IDs, one coordinator recording, disabled and unconfigured handlers, destination exceptions, failed/partial searches, and a copied payload used after the embedded request closes. The result tests use ordinary searches with At On Eric's storage concern: the callback still runs synchronously on the request thread, so a slow destination can delay a query. These tests establish exception isolation and the copied-payload contract. PR 2452 integration, persistent sinks, core reload/unload, asynchronous lifetime, redaction and production overhead remain follow-up work. If useful, you can take the tested example as a fast-forward from your current head: git switch solr-11994-hook
git fetch https://github.com/nick-boss-tech/solr.git solr-11994-pr5238-ubi-submit
git merge --ff-only c2c387064a83def34cece4163eccded28318caeb |
|
Heads-up on a change of approach here. Right now What's changing:
Things I'm working through as part of it:
I'll push this as new commits here, and I'm moving the PR back to draft until then. |
Co-authored-by: Nick Shanin <nick.boss.us@gmail.com>
The listener guard looked PATH up in the v1 handler registry, which misses v2-only handlers and EmbeddedSolrServer's qt fallback. RequestHandlerBase now records the first handler that runs for a request, and SearchHandler checks that instead. v2-only and qt fallback cases found by Nick Shanin.
Drop the custom configset. The dispatch collection now uses cloud-minimal and the test registers /select for v2 only plus a v2-only handler through the Config API. The embedded test is renamed to match what it covers: SolrJ strips qt, so it resolves through the default handler.
SearchHandler now marks the request, and SolrCore.postDecorateResponse reports it once the header has its status and QTime. Listeners get the exact header QTime, and a request that fails the echoParams check is no longer reported as a success.
SearchRequestInfo now holds only copied values, with the failure as a type and a capped message. Listeners copy anything else they need from req, rsp or rb in capture(), which runs on the request thread; a null return skips the search and a failing capture is logged.
The request thread now only offers the snapshot to a bounded queue, and one daemon thread per CoreContainer calls the listeners. A full queue drops the search instead of blocking, counts it in solr.search.listener.lost and logs a warning at most once a minute. The queue size is set with solr.search.listener.queue.size. Shutdown discards queued searches and counts them too.
A listener that restores the interrupt flag made the next take() throw, and the worker exited while the dispatcher was still open. The worker now exits only once closed, and an event cut short by close is counted as lost.
ffacd3b to
92a4aa0
Compare
|
Batching is in 92a4aa0. The listener thread now drains the queue in batches ( JMH SampleTime, single node, 4 threads,
Before batching, a no-op listener added about 5 µs at p50. After batching, the gap is smaller than the fork-to-fork noise on this laptop (a few µs between forks of the same config), so there's no measurable overhead. These are closed-loop runs, so I'm not reading anything into p99. |
…duplicate adds A StackOverflowError from a listener, or a listener whose equals throws, ended the delivery thread for good. Catch everything per listener call instead; an OutOfMemoryError already crashes the JVM under bin/solr. Adding the same listener twice no longer delivers each search to it twice.
SOLR-11994
Description
First step of SOLR-18571 (Query Insights). Adds a node-level hook notified once per client search, on the node that coordinated it, so a module can track slow queries without parsing logs. Nothing changes unless a listener is registered.
Solution
SearchRequestListener, registered throughCoreContainer.addSearchRequestListener.captureruns on the request thread once the header has its QTime, and returns what the listener needs, or null to skip.onRequestEndruns later on one daemon thread per node, so slow listener work doesn't add to latency or QTime.SearchRequestInfoholds scalars only.qTimeMsequals the header QTime.solr.search.listener.queue.size, default 1000). When it's full the search is dropped, never blocked, and counted insolr.search.listener.lost./get,/export, ping, warming,[subquery].Tests
SearchRequestListenerTest(2-shard cluster) andSearchRequestDispatcherTest(full queue, non-blocking offer, listener errors and interrupts, shutdown).