Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions docs/changelog/138105.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
pr: 138105
summary: MRT should default to `true` for CPS searches
area: CCS
type: enhancement
issues: []
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,12 @@
import org.elasticsearch.rest.action.RestToXContentListener;
import org.elasticsearch.rest.action.search.RestMultiSearchAction;
import org.elasticsearch.rest.action.search.RestSearchAction;
import org.elasticsearch.search.crossproject.CrossProjectModeDecider;
import org.elasticsearch.xcontent.XContentType;

import java.io.IOException;
import java.util.List;
import java.util.Optional;
import java.util.Set;

import static org.elasticsearch.rest.RestRequest.Method.GET;
Expand All @@ -35,11 +37,11 @@ public class RestMultiSearchTemplateAction extends BaseRestHandler {
private static final Set<String> RESPONSE_PARAMS = Set.of(RestSearchAction.TYPED_KEYS_PARAM, RestSearchAction.TOTAL_HITS_AS_INT_PARAM);

private final boolean allowExplicitIndex;
private final Settings settings;
private final CrossProjectModeDecider crossProjectModeDecider;

public RestMultiSearchTemplateAction(Settings settings) {
this.allowExplicitIndex = MULTI_ALLOW_EXPLICIT_INDEX.get(settings);
this.settings = settings;
this.crossProjectModeDecider = new CrossProjectModeDecider(settings);
}

@Override
Expand Down Expand Up @@ -67,7 +69,8 @@ public RestChannelConsumer prepareRequest(RestRequest request, NodeClient client
* Parses a {@link RestRequest} body and returns a {@link MultiSearchTemplateRequest}
*/
public MultiSearchTemplateRequest parseRequest(RestRequest restRequest, boolean allowExplicitIndex) throws IOException {
if (settings != null && settings.getAsBoolean("serverless.cross_project.enabled", false)) {
boolean crossProjectEnabled = crossProjectModeDecider.crossProjectEnabled();
if (crossProjectEnabled) {
// accept but drop project_routing param until fully supported
restRequest.param("project_routing");
}
Expand All @@ -90,7 +93,8 @@ public MultiSearchTemplateRequest parseRequest(RestRequest restRequest, boolean
throw new IllegalArgumentException("Malformed search template");
}
RestSearchAction.validateSearchRequest(restRequest, searchRequest);
}
},
Optional.of(crossProjectEnabled)
);
return multiRequest;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,12 @@
import org.elasticsearch.rest.ServerlessScope;
import org.elasticsearch.rest.action.RestToXContentListener;
import org.elasticsearch.rest.action.search.RestSearchAction;
import org.elasticsearch.search.crossproject.CrossProjectModeDecider;
import org.elasticsearch.xcontent.XContentParser;

import java.io.IOException;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.function.Predicate;

Expand All @@ -37,11 +39,11 @@ public class RestSearchTemplateAction extends BaseRestHandler {
private static final Set<String> RESPONSE_PARAMS = Set.of(TYPED_KEYS_PARAM, RestSearchAction.TOTAL_HITS_AS_INT_PARAM);

private final Predicate<NodeFeature> clusterSupportsFeature;
private final Settings settings;
private final CrossProjectModeDecider crossProjectModeDecider;

public RestSearchTemplateAction(Predicate<NodeFeature> clusterSupportsFeature, Settings settings) {
this.clusterSupportsFeature = clusterSupportsFeature;
this.settings = settings;
this.crossProjectModeDecider = new CrossProjectModeDecider(settings);
}

@Override
Expand All @@ -61,7 +63,8 @@ public String getName() {

@Override
public RestChannelConsumer prepareRequest(RestRequest request, NodeClient client) throws IOException {
if (settings != null && settings.getAsBoolean("serverless.cross_project.enabled", false)) {
boolean crossProjectEnabled = crossProjectModeDecider.crossProjectEnabled();
if (crossProjectEnabled) {
// accept but drop project_routing param until fully supported
request.param("project_routing");
}
Expand All @@ -73,7 +76,9 @@ public RestChannelConsumer prepareRequest(RestRequest request, NodeClient client
request,
null,
clusterSupportsFeature,
size -> searchRequest.source().size(size)
size -> searchRequest.source().size(size),
null,
Optional.of(crossProjectEnabled)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It worries me that the only other RestAction (besides the obvious ones of RestSearchAction and the async RestSearchAction) you had to change was this one.

But there are other several Rest actions that converge on _search that are (or will be) also CPS enabled:

_msearch/template
_eql/search
_sql
_mvt
_<index>/_count
_cat/count

We should track down whether any of those allow setting ccs_minimize_roundtrips and if so, if additional code changes are needed.

Copy link
Contributor Author

@pawankartik-elastic pawankartik-elastic Nov 14, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The endpoints you've listed have their own dedicated parsing strategies that are lenient wrt the query parameters being passed, and there's some inconsistency between our public-facing docs and how the endpoints work. For example, for some of them, you can pass MRT, but it's not consumed. Here's why I only had to touch only a few Rest*Action-s:

  • Both _eql and _msearch/template accept MRT and have their own dedicated parser. I can accommodate these changes there.
  • _sql does not appear to accept MRT and has its own parser.
  • _mvt does not appear to accept MRT and has its own parser.
  • {index}/_count does not appear to accept MRT and has its own simple parser.
  • _cat/count does not appear to accept MRT and has its own simple parser.

Endpoints that aren't explicitly consuming MRT are defaulting to true.

Own parser = the parsing happens within the respective Rest*Action with info manually being extracted via RestRequest#param() and passed to the respective *Request-s.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Awesome. Thanks. Excellent analysis. 🥇

);

// Creates the search template request
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,9 @@
import org.elasticsearch.common.bytes.BytesReference;
import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.io.stream.StreamOutput;
import org.elasticsearch.common.logging.HeaderWarning;
import org.elasticsearch.common.xcontent.XContentHelper;
import org.elasticsearch.rest.action.search.SearchParamsParser;
import org.elasticsearch.tasks.CancellableTask;
import org.elasticsearch.tasks.Task;
import org.elasticsearch.tasks.TaskId;
Expand All @@ -36,6 +38,7 @@
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.stream.Collectors;

import static org.elasticsearch.action.ValidateActions.addValidationError;
Expand Down Expand Up @@ -167,7 +170,12 @@ public static void readMultiLineFormat(
String routing,
String searchType,
Boolean ccsMinimizeRoundtrips,
boolean allowExplicitIndex
boolean allowExplicitIndex,
/*
* Refer to RestSearchAction#parseSearchRequest()'s JavaDoc to understand why this is an Optional
* and what its values mean with respect to an endpoint's Cross Project Search status/support.
*/
Optional<Boolean> crossProjectEnabled
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Optional suggestion: Add javadoc here what Optional.TRUE, Optional.FALSE and Optional.empty mean. That's not going to be obvious. Or if you feel like you have to document that 20 times, put a note here on where to see the javadoc for that?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

) throws IOException {
readMultiLineFormat(
xContent,
Expand All @@ -180,7 +188,8 @@ public static void readMultiLineFormat(
searchType,
ccsMinimizeRoundtrips,
allowExplicitIndex,
(s, o, r) -> false
(s, o, r) -> false,
crossProjectEnabled
);

}
Expand All @@ -196,10 +205,16 @@ public static void readMultiLineFormat(
String searchType,
Boolean ccsMinimizeRoundtrips,
boolean allowExplicitIndex,
TriFunction<String, Object, SearchRequest, Boolean> extraParamParser
TriFunction<String, Object, SearchRequest, Boolean> extraParamParser,
/*
* Refer to RestSearchAction#parseSearchRequest()'s JavaDoc to understand why this is an Optional
* and what its values mean with respect to an endpoint's Cross Project Search status/support.
*/
Optional<Boolean> crossProjectEnabled
) throws IOException {
int from = 0;
byte marker = xContent.bulkSeparator();
boolean warnedMrtForCps = false;
while (true) {
int nextMarker = findNextMarker(marker, from, data);
if (nextMarker == -1) {
Expand All @@ -219,6 +234,11 @@ public static void readMultiLineFormat(
if (searchType != null) {
searchRequest.searchType(searchType);
}
/*
* This `ccsMinimizeRoundtrips` refers to the value specified as the query parameter and is extracted in
* `RestMultiSearchAction#parseMultiLineRequest()`. If in a Cross Project Search environment, it is
* guaranteed to be `true`. Otherwise, its value is whatever that the user is provided.
*/
if (ccsMinimizeRoundtrips != null) {
searchRequest.setCcsMinimizeRoundtrips(ccsMinimizeRoundtrips);
}
Expand Down Expand Up @@ -250,7 +270,11 @@ public static void readMultiLineFormat(
} else if ("search_type".equals(entry.getKey()) || "searchType".equals(entry.getKey())) {
searchRequest.searchType(nodeStringValue(value, null));
} else if ("ccs_minimize_roundtrips".equals(entry.getKey()) || "ccsMinimizeRoundtrips".equals(entry.getKey())) {
searchRequest.setCcsMinimizeRoundtrips(nodeBooleanValue(value));
searchRequest.setCcsMinimizeRoundtrips(crossProjectEnabled.orElse(false) || nodeBooleanValue(value));
if (crossProjectEnabled.orElse(false) && warnedMrtForCps == false) {
HeaderWarning.addWarning(SearchParamsParser.MRT_SET_IN_CPS_WARN);
warnedMrtForCps = true;
}
} else if ("request_cache".equals(entry.getKey()) || "requestCache".equals(entry.getKey())) {
searchRequest.requestCache(nodeBooleanValue(value, entry.getKey()));
} else if ("preference".equals(entry.getKey())) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,15 @@
import org.elasticsearch.rest.action.RestCancellableNodeClient;
import org.elasticsearch.rest.action.RestRefCountedChunkedToXContentListener;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.crossproject.CrossProjectModeDecider;
import org.elasticsearch.usage.SearchUsageHolder;
import org.elasticsearch.xcontent.XContent;
import org.elasticsearch.xcontent.XContentParser;
import org.elasticsearch.xcontent.XContentType;

import java.io.IOException;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.function.Predicate;

Expand All @@ -48,13 +50,13 @@ public class RestMultiSearchAction extends BaseRestHandler {
private final boolean allowExplicitIndex;
private final SearchUsageHolder searchUsageHolder;
private final Predicate<NodeFeature> clusterSupportsFeature;
private final Settings settings;
private final CrossProjectModeDecider crossProjectModeDecider;

public RestMultiSearchAction(Settings settings, SearchUsageHolder searchUsageHolder, Predicate<NodeFeature> clusterSupportsFeature) {
this.settings = settings;
this.allowExplicitIndex = MULTI_ALLOW_EXPLICIT_INDEX.get(settings);
this.searchUsageHolder = searchUsageHolder;
this.clusterSupportsFeature = clusterSupportsFeature;
this.crossProjectModeDecider = new CrossProjectModeDecider(settings);
}

@Override
Expand All @@ -77,11 +79,18 @@ public RestChannelConsumer prepareRequest(final RestRequest request, final NodeC
if (client.threadPool() != null && client.threadPool().getThreadContext() != null) {
client.threadPool().getThreadContext().setErrorTraceTransportHeader(request);
}
if (settings != null && settings.getAsBoolean("serverless.cross_project.enabled", false)) {
boolean crossProjectEnabled = crossProjectModeDecider.crossProjectEnabled();
if (crossProjectEnabled) {
// accept but drop project_routing param until fully supported
request.param("project_routing");
}
final MultiSearchRequest multiSearchRequest = parseRequest(request, allowExplicitIndex, searchUsageHolder, clusterSupportsFeature);
final MultiSearchRequest multiSearchRequest = parseRequest(
request,
allowExplicitIndex,
searchUsageHolder,
clusterSupportsFeature,
Optional.of(crossProjectEnabled)
);
return channel -> {
final RestCancellableNodeClient cancellableClient = new RestCancellableNodeClient(client, request.getHttpChannel());
cancellableClient.execute(
Expand All @@ -99,9 +108,17 @@ public static MultiSearchRequest parseRequest(
RestRequest restRequest,
boolean allowExplicitIndex,
SearchUsageHolder searchUsageHolder,
Predicate<NodeFeature> clusterSupportsFeature
Predicate<NodeFeature> clusterSupportsFeature,
Optional<Boolean> crossProjectEnabled
) throws IOException {
return parseRequest(restRequest, allowExplicitIndex, searchUsageHolder, clusterSupportsFeature, (k, v, r) -> false);
return parseRequest(
restRequest,
allowExplicitIndex,
searchUsageHolder,
clusterSupportsFeature,
(k, v, r) -> false,
crossProjectEnabled
);
}

/**
Expand All @@ -113,7 +130,8 @@ public static MultiSearchRequest parseRequest(
boolean allowExplicitIndex,
SearchUsageHolder searchUsageHolder,
Predicate<NodeFeature> clusterSupportsFeature,
TriFunction<String, Object, SearchRequest, Boolean> extraParamParser
TriFunction<String, Object, SearchRequest, Boolean> extraParamParser,
Optional<Boolean> crossProjectEnabled
) throws IOException {
MultiSearchRequest multiRequest = new MultiSearchRequest();
IndicesOptions indicesOptions = IndicesOptions.fromRequest(restRequest, multiRequest.indicesOptions());
Expand Down Expand Up @@ -143,11 +161,11 @@ public static MultiSearchRequest parseRequest(
RestSearchAction.preparePointInTime(searchRequest, restRequest);
} else {
searchRequest.setCcsMinimizeRoundtrips(
restRequest.paramAsBoolean("ccs_minimize_roundtrips", searchRequest.isCcsMinimizeRoundtrips())
SearchParamsParser.parseCcsMinimizeRoundtrips(crossProjectEnabled, restRequest, searchRequest.isCcsMinimizeRoundtrips())
);
}
multiRequest.add(searchRequest);
}, extraParamParser);
}, extraParamParser, crossProjectEnabled);
List<SearchRequest> requests = multiRequest.requests();
for (SearchRequest request : requests) {
// preserve if it's set on the request
Expand All @@ -168,9 +186,10 @@ public static void parseMultiLineRequest(
RestRequest request,
IndicesOptions indicesOptions,
boolean allowExplicitIndex,
CheckedBiConsumer<SearchRequest, XContentParser, IOException> consumer
CheckedBiConsumer<SearchRequest, XContentParser, IOException> consumer,
Optional<Boolean> crossProjectEnabled
) throws IOException {
parseMultiLineRequest(request, indicesOptions, allowExplicitIndex, consumer, (k, v, r) -> false);
parseMultiLineRequest(request, indicesOptions, allowExplicitIndex, consumer, (k, v, r) -> false, crossProjectEnabled);
}

/**
Expand All @@ -182,12 +201,13 @@ public static void parseMultiLineRequest(
IndicesOptions indicesOptions,
boolean allowExplicitIndex,
CheckedBiConsumer<SearchRequest, XContentParser, IOException> consumer,
TriFunction<String, Object, SearchRequest, Boolean> extraParamParser
TriFunction<String, Object, SearchRequest, Boolean> extraParamParser,
Optional<Boolean> crossProjectEnabled
) throws IOException {

String[] indices = Strings.splitStringByCommaToArray(request.param("index"));
String searchType = request.param("search_type");
boolean ccsMinimizeRoundtrips = request.paramAsBoolean("ccs_minimize_roundtrips", true);
boolean ccsMinimizeRoundtrips = SearchParamsParser.parseCcsMinimizeRoundtrips(crossProjectEnabled, request);
String routing = request.param("routing");

final Tuple<XContentType, ReleasableBytesReference> sourceTuple = request.contentOrSourceParam();
Expand All @@ -204,7 +224,8 @@ public static void parseMultiLineRequest(
searchType,
ccsMinimizeRoundtrips,
allowExplicitIndex,
extraParamParser
extraParamParser,
crossProjectEnabled
);
}

Expand Down
Loading