fix(rank-fusion): stop waiting without limit for a searcher that does not answer - #3453
Merged
Merged
Conversation
… not answer RankFusionProcessor#searchWithMultipleSearchers collected every searcher's result with Future#get() and no timeout. With semantic search enabled, the semantic chunk searcher embeds the query before it searches, so an embedding provider that accepts the connection and never answers held every search for as long as the provider's client kept waiting and retrying, and the search then returned the keyword results with nothing to say that part of it was missing. The main searcher was collected through the same pool, so under load it could also queue for a thread behind searchers that were stuck that way. The searchers other than the main one are now waited for up to rank.fusion.timeout milliseconds (default 10000, the same as query.timeout), counted from their submission. One that has not answered by then is cancelled and left out of that search with a WARN that names it and the setting, and the response is flagged partial and timed out, so the search screen shows labels.process_time_is_exceeded and the search APIs return partial and timed_out. The main searcher now runs on the calling thread: the timeout does not apply to it, since the search engine's own timeouts do, and it no longer needs a pool thread. 0 or less keeps the previous unbounded wait. Only the path where Fess fuses the results itself changes. With rank.fusion.engine.enabled=true the sub-queries are still built on the calling thread before the single fused request is sent.
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.
Problem
RankFusionProcessor#searchWithMultipleSearcherscollects every searcher's result withFuture#get()and no timeout (RankFusionProcessor.java:404). With semantic search enabled,SemanticChunkSearcherembeds the query before it searches, so the fused search can only be as fast as the embedding provider. A provider that accepts the connection and never answers holds every search for as long as its client keeps waiting and retrying. Withfess-llm-ollamadefaults that is 3 × 60 s, and the search came back after about 186 s. The response then contained the keyword results only, and nothing in it said that part of the search was missing.The main searcher is collected through the same pool. Under load, a search can also wait for a pool thread held by semantic searches that are stuck on the provider.
Found while verifying 15.9.0; 15.8.0 has the same wait.
What changed
rank.fusion.timeout(milliseconds, default10000, the same asquery.timeout). It bounds how longsearchWithMultipleSearcherswaits for the searchers other than the main one, counted from when they are submitted.0or less keeps the previous unbounded wait.partialResultsandtimedOut, so the results page showslabels.process_time_is_exceededand/api/v2/searchreturns"partial": true, "timed_out": true. Keyword results that are missing their semantic half no longer look complete.query.timeout,index.search.timeout) do. It also no longer waits for a pool thread, so stuck semantic searches cannot delay the keyword results. Exception handling is the same as before:InvalidQueryException,ResultOffsetExceededExceptionandInvalidAccessTokenExceptionare rethrown, and anything else is logged and treated as an empty result.FessConfiggainsgetRankFusionTimeout()/getRankFusionTimeoutAsInteger()and a default-map entry, in the generated form.Compatibility and scope
rank.fusion.timeoutrestores the old behaviour.rank.fusion.engine.enabled=true,searchWithEngineFusionstill builds each sub-query, and so embeds the query, on the calling thread before it sends the single fused request. That path is not bounded by this setting yet.Verification
RankFusionProcessorTimeoutTest:test_searcherThatDoesNotAnswer_isLeftOutAfterTimeout: a searcher blocks for 5 s with the timeout at 200 ms. The main searcher's 10 hits come back well under 3 s, flagged partial and timed out. Against the previous code the search took 5003 ms.test_searcherWithinTimeout_isFused,test_mainSearcherIsNotCutByTimeoutandtest_nonPositiveTimeout_waitsForEverySearcherguard the rest of the contract. They passed before as well.mvn test: 7491 tests, 0 failures.End-to-end check: a Fess ZIP built from
main(before) and from this branch (after), run with fess-llm-ollama frommainand semantic search enabled. A local stub server's/api/embedanswered late, never answered, reset the connection, or refused it. The index was empty, so the numbers measure the wait, not the ranking./api/embedpartial+timed_outpartial+timed_outEach timed-out search logged
semantic_chunk did not return results within 10000ms (rank.fusion.timeout); it is left out of this search.A reset or refused endpoint fails inside the searcher before the timeout, and the searcher already falls back to keyword results for that, so those searches stay unflagged, as before.In the "never answers" run, cancelling the searcher interrupted the plugin's retry backoff (
Retry interrupted), which freed its pool thread at the next sleep. With the fess-llm-ollama change applied as well, the search again returned in 10.0 s, and the pool thread was released after a single 60 s read timeout.