Skip to content

Commit b90f374

Browse files
Remove some copying and slow sorting from search shard resolution (#123426)
Removed one unnecessary sort (the shards are already sorted in the same order) and ported two more to JDK's list sort which beats Lucene's timsort by quite a margin nowadays (the mutations are done straight on the array backing the list and that saves endless indirection). Also, removed a needless intermediary to and from set copy and an unnecessary conditional. This is all motivated by spacetime project benchmarking showing this stuff as one of the biggest contributors to search transport thread use.
1 parent 60ad8ba commit b90f374

File tree

3 files changed

+25
-40
lines changed

3 files changed

+25
-40
lines changed

server/src/main/java/org/elasticsearch/action/search/TransportSearchAction.java

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@
1111

1212
import org.apache.logging.log4j.LogManager;
1313
import org.apache.logging.log4j.Logger;
14-
import org.apache.lucene.util.CollectionUtil;
1514
import org.elasticsearch.ExceptionsHelper;
1615
import org.elasticsearch.TransportVersions;
1716
import org.elasticsearch.action.ActionListener;
@@ -1430,7 +1429,7 @@ static List<SearchShardIterator> mergeShardsIterators(
14301429
} else {
14311430
shards = CollectionUtils.concatLists(remoteShardIterators, localShardIterators);
14321431
}
1433-
CollectionUtil.timSort(shards);
1432+
shards.sort(SearchShardIterator::compareTo);
14341433
return shards;
14351434
}
14361435

server/src/main/java/org/elasticsearch/cluster/routing/IndexShardRoutingTable.java

Lines changed: 20 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
import java.util.Arrays;
2929
import java.util.Collections;
3030
import java.util.Comparator;
31+
import java.util.HashMap;
3132
import java.util.HashSet;
3233
import java.util.List;
3334
import java.util.Locale;
@@ -258,22 +259,14 @@ public ShardIterator activeInitializingShardsRankedIt(
258259
return new ShardIterator(shardId, ordered);
259260
}
260261

261-
private static Set<String> getAllNodeIds(final List<ShardRouting> shards) {
262-
final Set<String> nodeIds = new HashSet<>();
263-
for (ShardRouting shard : shards) {
264-
nodeIds.add(shard.currentNodeId());
265-
}
266-
return nodeIds;
267-
}
268-
269262
private static Map<String, Optional<ResponseCollectorService.ComputedNodeStats>> getNodeStats(
270-
final Set<String> nodeIds,
263+
List<ShardRouting> shardRoutings,
271264
final ResponseCollectorService collector
272265
) {
273266

274-
final Map<String, Optional<ResponseCollectorService.ComputedNodeStats>> nodeStats = Maps.newMapWithExpectedSize(nodeIds.size());
275-
for (String nodeId : nodeIds) {
276-
nodeStats.put(nodeId, collector.getNodeStatistics(nodeId));
267+
final Map<String, Optional<ResponseCollectorService.ComputedNodeStats>> nodeStats = new HashMap<>();
268+
for (ShardRouting shardRouting : shardRoutings) {
269+
nodeStats.computeIfAbsent(shardRouting.currentNodeId(), collector::getNodeStatistics);
277270
}
278271
return nodeStats;
279272
}
@@ -342,32 +335,28 @@ private static List<ShardRouting> rankShardsAndUpdateStats(
342335
}
343336

344337
// Retrieve which nodes we can potentially send the query to
345-
final Set<String> nodeIds = getAllNodeIds(shards);
346-
final Map<String, Optional<ResponseCollectorService.ComputedNodeStats>> nodeStats = getNodeStats(nodeIds, collector);
338+
final Map<String, Optional<ResponseCollectorService.ComputedNodeStats>> nodeStats = getNodeStats(shards, collector);
347339

348340
// Retrieve all the nodes the shards exist on
349-
final Map<String, Double> nodeRanks = rankNodes(nodeStats, nodeSearchCounts);
350341

351342
// sort all shards based on the shard rank
352343
ArrayList<ShardRouting> sortedShards = new ArrayList<>(shards);
353-
Collections.sort(sortedShards, new NodeRankComparator(nodeRanks));
344+
sortedShards.sort(new NodeRankComparator(rankNodes(nodeStats, nodeSearchCounts)));
354345

355346
// adjust the non-winner nodes' stats so they will get a chance to receive queries
356-
if (sortedShards.size() > 1) {
357-
ShardRouting minShard = sortedShards.get(0);
358-
// If the winning shard is not started we are ranking initializing
359-
// shards, don't bother to do adjustments
360-
if (minShard.started()) {
361-
String minNodeId = minShard.currentNodeId();
362-
Optional<ResponseCollectorService.ComputedNodeStats> maybeMinStats = nodeStats.get(minNodeId);
363-
if (maybeMinStats.isPresent()) {
364-
adjustStats(collector, nodeStats, minNodeId, maybeMinStats.get());
365-
// Increase the number of searches for the "winning" node by one.
366-
// Note that this doesn't actually affect the "real" counts, instead
367-
// it only affects the captured node search counts, which is
368-
// captured once for each query in TransportSearchAction
369-
nodeSearchCounts.compute(minNodeId, (id, conns) -> conns == null ? 1 : conns + 1);
370-
}
347+
ShardRouting minShard = sortedShards.get(0);
348+
// If the winning shard is not started we are ranking initializing
349+
// shards, don't bother to do adjustments
350+
if (minShard.started()) {
351+
String minNodeId = minShard.currentNodeId();
352+
Optional<ResponseCollectorService.ComputedNodeStats> maybeMinStats = nodeStats.get(minNodeId);
353+
if (maybeMinStats.isPresent()) {
354+
adjustStats(collector, nodeStats, minNodeId, maybeMinStats.get());
355+
// Increase the number of searches for the "winning" node by one.
356+
// Note that this doesn't actually affect the "real" counts, instead
357+
// it only affects the captured node search counts, which is
358+
// captured once for each query in TransportSearchAction
359+
nodeSearchCounts.compute(minNodeId, (id, conns) -> conns == null ? 1 : conns + 1);
371360
}
372361
}
373362

server/src/main/java/org/elasticsearch/cluster/routing/OperationRouting.java

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@
99

1010
package org.elasticsearch.cluster.routing;
1111

12-
import org.apache.lucene.util.CollectionUtil;
1312
import org.elasticsearch.cluster.ProjectState;
1413
import org.elasticsearch.cluster.metadata.IndexMetadata;
1514
import org.elasticsearch.cluster.metadata.ProjectMetadata;
@@ -19,7 +18,6 @@
1918
import org.elasticsearch.common.settings.ClusterSettings;
2019
import org.elasticsearch.common.settings.Setting;
2120
import org.elasticsearch.common.settings.Settings;
22-
import org.elasticsearch.common.util.set.Sets;
2321
import org.elasticsearch.core.Nullable;
2422
import org.elasticsearch.index.IndexNotFoundException;
2523
import org.elasticsearch.index.shard.ShardId;
@@ -110,9 +108,9 @@ public List<ShardIterator> searchShards(
110108
@Nullable ResponseCollectorService collectorService,
111109
@Nullable Map<String, Long> nodeCounts
112110
) {
113-
Set<IndexShardRoutingTable> shards = computeTargetedShards(projectState, concreteIndices, routing);
111+
final Set<IndexShardRoutingTable> shards = computeTargetedShards(projectState, concreteIndices, routing);
114112
DiscoveryNodes nodes = projectState.cluster().nodes();
115-
Set<ShardIterator> set = Sets.newHashSetWithExpectedSize(shards.size());
113+
List<ShardIterator> res = new ArrayList<>(shards.size());
116114
for (IndexShardRoutingTable shard : shards) {
117115
ShardIterator iterator = preferenceActiveShardIterator(
118116
shard,
@@ -123,11 +121,10 @@ public List<ShardIterator> searchShards(
123121
nodeCounts
124122
);
125123
if (iterator != null) {
126-
set.add(ShardIterator.allSearchableShards(iterator));
124+
res.add(ShardIterator.allSearchableShards(iterator));
127125
}
128126
}
129-
List<ShardIterator> res = new ArrayList<>(set);
130-
CollectionUtil.timSort(res);
127+
res.sort(ShardIterator::compareTo);
131128
return res;
132129
}
133130

0 commit comments

Comments
 (0)