-
Notifications
You must be signed in to change notification settings - Fork 1.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 new setting search.concurrent.max_slice to control the slice computation for concurrent segment search #8847
Changes from all commits
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,38 @@ | ||||||||
/* | ||||||||
* SPDX-License-Identifier: Apache-2.0 | ||||||||
* | ||||||||
* The OpenSearch Contributors require contributions made to | ||||||||
* this file be licensed under the Apache-2.0 license or a | ||||||||
* compatible open source license. | ||||||||
*/ | ||||||||
|
||||||||
package org.opensearch.search; | ||||||||
|
||||||||
import org.opensearch.common.settings.Setting; | ||||||||
import org.opensearch.common.settings.Settings; | ||||||||
|
||||||||
/** | ||||||||
* Keeps track of all the search related node level settings which can be accessed via static methods | ||||||||
*/ | ||||||||
public class SearchBootstrapSettings { | ||||||||
// settings to configure maximum slice created per search request using OS custom slice computation mechanism. Default lucene | ||||||||
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. Is there any question as to whether we will switch away from the static method to the dynamic method when the next release of Lucene is available? If not, I'd go ahead and create a GitHub issue to track it and link the issue in a comment in the code where appropriate. 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. @sohami to this point, why this setting is static? As far as I can tell, it is used in non-static context and could be implemented as a regular search-related setting. 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. @reta The 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. 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. thats interesting, do we usually update But nonetheless we can keep this change as is and backport to 2.x until 9.8 is officially released. Then add a follow-up commit to move it to dynamic setting as part of #8870 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 trying to understand why do you want to get this change in 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. @reta Let me try explaining :) . I do get your solution and can use that but would like to first try explaining why I am trying to merge it in main as well. As an example in this PR, we have this new class
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.
This is inevitable in any case - we will be backporting all changes related to Lucene 9.8.0 (as we did for all previous Apache Lucene versions), the argument here is: keep main clean by using the new Apache Lucene snapshots (this is why we've been always doing that, giving the ride to the feature before the release - bugs do happen), do backport as a best effort. 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 will close this PR and open a new one against 2.x. Will make the dynamic setting change as separate PR for main branch. And will create another tracking backport issue for merging the dynamic setting change to 2.x when lucene 9.8 gets released. As part of that backport we can revert the commit for static setting in 2.x and then apply the commit from main. 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. |
||||||||
// mechanism will not be used if this setting is set with value > 0 | ||||||||
public static final String CONCURRENT_SEGMENT_SEARCH_TARGET_MAX_SLICE_COUNT_KEY = "search.concurrent.max_slice"; | ||||||||
public static final int CONCURRENT_SEGMENT_SEARCH_TARGET_MAX_SLICE_COUNT_DEFAULT_VALUE = -1; | ||||||||
|
||||||||
// value <= 0 means lucene slice computation will be used | ||||||||
public static final Setting<Integer> CONCURRENT_SEGMENT_SEARCH_TARGET_MAX_SLICE_COUNT_SETTING = Setting.intSetting( | ||||||||
CONCURRENT_SEGMENT_SEARCH_TARGET_MAX_SLICE_COUNT_KEY, | ||||||||
CONCURRENT_SEGMENT_SEARCH_TARGET_MAX_SLICE_COUNT_DEFAULT_VALUE, | ||||||||
Setting.Property.NodeScope | ||||||||
); | ||||||||
Comment on lines
+24
to
+28
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 think we should use the intSetting with minValue (and maybe max value): OpenSearch/server/src/main/java/org/opensearch/common/settings/Setting.java Lines 1492 to 1494 in 0ea7210
For max value, it is naturally bounded by the number of segments, which shouldn't grow unbounded due to Lucene merges so I'm inclined to say that is not necessary. 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 don't see this as a must have since there is no true bounds we are enforcing for now. Anything < 0 is treated as to use the lucene mechanism vs custom mechanism. And if a large +ve value is set that will be tuned down to the segment count in the slice computer logic. 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. Thinking about this some more, I agree that this is not a must have from a functional perspective since the <0 case is handled in 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.
This setting is used in 2 ways. If value > 0 then use the custom slice mechanism and use the value as target max slice. If value is <=0 then use the lucene slice mechanism. So here the actual negative value or 0 is not relevant other than meaning fallback to lucene behavior. It is used for enabling/disabling the feature (which is custom slicer vs lucene slicer). I will prefer min/max range for settings where there is clear range defined. Here <=0 is used a boolean flag to control using one feature over other. Also didn't want to add a new setting to control that, as I would expect based on the test we will default to one behavior. |
||||||||
private static Settings settings; | ||||||||
|
||||||||
public static void initialize(Settings openSearchSettings) { | ||||||||
settings = openSearchSettings; | ||||||||
} | ||||||||
|
||||||||
public static int getValueAsInt(String settingName, int defaultValue) { | ||||||||
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. This is a bit weird because any setting can be passed in here even if unrelated to search. I'd probably implement this as 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.
|
||||||||
return (settings != null) ? settings.getAsInt(settingName, defaultValue) : defaultValue; | ||||||||
} | ||||||||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -32,6 +32,8 @@ | |
|
||
package org.opensearch.search.internal; | ||
|
||
import org.apache.logging.log4j.LogManager; | ||
import org.apache.logging.log4j.Logger; | ||
import org.apache.lucene.index.DirectoryReader; | ||
import org.apache.lucene.index.IndexReader; | ||
import org.apache.lucene.index.LeafReaderContext; | ||
|
@@ -66,6 +68,7 @@ | |
import org.opensearch.common.lucene.search.TopDocsAndMaxScore; | ||
import org.opensearch.common.lease.Releasable; | ||
import org.opensearch.search.DocValueFormat; | ||
import org.opensearch.search.SearchBootstrapSettings; | ||
import org.opensearch.search.SearchService; | ||
import org.opensearch.search.dfs.AggregatedDfs; | ||
import org.opensearch.search.profile.ContextualProfileBreakdown; | ||
|
@@ -93,11 +96,13 @@ | |
* @opensearch.internal | ||
*/ | ||
public class ContextIndexSearcher extends IndexSearcher implements Releasable { | ||
|
||
private static final Logger logger = LogManager.getLogger(ContextIndexSearcher.class); | ||
/** | ||
* The interval at which we check for search cancellation when we cannot use | ||
* a {@link CancellableBulkScorer}. See {@link #intersectScorerAndBitSet}. | ||
*/ | ||
private static int CHECK_CANCELLED_SCORER_INTERVAL = 1 << 11; | ||
private static final int CHECK_CANCELLED_SCORER_INTERVAL = 1 << 11; | ||
|
||
private AggregatedDfs aggregatedDfs; | ||
private QueryProfiler profiler; | ||
|
@@ -439,6 +444,20 @@ public CollectionStatistics collectionStatistics(String field) throws IOExceptio | |
return collectionStatistics; | ||
} | ||
|
||
/** | ||
* Compute the leaf slices that will be used by concurrent segment search to spread work across threads | ||
* @param leaves all the segments | ||
* @return leafSlice group to be executed by different threads | ||
*/ | ||
@Override | ||
public LeafSlice[] slices(List<LeafReaderContext> leaves) { | ||
final int target_max_slices = SearchBootstrapSettings.getValueAsInt( | ||
SearchBootstrapSettings.CONCURRENT_SEGMENT_SEARCH_TARGET_MAX_SLICE_COUNT_KEY, | ||
SearchBootstrapSettings.CONCURRENT_SEGMENT_SEARCH_TARGET_MAX_SLICE_COUNT_DEFAULT_VALUE | ||
); | ||
return slicesInternal(leaves, target_max_slices); | ||
} | ||
|
||
public DirectoryReader getDirectoryReader() { | ||
final IndexReader reader = getIndexReader(); | ||
assert reader instanceof DirectoryReader : "expected an instance of DirectoryReader, got " + reader.getClass(); | ||
|
@@ -518,4 +537,19 @@ private boolean shouldReverseLeafReaderContexts() { | |
} | ||
return false; | ||
} | ||
|
||
// package-private for testing | ||
LeafSlice[] slicesInternal(List<LeafReaderContext> leaves, int target_max_slices) { | ||
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.
|
||
LeafSlice[] leafSlices; | ||
if (target_max_slices <= 0) { | ||
// use the default lucene slice calculation | ||
leafSlices = super.slices(leaves); | ||
logger.debug("Slice count using lucene default [{}]", leafSlices.length); | ||
} else { | ||
// use the custom slice calculation based on target_max_slices. It will sort | ||
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. This comment looks cut off |
||
leafSlices = MaxTargetSliceSupplier.getSlices(leaves, target_max_slices); | ||
logger.debug("Slice count using max target slice supplier [{}]", leafSlices.length); | ||
} | ||
return leafSlices; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,61 @@ | ||
/* | ||
* SPDX-License-Identifier: Apache-2.0 | ||
* | ||
* The OpenSearch Contributors require contributions made to | ||
* this file be licensed under the Apache-2.0 license or a | ||
* compatible open source license. | ||
*/ | ||
|
||
package org.opensearch.search.internal; | ||
|
||
import org.apache.lucene.index.LeafReaderContext; | ||
import org.apache.lucene.search.IndexSearcher; | ||
|
||
import java.util.ArrayList; | ||
import java.util.Collections; | ||
import java.util.Comparator; | ||
import java.util.List; | ||
|
||
/** | ||
* Supplier to compute leaf slices based on passed in leaves and max target slice count to limit the number of computed slices. It sorts | ||
* all the leaves based on document count and then assign each leaf in round-robin fashion to the target slice count slices. Based on | ||
* experiment results as shared in <a href=https://github.com/opensearch-project/OpenSearch/issues/7358>issue-7358</a> | ||
* we can see this mechanism helps to achieve better tail/median latency over default lucene slice computation. | ||
*/ | ||
public class MaxTargetSliceSupplier { | ||
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. Add |
||
|
||
public static IndexSearcher.LeafSlice[] getSlices(List<LeafReaderContext> leaves, int target_max_slice) { | ||
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.
|
||
if (target_max_slice <= 0) { | ||
throw new IllegalArgumentException("MaxTargetSliceSupplier called with unexpected slice count of " + target_max_slice); | ||
} | ||
|
||
// slice count should not exceed the segment count | ||
int target_slice_count = Math.min(target_max_slice, leaves.size()); | ||
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.
|
||
|
||
// Make a copy so we can sort: | ||
List<LeafReaderContext> sortedLeaves = new ArrayList<>(leaves); | ||
|
||
// Sort by maxDoc, descending: | ||
sortedLeaves.sort(Collections.reverseOrder(Comparator.comparingInt(l -> l.reader().maxDoc()))); | ||
|
||
final List<List<LeafReaderContext>> groupedLeaves = new ArrayList<>(); | ||
for (int i = 0; i < target_slice_count; ++i) { | ||
groupedLeaves.add(new ArrayList<>()); | ||
} | ||
// distribute the slices in round-robin fashion | ||
List<LeafReaderContext> group; | ||
for (int idx = 0; idx < sortedLeaves.size(); ++idx) { | ||
int currentGroup = idx % target_slice_count; | ||
group = groupedLeaves.get(currentGroup); | ||
group.add(sortedLeaves.get(idx)); | ||
Comment on lines
+49
to
+50
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. The |
||
} | ||
|
||
IndexSearcher.LeafSlice[] slices = new IndexSearcher.LeafSlice[target_slice_count]; | ||
int upto = 0; | ||
for (List<LeafReaderContext> currentLeaf : groupedLeaves) { | ||
slices[upto] = new IndexSearcher.LeafSlice(currentLeaf); | ||
++upto; | ||
} | ||
return slices; | ||
Comment on lines
+53
to
+59
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. Can you replace this with the following?
|
||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
/* | ||
* SPDX-License-Identifier: Apache-2.0 | ||
* | ||
* The OpenSearch Contributors require contributions made to | ||
* this file be licensed under the Apache-2.0 license or a | ||
* compatible open source license. | ||
*/ | ||
|
||
package org.opensearch.search.internal; | ||
|
||
import org.apache.lucene.analysis.standard.StandardAnalyzer; | ||
import org.apache.lucene.document.Document; | ||
import org.apache.lucene.document.Field; | ||
import org.apache.lucene.document.StringField; | ||
import org.apache.lucene.index.DirectoryReader; | ||
import org.apache.lucene.index.IndexWriter; | ||
import org.apache.lucene.index.IndexWriterConfig; | ||
import org.apache.lucene.index.LeafReaderContext; | ||
import org.apache.lucene.index.NoMergePolicy; | ||
import org.apache.lucene.store.Directory; | ||
|
||
import java.util.List; | ||
|
||
import static org.apache.lucene.tests.util.LuceneTestCase.newDirectory; | ||
|
||
public class IndexReaderUtils { | ||
|
||
/** | ||
* Utility to create leafCount number of {@link LeafReaderContext} | ||
* @param leafCount count of leaves to create | ||
* @return created leaves | ||
*/ | ||
public static List<LeafReaderContext> getLeaves(int leafCount) throws Exception { | ||
final Directory directory = newDirectory(); | ||
IndexWriter iw = new IndexWriter(directory, new IndexWriterConfig(new StandardAnalyzer()).setMergePolicy(NoMergePolicy.INSTANCE)); | ||
for (int i = 0; i < leafCount; ++i) { | ||
Document document = new Document(); | ||
final String fieldValue = "value" + i; | ||
document.add(new StringField("field1", fieldValue, Field.Store.NO)); | ||
document.add(new StringField("field2", fieldValue, Field.Store.NO)); | ||
iw.addDocument(document); | ||
iw.commit(); | ||
} | ||
iw.close(); | ||
DirectoryReader directoryReader = DirectoryReader.open(directory); | ||
List<LeafReaderContext> leaves = directoryReader.leaves(); | ||
directoryReader.close(); | ||
directory.close(); | ||
return leaves; | ||
} | ||
} |
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.
Please add
@opensearch.internal