Eric Pugh created SOLR-18348:
--------------------------------

             Summary: `buildOnCommit=true` suggester rebuild blocks commit (and 
the client request) indefinitely with no timeout
                 Key: SOLR-18348
                 URL: https://issues.apache.org/jira/browse/SOLR-18348
             Project: Solr
          Issue Type: Bug
          Components: Suggester
    Affects Versions: 9.10.1
            Reporter: Eric Pugh
            Assignee: Eric Pugh


h3. Problem

When a suggester is configured with `buildOnCommit=true`, a slow suggester 
rebuild does not just make that commit take longer in the background — it 
synchronously blocks the commit call itself, and therefore the client/update 
request waiting on it, with *no timeout*. Under sustained heavy indexing 
combined with throttled storage (e.g. an Azure disk after burst credits are 
exhausted), this manifests as outright request timeouts requiring manual 
operator intervention, rather than graceful degradation.

A customer hit this in production: ~4 million product documents being indexed 
against a suggester with an I/O-intensive `AnalyzingInfixSuggester` 
configuration, on a disk that had exhausted its burst-credit allowance. 
Indexing requests began timing out. Thread dumps showed threads stuck in:

{code}
"searcherExecutor-250-thread-3-processing-...-core_node4" ... RUNNABLE
  at org.apache.lucene.util.compress.LZ4.decompress(LZ4.java:138)
  at 
org.apache.lucene.codecs.lucene90.LZ4WithPresetDictCompressionMode$LZ4WithPresetDictDecompressor.decompress(...)
  at 
org.apache.lucene.codecs.lucene90.compressing.Lucene90CompressingStoredFieldsReader...
  at 
org.apache.lucene.search.suggest.DocumentDictionary$DocumentInputIterator.next(DocumentDictionary.java:169)
  at 
org.apache.lucene.search.suggest.analyzing.AnalyzingInfixSuggester.build(AnalyzingInfixSuggester.java:346)
{code}

h3. Root cause

* \{{SuggestComponent.SuggesterListener.newSearcher()}} calls 
\{{suggester.build(core, newSearcher)}} synchronously — see 
[SuggestComponent.java|https://github.com/apache/solr/blob/releases/solr/9.10.1/solr/core/src/java/org/apache/solr/handler/component/SuggestComponent.java#L566-L571].
* This listener is invoked as a \{{newSearcherListener}} on \{{SolrCore}}'s 
\{{searcherExecutor}}, which is explicitly a *single-threaded* executor (see 
the comment "WARNING: this code assumes a single threaded executor" in 
\{{SolrCore.java}}). Warmup, all \{{newSearcherListener}}s, and searcher 
registration all run sequentially on this one thread.
* \{{DirectUpdateHandler2.commit()}} blocks the committing thread on 
\{{waitSearcher[0].get()}} — a no-arg, *no-timeout* \{{Future.get()}} — 
whenever \{{waitSearcher=true}}, which is the default for a normal 
\{{commit=true}} request (\{{CommitUpdateCommand.waitSearcher}} defaults to 
\{{true}}).
* Net effect: an arbitrarily slow \{{buildOnCommit}} rebuild (or any other slow 
\{{newSearcherListener}}) blocks the commit-issuing thread/HTTP request 
indefinitely, bounded only by client/proxy socket timeouts.

This exact hazard is already flagged in the reference guide ("Some lookup 
implementations may take a long time to build... using buildOnCommit or 
buildOnOptimize, particularly with a high frequency of soft commits is not 
recommended") but the current behavior is worse than "slow" — it can hang a 
commit indefinitely with no internal safeguard.

Note: \{{SpellCheckComponent}} has an analogous 
\{{buildOnCommit}}/\{{SpellCheckerListener}} pattern and likely shares this 
same architectural risk; filing this ticket scoped to \{{SuggestComponent}} 
only.

h3. Reproduction

Two JUnit tests demonstrate this deterministically using a test-only dictionary 
that simulates slow I/O (sleeping a fixed time per term, so total build time is 
bounded and reproducible):

* \{{SuggestComponentBuildOnCommitBlocksCommitTest}} — with 
\{{buildOnCommit=true}}, \{{commit()}} takes as long as the (artificially slow) 
suggester build.
* \{{SuggestComponentBuildOnCommitDisabledCommitStaysFastTest}} — same slow 
dictionary, \{{buildOnCommit=false}}: \{{commit()}} stays fast, isolating the 
cause to \{{buildOnCommit}} itself.

h3. Suggested direction (open to discussion)

* Run \{{buildOnCommit}}/\{{buildOnOptimize}} suggester rebuilds off the shared 
single-threaded \{{searcherExecutor}} (e.g. a dedicated executor) so they don't 
block warmup/registration/commit for other listeners.
* And/or decouple the rebuild from the commit's \{{waitSearcher}} path 
entirely, so a slow rebuild degrades to "suggestions are stale a bit longer" 
rather than "commit hangs."
* And/or add an explicit, documented timeout/async option for 
\{{buildOnCommit}}.



--
This message was sent by Atlassian Jira
(v8.20.10#820010)

---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to