Skip to content

Commit bc76e71

Browse files
Enable concurrent_segment_search auto mode by default (#17978) (#18077)
* Enable concurrent_segment_search auto mode by default * Make Default Slice count to 1 for Non-Concurrent Path * Add tolerance to matrix_stats agg correlation value assertion The correlation metric could be different for different document distribution across shards, or slices. slice1(doc1,doc2), slice2(doc3,doc4,doc5) could give different correlation from slice1(doc1,doc2,doc3), slice2(doc4,doc5) The tolerance followed here is 0.000000000000001 --------- Signed-off-by: Vikasht34 <[email protected]> Signed-off-by: bowenlan-amzn <[email protected]> Co-authored-by: Vikasht34 <[email protected]>
1 parent 26d7801 commit bc76e71

File tree

11 files changed

+112
-50
lines changed

11 files changed

+112
-50
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
1010

1111
### Changed
1212
- Change the default max header size from 8KB to 16KB. ([#18024](https://github.com/opensearch-project/OpenSearch/pull/18024))
13+
- Enable concurrent_segment_search auto mode by default[#17978](https://github.com/opensearch-project/OpenSearch/pull/17978)
1314

1415
### Dependencies
1516

modules/aggs-matrix-stats/src/yamlRestTest/resources/rest-api-spec/test/stats/30_single_value_field.yml

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,8 @@ setup:
145145
- match: {hits.total: 15}
146146
- match: {aggregations.mfs.doc_count: 14}
147147
- match: {aggregations.mfs.fields.0.count: 14}
148-
- match: {aggregations.mfs.fields.2.correlation.val2: 0.9569513137793205}
148+
- gte: {aggregations.mfs.fields.2.correlation.val2: 0.956951313779319}
149+
- lte: {aggregations.mfs.fields.2.correlation.val2: 0.956951313779321}
149150

150151
---
151152
"Partially unmapped with missing default":
@@ -159,7 +160,8 @@ setup:
159160
- match: {hits.total: 15}
160161
- match: {aggregations.mfs.doc_count: 15}
161162
- match: {aggregations.mfs.fields.0.count: 15}
162-
- match: {aggregations.mfs.fields.2.correlation.val2: 0.9567970467908384}
163+
- gte: {aggregations.mfs.fields.2.correlation.val2: 0.956797046790837}
164+
- lte: {aggregations.mfs.fields.2.correlation.val2: 0.956797046790839}
163165

164166
---
165167
"With script":

modules/aggs-matrix-stats/src/yamlRestTest/resources/rest-api-spec/test/stats/40_multi_value_field.yml

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,8 @@ setup:
132132
- match: {hits.total: 15}
133133
- match: {aggregations.mfs.doc_count: 14}
134134
- match: {aggregations.mfs.fields.0.count: 14}
135-
- match: {aggregations.mfs.fields.0.correlation.val1: 0.06838646533369998}
135+
- gte: {aggregations.mfs.fields.0.correlation.val1: 0.068386465333698}
136+
- lte: {aggregations.mfs.fields.0.correlation.val1: 0.068386465333701}
136137

137138
---
138139
"Multi value field Min":
@@ -146,7 +147,8 @@ setup:
146147
- match: {hits.total: 15}
147148
- match: {aggregations.mfs.doc_count: 14}
148149
- match: {aggregations.mfs.fields.0.count: 14}
149-
- match: {aggregations.mfs.fields.0.correlation.val1: -0.09777682707831963}
150+
- gte: {aggregations.mfs.fields.0.correlation.val1: -0.097776827078320}
151+
- lte: {aggregations.mfs.fields.0.correlation.val1: -0.097776827078318}
150152

151153
---
152154
"Partially unmapped":
@@ -160,7 +162,8 @@ setup:
160162
- match: {hits.total: 15}
161163
- match: {aggregations.mfs.doc_count: 13}
162164
- match: {aggregations.mfs.fields.0.count: 13}
163-
- match: {aggregations.mfs.fields.0.correlation.val1: -0.044997535185684244}
165+
- gte: {aggregations.mfs.fields.0.correlation.val1: -0.044997535185685}
166+
- lte: {aggregations.mfs.fields.0.correlation.val1: -0.044997535185683}
164167

165168
---
166169
"Partially unmapped with missing defaults":
@@ -174,7 +177,8 @@ setup:
174177
- match: {hits.total: 15}
175178
- match: {aggregations.mfs.doc_count: 15}
176179
- match: {aggregations.mfs.fields.0.count: 15}
177-
- match: {aggregations.mfs.fields.0.correlation.val2: 0.04028024709708195}
180+
- gte: {aggregations.mfs.fields.0.correlation.val2: 0.040280247097080}
181+
- lte: {aggregations.mfs.fields.0.correlation.val2: 0.040280247097082}
178182

179183
---
180184
"With script":

server/src/internalClusterTest/java/org/opensearch/indices/memory/breaker/CircuitBreakerServiceIT.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@ public static Collection<Object[]> parameters() {
110110
protected Settings nodeSettings(int nodeOrdinal) {
111111
return Settings.builder()
112112
.put(super.nodeSettings(nodeOrdinal))
113-
.put(SearchService.CONCURRENT_SEGMENT_SEARCH_TARGET_MAX_SLICE_COUNT_KEY, randomIntBetween(1, 2))
113+
.put(SearchService.CONCURRENT_SEGMENT_SEARCH_MAX_SLICE_COUNT_KEY, randomIntBetween(1, 2))
114114
.build();
115115
}
116116

server/src/internalClusterTest/java/org/opensearch/search/stats/ConcurrentSearchStatsIT.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ protected Settings nodeSettings(int nodeOrdinal) {
5959
.put(super.nodeSettings(nodeOrdinal))
6060
.put(IndicesService.INDICES_CACHE_CLEAN_INTERVAL_SETTING.getKey(), "1ms")
6161
.put(IndicesQueryCache.INDICES_QUERIES_CACHE_ALL_SEGMENTS_SETTING.getKey(), true)
62-
.put(SearchService.CONCURRENT_SEGMENT_SEARCH_TARGET_MAX_SLICE_COUNT_KEY, SEGMENT_SLICE_COUNT)
62+
.put(SearchService.CONCURRENT_SEGMENT_SEARCH_MAX_SLICE_COUNT_KEY, SEGMENT_SLICE_COUNT)
6363
.put(SearchService.CLUSTER_CONCURRENT_SEGMENT_SEARCH_SETTING.getKey(), true)
6464
.build();
6565
}

server/src/main/java/org/opensearch/index/IndexSettings.java

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -78,10 +78,11 @@
7878
import static org.opensearch.index.mapper.MapperService.INDEX_MAPPING_NESTED_FIELDS_LIMIT_SETTING;
7979
import static org.opensearch.index.mapper.MapperService.INDEX_MAPPING_TOTAL_FIELDS_LIMIT_SETTING;
8080
import static org.opensearch.index.store.remote.directory.RemoteSnapshotDirectory.SEARCHABLE_SNAPSHOT_EXTENDED_COMPATIBILITY_MINIMUM_VERSION;
81+
import static org.opensearch.search.SearchService.CONCURRENT_SEGMENT_SEARCH_DEFAULT_SLICE_COUNT_VALUE;
82+
import static org.opensearch.search.SearchService.CONCURRENT_SEGMENT_SEARCH_MIN_SLICE_COUNT_VALUE;
8183
import static org.opensearch.search.SearchService.CONCURRENT_SEGMENT_SEARCH_MODE_ALL;
8284
import static org.opensearch.search.SearchService.CONCURRENT_SEGMENT_SEARCH_MODE_AUTO;
8385
import static org.opensearch.search.SearchService.CONCURRENT_SEGMENT_SEARCH_MODE_NONE;
84-
import static org.opensearch.search.SearchService.CONCURRENT_SEGMENT_SEARCH_TARGET_MAX_SLICE_COUNT_DEFAULT_VALUE;
8586

8687
/**
8788
* This class encapsulates all index level settings and handles settings updates.
@@ -726,8 +727,8 @@ public static IndexMergePolicy fromString(String text) {
726727

727728
public static final Setting<Integer> INDEX_CONCURRENT_SEGMENT_SEARCH_MAX_SLICE_COUNT = Setting.intSetting(
728729
"index.search.concurrent.max_slice_count",
729-
CONCURRENT_SEGMENT_SEARCH_TARGET_MAX_SLICE_COUNT_DEFAULT_VALUE,
730-
CONCURRENT_SEGMENT_SEARCH_TARGET_MAX_SLICE_COUNT_DEFAULT_VALUE,
730+
CONCURRENT_SEGMENT_SEARCH_DEFAULT_SLICE_COUNT_VALUE,
731+
CONCURRENT_SEGMENT_SEARCH_MIN_SLICE_COUNT_VALUE,
731732
Property.Dynamic,
732733
Property.IndexScope
733734
);

server/src/main/java/org/opensearch/search/DefaultSearchContext.java

Lines changed: 62 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,8 @@
5050
import org.opensearch.common.SetOnce;
5151
import org.opensearch.common.lease.Releasables;
5252
import org.opensearch.common.lucene.search.Queries;
53+
import org.opensearch.common.settings.ClusterSettings;
54+
import org.opensearch.common.settings.Settings;
5355
import org.opensearch.common.unit.TimeValue;
5456
import org.opensearch.common.util.BigArrays;
5557
import org.opensearch.core.concurrency.OpenSearchRejectedExecutionException;
@@ -1056,46 +1058,77 @@ public BucketCollectorProcessor bucketCollectorProcessor() {
10561058
}
10571059

10581060
/**
1059-
* Evaluate the concurrentSearchMode based on cluster and index settings if concurrent segment search
1060-
* should be used for this request context
1061-
* If the cluster.search.concurrent_segment_search.mode setting
1062-
* is not explicitly set, the evaluation falls back to the
1063-
* cluster.search.concurrent_segment_search.enabled boolean setting
1064-
* which will evaluate to true or false. This is then evaluated to "all" or "none" respectively
1065-
* @return one of "none", "auto", "all"
1061+
* Determines the appropriate concurrent segment search mode for the current search request.
1062+
* <p>
1063+
* This method evaluates both index-level and cluster-level settings to decide whether
1064+
* concurrent segment search should be enabled. The resolution logic is as follows:
1065+
* <ol>
1066+
* <li>If the request targets a system index or uses search throttling, concurrent segment search is disabled.</li>
1067+
* <li>If a legacy boolean setting is present (cluster or index level), it is honored:
1068+
* <ul>
1069+
* <li><code>true</code> → enables concurrent segment search ("all")</li>
1070+
* <li><code>false</code> → disables it ("none")</li>
1071+
* </ul>
1072+
* </li>
1073+
* <li>Otherwise, the modern string-based setting is used. Allowed values are: "none", "auto", or "all".</li>
1074+
* </ol>
1075+
*
1076+
* @param concurrentSearchExecutor the executor used for concurrent segment search; if null, disables the feature
1077+
* @return the resolved concurrent segment search mode: "none", "auto", or "all"
10661078
*/
10671079
private String evaluateConcurrentSearchMode(Executor concurrentSearchExecutor) {
1068-
// Do not use concurrent segment search for system indices or throttled requests. See:
1069-
// https://github.com/opensearch-project/OpenSearch/issues/12951
1070-
if (indexShard.isSystem() || indexShard.indexSettings().isSearchThrottled()) {
1080+
// Skip concurrent search for system indices, throttled requests, or if dependencies are missing
1081+
if (indexShard.isSystem()
1082+
|| indexShard.indexSettings().isSearchThrottled()
1083+
|| clusterService == null
1084+
|| concurrentSearchExecutor == null) {
10711085
return CONCURRENT_SEGMENT_SEARCH_MODE_NONE;
10721086
}
1073-
if ((clusterService != null) && concurrentSearchExecutor != null) {
1074-
String concurrentSearchMode = indexService.getIndexSettings()
1075-
.getSettings()
1076-
.get(
1077-
IndexSettings.INDEX_CONCURRENT_SEGMENT_SEARCH_MODE.getKey(),
1078-
clusterService.getClusterSettings().getOrNull(CLUSTER_CONCURRENT_SEGMENT_SEARCH_MODE)
1079-
);
1080-
if (concurrentSearchMode != null) {
1081-
return concurrentSearchMode;
1082-
}
10831087

1084-
// mode setting not set, fallback to concurrent_segment_search.enabled setting
1085-
return indexService.getIndexSettings()
1086-
.getSettings()
1087-
.getAsBoolean(
1088-
IndexSettings.INDEX_CONCURRENT_SEGMENT_SEARCH_SETTING.getKey(),
1089-
clusterService.getClusterSettings().get(CLUSTER_CONCURRENT_SEGMENT_SEARCH_SETTING)
1090-
) ? CONCURRENT_SEGMENT_SEARCH_MODE_ALL : CONCURRENT_SEGMENT_SEARCH_MODE_NONE;
1088+
Settings indexSettings = indexService.getIndexSettings().getSettings();
1089+
ClusterSettings clusterSettings = clusterService.getClusterSettings();
1090+
final String newConcurrentMode = indexSettings.get(
1091+
IndexSettings.INDEX_CONCURRENT_SEGMENT_SEARCH_MODE.getKey(),
1092+
clusterSettings.getOrNull(CLUSTER_CONCURRENT_SEGMENT_SEARCH_MODE)
1093+
);
1094+
1095+
// Step 1: New concurrent mode is explicitly set → use it
1096+
if (newConcurrentMode != null) return newConcurrentMode;
1097+
1098+
final Boolean legacySetting = indexSettings.getAsBoolean(
1099+
IndexSettings.INDEX_CONCURRENT_SEGMENT_SEARCH_SETTING.getKey(),
1100+
clusterSettings.getOrNull(CLUSTER_CONCURRENT_SEGMENT_SEARCH_SETTING)
1101+
);
1102+
1103+
// Step 2: If new concurrent setting is not explicitly set
1104+
// then check if Legacy boolean setting is explicitly set → use it
1105+
if (legacySetting != null) {
1106+
return Boolean.TRUE.equals(legacySetting) ? CONCURRENT_SEGMENT_SEARCH_MODE_ALL : CONCURRENT_SEGMENT_SEARCH_MODE_NONE;
10911107
}
1092-
return CONCURRENT_SEGMENT_SEARCH_MODE_NONE;
1108+
1109+
// Step 3: Neither explicitly set → use default concurrent mode
1110+
return indexSettings.get(
1111+
IndexSettings.INDEX_CONCURRENT_SEGMENT_SEARCH_MODE.getKey(),
1112+
clusterSettings.get(CLUSTER_CONCURRENT_SEGMENT_SEARCH_MODE)
1113+
);
10931114
}
10941115

1116+
/**
1117+
* Returns the target maximum slice count to use for concurrent segment search.
1118+
*
1119+
* If concurrent segment search is disabled (either due to system index, throttled search,
1120+
* missing executor, or explicitly disabled settings), then we return a slice count of 1.
1121+
* This effectively disables concurrent slicing and ensures that the search is performed
1122+
* in a single-threaded manner.
1123+
*
1124+
* Otherwise, fetch the configured slice count from index or cluster-level settings.
1125+
*
1126+
* @return number of slices to use for concurrent segment search; returns 1 if concurrent search is disabled.
1127+
*/
10951128
@Override
10961129
public int getTargetMaxSliceCount() {
10971130
if (shouldUseConcurrentSearch() == false) {
1098-
throw new IllegalStateException("Target slice count should not be used when concurrent search is disabled");
1131+
return 1; // Disable slicing: run search in a single thread when concurrent search is off
10991132
}
11001133

11011134
return indexService.getIndexSettings()

server/src/main/java/org/opensearch/search/SearchService.java

Lines changed: 28 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -279,7 +279,7 @@ public class SearchService extends AbstractLifecycleComponent implements IndexEv
279279

280280
public static final Setting<String> CLUSTER_CONCURRENT_SEGMENT_SEARCH_MODE = Setting.simpleString(
281281
"search.concurrent_segment_search.mode",
282-
CONCURRENT_SEGMENT_SEARCH_MODE_NONE,
282+
CONCURRENT_SEGMENT_SEARCH_MODE_AUTO,
283283
value -> {
284284
switch (value) {
285285
case CONCURRENT_SEGMENT_SEARCH_MODE_ALL:
@@ -297,18 +297,18 @@ public class SearchService extends AbstractLifecycleComponent implements IndexEv
297297

298298
// settings to configure maximum slice created per search request using OS custom slice computation mechanism. Default lucene
299299
// mechanism will not be used if this setting is set with value > 0
300-
public static final String CONCURRENT_SEGMENT_SEARCH_TARGET_MAX_SLICE_COUNT_KEY = "search.concurrent.max_slice_count";
301-
public static final int CONCURRENT_SEGMENT_SEARCH_TARGET_MAX_SLICE_COUNT_DEFAULT_VALUE = 0;
300+
public static final String CONCURRENT_SEGMENT_SEARCH_MAX_SLICE_COUNT_KEY = "search.concurrent.max_slice_count";
301+
public static final int CONCURRENT_SEGMENT_SEARCH_DEFAULT_SLICE_COUNT_VALUE = computeDefaultSliceCount();
302+
public static final int CONCURRENT_SEGMENT_SEARCH_MIN_SLICE_COUNT_VALUE = 0;
302303

303304
// value == 0 means lucene slice computation will be used
304305
public static final Setting<Integer> CONCURRENT_SEGMENT_SEARCH_TARGET_MAX_SLICE_COUNT_SETTING = Setting.intSetting(
305-
CONCURRENT_SEGMENT_SEARCH_TARGET_MAX_SLICE_COUNT_KEY,
306-
CONCURRENT_SEGMENT_SEARCH_TARGET_MAX_SLICE_COUNT_DEFAULT_VALUE,
307-
CONCURRENT_SEGMENT_SEARCH_TARGET_MAX_SLICE_COUNT_DEFAULT_VALUE,
306+
CONCURRENT_SEGMENT_SEARCH_MAX_SLICE_COUNT_KEY,
307+
CONCURRENT_SEGMENT_SEARCH_DEFAULT_SLICE_COUNT_VALUE,
308+
CONCURRENT_SEGMENT_SEARCH_MIN_SLICE_COUNT_VALUE,
308309
Property.Dynamic,
309310
Property.NodeScope
310311
);
311-
312312
// value 0 means rewrite filters optimization in aggregations will be disabled
313313
@ExperimentalApi
314314
public static final Setting<Integer> MAX_AGGREGATION_REWRITE_FILTERS = Setting.intSetting(
@@ -1871,6 +1871,27 @@ public MinAndMax<?> estimatedMinAndMax() {
18711871
}
18721872
}
18731873

1874+
/**
1875+
* Computes the default maximum number of slices for concurrent segment search.
1876+
* <p>
1877+
* This value is dynamically calculated as:
1878+
* <pre>
1879+
* min(availableProcessors / 2, 4)
1880+
* </pre>
1881+
* This ensures that:
1882+
* <ul>
1883+
* <li>On small machines, it avoids over-threading.</li>
1884+
* <li>On larger machines, it caps the concurrency to a reasonable level (4 slices).</li>
1885+
* </ul>
1886+
* This default is used when the user does not explicitly set the
1887+
* {@code search.concurrent.max_slice_count} cluster setting.
1888+
*
1889+
* @return the computed default slice count
1890+
*/
1891+
private static int computeDefaultSliceCount() {
1892+
return Math.max(1, Math.min(Runtime.getRuntime().availableProcessors() / 2, 4));
1893+
}
1894+
18741895
/**
18751896
* This helper class ensures we only execute either the success or the failure path for {@link SearchOperationListener}.
18761897
* This is crucial for some implementations like {@link org.opensearch.index.search.stats.ShardSearchStats}.

server/src/test/java/org/opensearch/search/internal/ContextIndexSearcherTests.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -341,7 +341,7 @@ public void testSlicesInternal() throws Exception {
341341
// Case 1: Verify the slice count when lucene default slice computation is used
342342
IndexSearcher.LeafSlice[] slices = searcher.slicesInternal(
343343
leaves,
344-
SearchService.CONCURRENT_SEGMENT_SEARCH_TARGET_MAX_SLICE_COUNT_DEFAULT_VALUE
344+
SearchService.CONCURRENT_SEGMENT_SEARCH_MIN_SLICE_COUNT_VALUE
345345
);
346346
int expectedSliceCount = 2;
347347
// 2 slices will be created since max segment per slice of 5 will be reached

test/framework/src/main/java/org/opensearch/test/OpenSearchIntegTestCase.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1958,7 +1958,7 @@ protected Settings nodeSettings(int nodeOrdinal) {
19581958
.putList(DISCOVERY_SEED_PROVIDERS_SETTING.getKey(), "file")
19591959
// By default, for tests we will put the target slice count of 2. This will increase the probability of having multiple slices
19601960
// when tests are run with concurrent segment search enabled
1961-
.put(SearchService.CONCURRENT_SEGMENT_SEARCH_TARGET_MAX_SLICE_COUNT_KEY, 2)
1961+
.put(SearchService.CONCURRENT_SEGMENT_SEARCH_MAX_SLICE_COUNT_KEY, 2)
19621962
.put(featureFlagSettings());
19631963

19641964
// Enable tracer only when Telemetry Setting is enabled

0 commit comments

Comments
 (0)