org.elasticsearch.action.search.TransportSearchAction Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of elasticsearch Show documentation
Show all versions of elasticsearch Show documentation
Elasticsearch subproject :server
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
package org.elasticsearch.action.search;
import org.elasticsearch.action.ActionListener;
import org.elasticsearch.action.IndicesRequest;
import org.elasticsearch.action.OriginalIndices;
import org.elasticsearch.action.admin.cluster.shards.ClusterSearchShardsGroup;
import org.elasticsearch.action.admin.cluster.shards.ClusterSearchShardsRequest;
import org.elasticsearch.action.admin.cluster.shards.ClusterSearchShardsResponse;
import org.elasticsearch.action.support.ActionFilters;
import org.elasticsearch.action.support.HandledTransportAction;
import org.elasticsearch.action.support.IndicesOptions;
import org.elasticsearch.client.internal.Client;
import org.elasticsearch.cluster.ClusterState;
import org.elasticsearch.cluster.block.ClusterBlockException;
import org.elasticsearch.cluster.block.ClusterBlockLevel;
import org.elasticsearch.cluster.metadata.IndexAbstraction;
import org.elasticsearch.cluster.metadata.IndexMetadata;
import org.elasticsearch.cluster.metadata.IndexNameExpressionResolver;
import org.elasticsearch.cluster.node.DiscoveryNode;
import org.elasticsearch.cluster.node.DiscoveryNodes;
import org.elasticsearch.cluster.routing.GroupShardsIterator;
import org.elasticsearch.cluster.routing.OperationRouting;
import org.elasticsearch.cluster.routing.ShardIterator;
import org.elasticsearch.cluster.routing.ShardRouting;
import org.elasticsearch.cluster.service.ClusterService;
import org.elasticsearch.common.Strings;
import org.elasticsearch.common.breaker.CircuitBreaker;
import org.elasticsearch.common.inject.Inject;
import org.elasticsearch.common.io.stream.NamedWriteableRegistry;
import org.elasticsearch.common.io.stream.Writeable;
import org.elasticsearch.common.logging.DeprecationCategory;
import org.elasticsearch.common.logging.DeprecationLogger;
import org.elasticsearch.common.settings.Setting;
import org.elasticsearch.common.settings.Setting.Property;
import org.elasticsearch.common.util.Maps;
import org.elasticsearch.common.util.concurrent.AtomicArray;
import org.elasticsearch.common.util.concurrent.CountDown;
import org.elasticsearch.core.Nullable;
import org.elasticsearch.core.TimeValue;
import org.elasticsearch.index.Index;
import org.elasticsearch.index.IndexNotFoundException;
import org.elasticsearch.index.query.Rewriteable;
import org.elasticsearch.index.shard.ShardId;
import org.elasticsearch.index.shard.ShardNotFoundException;
import org.elasticsearch.indices.ExecutorSelector;
import org.elasticsearch.indices.breaker.CircuitBreakerService;
import org.elasticsearch.search.SearchPhaseResult;
import org.elasticsearch.search.SearchService;
import org.elasticsearch.search.SearchShardTarget;
import org.elasticsearch.search.aggregations.AggregationReduceContext;
import org.elasticsearch.search.aggregations.InternalAggregations;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.internal.AliasFilter;
import org.elasticsearch.search.internal.InternalSearchResponse;
import org.elasticsearch.search.internal.SearchContext;
import org.elasticsearch.search.profile.SearchProfileResults;
import org.elasticsearch.search.profile.SearchProfileShardResult;
import org.elasticsearch.tasks.Task;
import org.elasticsearch.tasks.TaskId;
import org.elasticsearch.threadpool.ThreadPool;
import org.elasticsearch.transport.RemoteClusterAware;
import org.elasticsearch.transport.RemoteClusterService;
import org.elasticsearch.transport.RemoteTransportException;
import org.elasticsearch.transport.Transport;
import org.elasticsearch.transport.TransportService;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.BiConsumer;
import java.util.function.BiFunction;
import java.util.function.BooleanSupplier;
import java.util.function.Function;
import java.util.function.LongSupplier;
import java.util.stream.Collectors;
import java.util.stream.StreamSupport;
import static org.elasticsearch.action.search.SearchType.DFS_QUERY_THEN_FETCH;
import static org.elasticsearch.action.search.SearchType.QUERY_THEN_FETCH;
import static org.elasticsearch.action.search.TransportSearchHelper.checkCCSVersionCompatibility;
import static org.elasticsearch.search.sort.FieldSortBuilder.hasPrimaryFieldSort;
import static org.elasticsearch.threadpool.ThreadPool.Names.SYSTEM_CRITICAL_READ;
import static org.elasticsearch.threadpool.ThreadPool.Names.SYSTEM_READ;
public class TransportSearchAction extends HandledTransportAction {
private static final DeprecationLogger DEPRECATION_LOGGER = DeprecationLogger.getLogger(TransportSearchAction.class);
public static final String FROZEN_INDICES_DEPRECATION_MESSAGE = "Searching frozen indices [{}] is deprecated."
+ " Consider cold or frozen tiers in place of frozen indices. The frozen feature will be removed in a feature release.";
/** The maximum number of shards for a single search request. */
public static final Setting SHARD_COUNT_LIMIT_SETTING = Setting.longSetting(
"action.search.shard_count.limit",
Long.MAX_VALUE,
1L,
Property.Dynamic,
Property.NodeScope
);
public static final Setting DEFAULT_PRE_FILTER_SHARD_SIZE = Setting.intSetting(
"action.search.pre_filter_shard_size.default",
SearchRequest.DEFAULT_PRE_FILTER_SHARD_SIZE,
1,
Property.NodeScope
);
private final ThreadPool threadPool;
private final ClusterService clusterService;
private final SearchTransportService searchTransportService;
private final RemoteClusterService remoteClusterService;
private final SearchPhaseController searchPhaseController;
private final SearchService searchService;
private final IndexNameExpressionResolver indexNameExpressionResolver;
private final NamedWriteableRegistry namedWriteableRegistry;
private final CircuitBreaker circuitBreaker;
private final ExecutorSelector executorSelector;
private final int defaultPreFilterShardSize;
private final boolean ccsCheckCompatibility;
@Inject
public TransportSearchAction(
ThreadPool threadPool,
CircuitBreakerService circuitBreakerService,
TransportService transportService,
SearchService searchService,
SearchTransportService searchTransportService,
SearchPhaseController searchPhaseController,
ClusterService clusterService,
ActionFilters actionFilters,
IndexNameExpressionResolver indexNameExpressionResolver,
NamedWriteableRegistry namedWriteableRegistry,
ExecutorSelector executorSelector
) {
super(SearchAction.NAME, transportService, actionFilters, (Writeable.Reader) SearchRequest::new);
this.threadPool = threadPool;
this.circuitBreaker = circuitBreakerService.getBreaker(CircuitBreaker.REQUEST);
this.searchPhaseController = searchPhaseController;
this.searchTransportService = searchTransportService;
this.remoteClusterService = searchTransportService.getRemoteClusterService();
SearchTransportService.registerRequestHandler(transportService, searchService);
this.clusterService = clusterService;
this.searchService = searchService;
this.indexNameExpressionResolver = indexNameExpressionResolver;
this.namedWriteableRegistry = namedWriteableRegistry;
this.executorSelector = executorSelector;
this.defaultPreFilterShardSize = DEFAULT_PRE_FILTER_SHARD_SIZE.get(clusterService.getSettings());
this.ccsCheckCompatibility = SearchService.CCS_VERSION_CHECK_SETTING.get(clusterService.getSettings());
}
private Map buildPerIndexOriginalIndices(
ClusterState clusterState,
Set indicesAndAliases,
Index[] concreteIndices,
IndicesOptions indicesOptions
) {
Map res = new HashMap<>();
for (Index index : concreteIndices) {
clusterState.blocks().indexBlockedRaiseException(ClusterBlockLevel.READ, index.getName());
String[] aliases = indexNameExpressionResolver.indexAliases(
clusterState,
index.getName(),
aliasMetadata -> true,
dataStreamAlias -> true,
true,
indicesAndAliases
);
BooleanSupplier hasDataStreamRef = () -> {
IndexAbstraction ret = clusterState.getMetadata().getIndicesLookup().get(index.getName());
if (ret == null || ret.getParentDataStream() == null) {
return false;
}
return indicesAndAliases.contains(ret.getParentDataStream().getName());
};
List finalIndices = new ArrayList<>();
if (aliases == null || aliases.length == 0 || indicesAndAliases.contains(index.getName()) || hasDataStreamRef.getAsBoolean()) {
finalIndices.add(index.getName());
}
if (aliases != null) {
finalIndices.addAll(Arrays.asList(aliases));
}
res.put(index.getUUID(), new OriginalIndices(finalIndices.toArray(String[]::new), indicesOptions));
}
return Collections.unmodifiableMap(res);
}
private Map buildPerIndexAliasFilter(
ClusterState clusterState,
Set indicesAndAliases,
Index[] concreteIndices,
Map remoteAliasMap
) {
final Map aliasFilterMap = new HashMap<>();
for (Index index : concreteIndices) {
clusterState.blocks().indexBlockedRaiseException(ClusterBlockLevel.READ, index.getName());
AliasFilter aliasFilter = searchService.buildAliasFilter(clusterState, index.getName(), indicesAndAliases);
assert aliasFilter != null;
aliasFilterMap.put(index.getUUID(), aliasFilter);
}
aliasFilterMap.putAll(remoteAliasMap);
return aliasFilterMap;
}
private Map resolveIndexBoosts(SearchRequest searchRequest, ClusterState clusterState) {
if (searchRequest.source() == null) {
return Collections.emptyMap();
}
SearchSourceBuilder source = searchRequest.source();
if (source.indexBoosts() == null) {
return Collections.emptyMap();
}
Map concreteIndexBoosts = new HashMap<>();
for (SearchSourceBuilder.IndexBoost ib : source.indexBoosts()) {
Index[] concreteIndices = indexNameExpressionResolver.concreteIndices(
clusterState,
searchRequest.indicesOptions(),
ib.getIndex()
);
for (Index concreteIndex : concreteIndices) {
concreteIndexBoosts.putIfAbsent(concreteIndex.getUUID(), ib.getBoost());
}
}
return Collections.unmodifiableMap(concreteIndexBoosts);
}
/**
* Search operations need two clocks. One clock is to fulfill real clock needs (e.g., resolving
* "now" to an index name). Another clock is needed for measuring how long a search operation
* took. These two uses are at odds with each other. There are many issues with using a real
* clock for measuring how long an operation took (they often lack precision, they are subject
* to moving backwards due to NTP and other such complexities, etc.). There are also issues with
* using a relative clock for reporting real time. Thus, we simply separate these two uses.
*/
record SearchTimeProvider(long absoluteStartMillis, long relativeStartNanos, LongSupplier relativeCurrentNanosProvider) {
/**
* Instantiates a new search time provider. The absolute start time is the real clock time
* used for resolving index expressions that include dates. The relative start time is the
* start of the search operation according to a relative clock. The total time the search
* operation took can be measured against the provided relative clock and the relative start
* time.
*
* @param absoluteStartMillis the absolute start time in milliseconds since the epoch
* @param relativeStartNanos the relative start time in nanoseconds
* @param relativeCurrentNanosProvider provides the current relative time
*/
SearchTimeProvider {}
long buildTookInMillis() {
return TimeUnit.NANOSECONDS.toMillis(relativeCurrentNanosProvider.getAsLong() - relativeStartNanos);
}
}
@Override
protected void doExecute(Task task, SearchRequest searchRequest, ActionListener listener) {
executeRequest((SearchTask) task, searchRequest, this::searchAsyncAction, listener);
}
public interface SinglePhaseSearchAction {
void executeOnShardTarget(
SearchTask searchTask,
SearchShardIterator shardIt,
Transport.Connection connection,
ActionListener listener
);
}
public void executeRequest(
SearchTask task,
SearchRequest searchRequest,
String actionName,
boolean includeSearchContext,
SinglePhaseSearchAction phaseSearchAction,
ActionListener listener
) {
executeRequest(task, searchRequest, new SearchAsyncActionProvider() {
@Override
public AbstractSearchAsyncAction asyncSearchAction(
SearchTask task,
SearchRequest searchRequest,
Executor executor,
GroupShardsIterator shardsIts,
SearchTimeProvider timeProvider,
BiFunction connectionLookup,
ClusterState clusterState,
Map aliasFilter,
Map concreteIndexBoosts,
ActionListener listener,
boolean preFilter,
ThreadPool threadPool,
SearchResponse.Clusters clusters
) {
return new AbstractSearchAsyncAction<>(
actionName,
logger,
searchTransportService,
connectionLookup,
aliasFilter,
concreteIndexBoosts,
executor,
searchRequest,
listener,
shardsIts,
timeProvider,
clusterState,
task,
new ArraySearchPhaseResults<>(shardsIts.size()),
1,
clusters
) {
@Override
protected void executePhaseOnShard(
SearchShardIterator shardIt,
SearchShardTarget shard,
SearchActionListener listener
) {
final Transport.Connection connection = getConnection(shard.getClusterAlias(), shard.getNodeId());
phaseSearchAction.executeOnShardTarget(task, shardIt, connection, listener);
}
@Override
protected SearchPhase getNextPhase(SearchPhaseResults results, SearchPhaseContext context) {
return new SearchPhase(getName()) {
@Override
public void run() {
final AtomicArray atomicArray = results.getAtomicArray();
sendSearchResponse(InternalSearchResponse.EMPTY_WITH_TOTAL_HITS, atomicArray);
}
};
}
@Override
boolean buildPointInTimeFromSearchResults() {
return includeSearchContext;
}
};
}
}, listener);
}
private void executeRequest(
SearchTask task,
SearchRequest original,
SearchAsyncActionProvider searchAsyncActionProvider,
ActionListener listener
) {
final long relativeStartNanos = System.nanoTime();
final SearchTimeProvider timeProvider = new SearchTimeProvider(
original.getOrCreateAbsoluteStartMillis(),
relativeStartNanos,
System::nanoTime
);
ActionListener rewriteListener = ActionListener.wrap(rewritten -> {
final SearchContextId searchContext;
final Map remoteClusterIndices;
if (ccsCheckCompatibility) {
checkCCSVersionCompatibility(rewritten);
}
if (rewritten.pointInTimeBuilder() != null) {
searchContext = rewritten.pointInTimeBuilder().getSearchContextId(namedWriteableRegistry);
remoteClusterIndices = getIndicesFromSearchContexts(searchContext, rewritten.indicesOptions());
} else {
searchContext = null;
remoteClusterIndices = remoteClusterService.groupIndices(rewritten.indicesOptions(), rewritten.indices());
}
OriginalIndices localIndices = remoteClusterIndices.remove(RemoteClusterAware.LOCAL_CLUSTER_GROUP_KEY);
final ClusterState clusterState = clusterService.state();
if (remoteClusterIndices.isEmpty()) {
executeLocalSearch(
task,
timeProvider,
rewritten,
localIndices,
clusterState,
listener,
searchContext,
searchAsyncActionProvider
);
} else {
if (shouldMinimizeRoundtrips(rewritten)) {
final TaskId parentTaskId = task.taskInfo(clusterService.localNode().getId(), false).taskId();
ccsRemoteReduce(
parentTaskId,
rewritten,
localIndices,
remoteClusterIndices,
timeProvider,
searchService.aggReduceContextBuilder(task::isCancelled, rewritten),
remoteClusterService,
threadPool,
listener,
(r, l) -> executeLocalSearch(
task,
timeProvider,
r,
localIndices,
clusterState,
l,
searchContext,
searchAsyncActionProvider
)
);
} else {
AtomicInteger skippedClusters = new AtomicInteger(0);
collectSearchShards(
rewritten.indicesOptions(),
rewritten.preference(),
rewritten.routing(),
skippedClusters,
remoteClusterIndices,
remoteClusterService,
threadPool,
ActionListener.wrap(searchShardsResponses -> {
final BiFunction clusterNodeLookup = getRemoteClusterNodeLookup(
searchShardsResponses
);
final Map remoteAliasFilters;
final List remoteShardIterators;
if (searchContext != null) {
remoteAliasFilters = searchContext.aliasFilter();
remoteShardIterators = getRemoteShardsIteratorFromPointInTime(
searchShardsResponses,
searchContext,
rewritten.pointInTimeBuilder().getKeepAlive(),
remoteClusterIndices
);
} else {
remoteAliasFilters = getRemoteAliasFilters(searchShardsResponses);
remoteShardIterators = getRemoteShardsIterator(
searchShardsResponses,
remoteClusterIndices,
remoteAliasFilters
);
}
int localClusters = localIndices == null ? 0 : 1;
int totalClusters = remoteClusterIndices.size() + localClusters;
int successfulClusters = searchShardsResponses.size() + localClusters;
executeSearch(
task,
timeProvider,
rewritten,
localIndices,
remoteShardIterators,
clusterNodeLookup,
clusterState,
remoteAliasFilters,
listener,
new SearchResponse.Clusters(totalClusters, successfulClusters, skippedClusters.get()),
searchContext,
searchAsyncActionProvider
);
}, listener::onFailure)
);
}
}
}, listener::onFailure);
Rewriteable.rewriteAndFetch(original, searchService.getRewriteContext(timeProvider::absoluteStartMillis), rewriteListener);
}
static boolean shouldMinimizeRoundtrips(SearchRequest searchRequest) {
if (searchRequest.isCcsMinimizeRoundtrips() == false) {
return false;
}
if (searchRequest.scroll() != null) {
return false;
}
if (searchRequest.pointInTimeBuilder() != null) {
return false;
}
if (searchRequest.searchType() == DFS_QUERY_THEN_FETCH) {
return false;
}
SearchSourceBuilder source = searchRequest.source();
return source == null
|| source.collapse() == null
|| source.collapse().getInnerHits() == null
|| source.collapse().getInnerHits().isEmpty();
}
static void ccsRemoteReduce(
TaskId parentTaskId,
SearchRequest searchRequest,
OriginalIndices localIndices,
Map remoteIndices,
SearchTimeProvider timeProvider,
AggregationReduceContext.Builder aggReduceContextBuilder,
RemoteClusterService remoteClusterService,
ThreadPool threadPool,
ActionListener listener,
BiConsumer> localSearchConsumer
) {
if (localIndices == null && remoteIndices.size() == 1) {
// if we are searching against a single remote cluster, we simply forward the original search request to such cluster
// and we directly perform final reduction in the remote cluster
Map.Entry entry = remoteIndices.entrySet().iterator().next();
String clusterAlias = entry.getKey();
boolean skipUnavailable = remoteClusterService.isSkipUnavailable(clusterAlias);
OriginalIndices indices = entry.getValue();
SearchRequest ccsSearchRequest = SearchRequest.subSearchRequest(
parentTaskId,
searchRequest,
indices.indices(),
clusterAlias,
timeProvider.absoluteStartMillis(),
true
);
Client remoteClusterClient = remoteClusterService.getRemoteClusterClient(threadPool, clusterAlias);
remoteClusterClient.search(ccsSearchRequest, new ActionListener() {
@Override
public void onResponse(SearchResponse searchResponse) {
Map profileResults = searchResponse.getProfileResults();
SearchProfileResults profile = profileResults == null || profileResults.isEmpty()
? null
: new SearchProfileResults(profileResults);
InternalSearchResponse internalSearchResponse = new InternalSearchResponse(
searchResponse.getHits(),
(InternalAggregations) searchResponse.getAggregations(),
searchResponse.getSuggest(),
profile,
searchResponse.isTimedOut(),
searchResponse.isTerminatedEarly(),
searchResponse.getNumReducePhases()
);
listener.onResponse(
new SearchResponse(
internalSearchResponse,
searchResponse.getScrollId(),
searchResponse.getTotalShards(),
searchResponse.getSuccessfulShards(),
searchResponse.getSkippedShards(),
timeProvider.buildTookInMillis(),
searchResponse.getShardFailures(),
new SearchResponse.Clusters(1, 1, 0),
searchResponse.pointInTimeId()
)
);
}
@Override
public void onFailure(Exception e) {
if (skipUnavailable) {
listener.onResponse(SearchResponse.empty(timeProvider::buildTookInMillis, new SearchResponse.Clusters(1, 0, 1)));
} else {
listener.onFailure(wrapRemoteClusterFailure(clusterAlias, e));
}
}
});
} else {
SearchResponseMerger searchResponseMerger = createSearchResponseMerger(
searchRequest.source(),
timeProvider,
aggReduceContextBuilder
);
AtomicInteger skippedClusters = new AtomicInteger(0);
final AtomicReference exceptions = new AtomicReference<>();
int totalClusters = remoteIndices.size() + (localIndices == null ? 0 : 1);
final CountDown countDown = new CountDown(totalClusters);
for (Map.Entry entry : remoteIndices.entrySet()) {
String clusterAlias = entry.getKey();
boolean skipUnavailable = remoteClusterService.isSkipUnavailable(clusterAlias);
OriginalIndices indices = entry.getValue();
SearchRequest ccsSearchRequest = SearchRequest.subSearchRequest(
parentTaskId,
searchRequest,
indices.indices(),
clusterAlias,
timeProvider.absoluteStartMillis(),
false
);
ActionListener ccsListener = createCCSListener(
clusterAlias,
skipUnavailable,
countDown,
skippedClusters,
exceptions,
searchResponseMerger,
totalClusters,
listener
);
Client remoteClusterClient = remoteClusterService.getRemoteClusterClient(threadPool, clusterAlias);
remoteClusterClient.search(ccsSearchRequest, ccsListener);
}
if (localIndices != null) {
ActionListener ccsListener = createCCSListener(
RemoteClusterAware.LOCAL_CLUSTER_GROUP_KEY,
false,
countDown,
skippedClusters,
exceptions,
searchResponseMerger,
totalClusters,
listener
);
SearchRequest ccsLocalSearchRequest = SearchRequest.subSearchRequest(
parentTaskId,
searchRequest,
localIndices.indices(),
RemoteClusterAware.LOCAL_CLUSTER_GROUP_KEY,
timeProvider.absoluteStartMillis(),
false
);
localSearchConsumer.accept(ccsLocalSearchRequest, ccsListener);
}
}
}
static SearchResponseMerger createSearchResponseMerger(
SearchSourceBuilder source,
SearchTimeProvider timeProvider,
AggregationReduceContext.Builder aggReduceContextBuilder
) {
final int from;
final int size;
final int trackTotalHitsUpTo;
if (source == null) {
from = SearchService.DEFAULT_FROM;
size = SearchService.DEFAULT_SIZE;
trackTotalHitsUpTo = SearchContext.DEFAULT_TRACK_TOTAL_HITS_UP_TO;
} else {
from = source.from() == -1 ? SearchService.DEFAULT_FROM : source.from();
size = source.size() == -1 ? SearchService.DEFAULT_SIZE : source.size();
trackTotalHitsUpTo = source.trackTotalHitsUpTo() == null
? SearchContext.DEFAULT_TRACK_TOTAL_HITS_UP_TO
: source.trackTotalHitsUpTo();
// here we modify the original source so we can re-use it by setting it to each outgoing search request
source.from(0);
source.size(from + size);
}
return new SearchResponseMerger(from, size, trackTotalHitsUpTo, timeProvider, aggReduceContextBuilder);
}
static void collectSearchShards(
IndicesOptions indicesOptions,
String preference,
String routing,
AtomicInteger skippedClusters,
Map remoteIndicesByCluster,
RemoteClusterService remoteClusterService,
ThreadPool threadPool,
ActionListener