-
Notifications
You must be signed in to change notification settings - Fork 24.9k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Introduce executor for concurrent search #98204
Changes from 34 commits
20e6dbe
01733dc
9229f4d
f67551f
db6e906
3b86ab1
438d76d
ad260e0
5a8ec56
9069bfe
026258c
fed2aaf
6688e92
cfcc819
d2a982b
0b2b243
103363f
b44a239
4220d6a
89faa2b
9ec1fd5
eced2c8
2844a05
5959f83
ee55047
8c77ed6
af76ab6
262996d
bb4ffca
c3d8034
151b93b
23121e3
bca4050
df0ff91
9b92a0b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
pr: 98204 | ||
summary: Introduce executor for concurrent search | ||
area: Search | ||
type: enhancement | ||
issues: [] |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -281,13 +281,6 @@ public class SearchModule { | |
Setting.Property.NodeScope | ||
); | ||
|
||
public static final Setting<Boolean> SEARCH_CONCURRENCY_ENABLED = Setting.boolSetting( | ||
"search.concurrency_enabled", | ||
true, | ||
Setting.Property.NodeScope, | ||
Setting.Property.Dynamic | ||
); | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I moved this to SearchService together with the other existing search settings (including minimum docs per slice which affects search concurrency) |
||
private final Map<String, Highlighter> highlighters; | ||
|
||
private final List<FetchSubPhase> fetchSubPhases = new ArrayList<>(); | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -130,6 +130,8 @@ | |
import java.util.Set; | ||
import java.util.concurrent.ExecutionException; | ||
import java.util.concurrent.Executor; | ||
import java.util.concurrent.ExecutorService; | ||
import java.util.concurrent.ThreadPoolExecutor; | ||
import java.util.concurrent.TimeoutException; | ||
import java.util.concurrent.atomic.AtomicBoolean; | ||
import java.util.concurrent.atomic.AtomicInteger; | ||
|
@@ -141,7 +143,6 @@ | |
import static org.elasticsearch.core.TimeValue.timeValueMillis; | ||
import static org.elasticsearch.core.TimeValue.timeValueMinutes; | ||
import static org.elasticsearch.index.seqno.SequenceNumbers.UNASSIGNED_SEQ_NO; | ||
import static org.elasticsearch.search.SearchModule.SEARCH_CONCURRENCY_ENABLED; | ||
|
||
public class SearchService extends AbstractLifecycleComponent implements IndexEventListener { | ||
private static final Logger logger = LogManager.getLogger(SearchService.class); | ||
|
@@ -211,6 +212,13 @@ public class SearchService extends AbstractLifecycleComponent implements IndexEv | |
Property.NodeScope | ||
); | ||
|
||
public static final Setting<Boolean> SEARCH_WORKER_THREADS_ENABLED = Setting.boolSetting( | ||
"search.worker_threads_enabled", | ||
true, | ||
Property.NodeScope, | ||
Property.Dynamic | ||
); | ||
|
||
public static final Setting<Integer> MAX_OPEN_SCROLL_CONTEXT = Setting.intSetting( | ||
"search.max_open_scroll_context", | ||
500, | ||
|
@@ -253,7 +261,7 @@ public class SearchService extends AbstractLifecycleComponent implements IndexEv | |
private final DfsPhase dfsPhase = new DfsPhase(); | ||
|
||
private final FetchPhase fetchPhase; | ||
private volatile boolean enableConcurrentCollection; | ||
private volatile boolean enableSearchWorkerThreads; | ||
|
||
private volatile long defaultKeepAlive; | ||
|
||
|
@@ -344,16 +352,12 @@ public SearchService( | |
clusterService.getClusterSettings() | ||
.addSettingsUpdateConsumer(ENABLE_REWRITE_AGGS_TO_FILTER_BY_FILTER, this::setEnableRewriteAggsToFilterByFilter); | ||
|
||
enableConcurrentCollection = SEARCH_CONCURRENCY_ENABLED.get(settings); | ||
clusterService.getClusterSettings().addSettingsUpdateConsumer(SEARCH_CONCURRENCY_ENABLED, this::setEnableConcurrentCollection); | ||
} | ||
|
||
private void setEnableConcurrentCollection(boolean concurrentCollection) { | ||
this.enableConcurrentCollection = concurrentCollection; | ||
enableSearchWorkerThreads = SEARCH_WORKER_THREADS_ENABLED.get(settings); | ||
clusterService.getClusterSettings().addSettingsUpdateConsumer(SEARCH_WORKER_THREADS_ENABLED, this::setEnableSearchWorkerThreads); | ||
} | ||
|
||
boolean isConcurrentCollectionEnabled() { | ||
return this.enableConcurrentCollection; | ||
private void setEnableSearchWorkerThreads(boolean enableSearchWorkerThreads) { | ||
this.enableSearchWorkerThreads = enableSearchWorkerThreads; | ||
} | ||
|
||
private static void validateKeepAlives(TimeValue defaultKeepAlive, TimeValue maxKeepAlive) { | ||
|
@@ -1039,7 +1043,7 @@ public DefaultSearchContext createSearchContext(ShardSearchRequest request, Time | |
final Engine.SearcherSupplier reader = indexShard.acquireSearcherSupplier(); | ||
final ShardSearchContextId id = new ShardSearchContextId(sessionId, idGenerator.incrementAndGet()); | ||
try (ReaderContext readerContext = new ReaderContext(id, indexService, indexShard, reader, -1L, true)) { | ||
DefaultSearchContext searchContext = createSearchContext(readerContext, request, timeout, null); | ||
DefaultSearchContext searchContext = createSearchContext(readerContext, request, timeout, ResultsType.NONE); | ||
searchContext.addReleasable(readerContext.markAsUsed(0L)); | ||
return searchContext; | ||
} | ||
|
@@ -1060,16 +1064,20 @@ private DefaultSearchContext createSearchContext( | |
reader.indexShard().shardId(), | ||
request.getClusterAlias() | ||
); | ||
ExecutorService executor = this.enableSearchWorkerThreads ? threadPool.executor(Names.SEARCH_WORKER) : null; | ||
int maximumNumberOfSlices = executor instanceof ThreadPoolExecutor tpe | ||
&& supportsParallelCollection(resultsType, request.source()) ? tpe.getMaximumPoolSize() : 1; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Whenever search workers are disabled through escape hatch, we don't set the executor. Otherwise, we do set an executor, but we really parallelize only the DFS phase. The query phase still executes sequentially, because it does not support concurrency yet, but sequential execution is performed by the worker threads. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Update: Whenever search workers are disabled through escape hatch, we don't set the executor. |
||
searchContext = new DefaultSearchContext( | ||
reader, | ||
request, | ||
shardTarget, | ||
threadPool::relativeTimeInMillis, | ||
timeout, | ||
minimumDocsPerSlice, | ||
fetchPhase, | ||
lowLevelCancellation, | ||
this.enableConcurrentCollection && concurrentSearchEnabled(resultsType, request.source()) | ||
executor, | ||
maximumNumberOfSlices, | ||
minimumDocsPerSlice | ||
); | ||
// we clone the query shard context here just for rewriting otherwise we | ||
// might end up with incorrect state since we are using now() or script services | ||
|
@@ -1089,10 +1097,16 @@ this.enableConcurrentCollection && concurrentSearchEnabled(resultsType, request. | |
return searchContext; | ||
} | ||
|
||
static boolean concurrentSearchEnabled(ResultsType resultsType, SearchSourceBuilder source) { | ||
static boolean supportsParallelCollection(ResultsType resultsType, SearchSourceBuilder source) { | ||
if (resultsType == ResultsType.DFS) { | ||
return true; // only enable concurrent collection for DFS phase for now | ||
} | ||
/* | ||
TODO uncomment this block to enable inter-segment concurrency for the query phase | ||
if (resultsType == ResultsType.QUERY) { | ||
return source == null || source.aggregations() == null || source.aggregations().supportsParallelCollection(); | ||
} | ||
*/ | ||
return false; | ||
} | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -72,6 +72,7 @@ private static void executeInSortOrder(SearchContext context, BucketCollector co | |
searcher.setProfiler(context); | ||
try { | ||
searcher.search(context.rewrittenQuery(), collector); | ||
collector.postCollection(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I am thinking that this is another exception to the offloading that we should remove. We should provide an executor to the time series index searcher, and offload its sequential execution to it. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I was thinking the same thing, when making that change. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. yep, I think though that because it's part of the query phase, it could potentially cause us to take in more heavy work than desired, say that we have many searches being offloaded and at the same time many others using this time series index searcher, we'd have many threads (potentially double compared to before adding the search workers thread pool) doing I/O and CPU bound stuff. I don't think this is a blocker for this PR but we should look into it as a follow-up. |
||
} catch (IOException e) { | ||
throw new AggregationExecutionException("Could not perform time series aggregation", e); | ||
} | ||
|
@@ -113,7 +114,6 @@ public static void execute(SearchContext context) { | |
final List<InternalAggregation> aggregations = new ArrayList<>(aggregators.length); | ||
for (Aggregator aggregator : aggregators) { | ||
try { | ||
aggregator.postCollection(); | ||
aggregations.add(aggregator.buildTopLevel()); | ||
} catch (IOException e) { | ||
throw new AggregationExecutionException("Failed to build aggregation [" + aggregator.name() + "]", e); | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have renamed the escape hatch to disable concurrency to align it with the name of the new thread pool, as it effectively affects whether the new thread pool is enabled or not.