23
23
import org .elasticsearch .action .ActionListener ;
24
24
import org .elasticsearch .cluster .routing .GroupShardsIterator ;
25
25
import org .elasticsearch .cluster .routing .ShardRouting ;
26
- import org .elasticsearch .search .SearchService ;
26
+ import org .elasticsearch .search .SearchService .CanMatchResponse ;
27
+ import org .elasticsearch .search .builder .SearchSourceBuilder ;
27
28
import org .elasticsearch .search .internal .AliasFilter ;
29
+ import org .elasticsearch .search .sort .FieldSortBuilder ;
30
+ import org .elasticsearch .search .sort .MinAndMax ;
31
+ import org .elasticsearch .search .sort .SortOrder ;
28
32
import org .elasticsearch .transport .Transport ;
29
33
34
+ import java .util .Arrays ;
35
+ import java .util .Comparator ;
36
+ import java .util .List ;
30
37
import java .util .Map ;
38
+ import java .util .Objects ;
31
39
import java .util .Set ;
32
40
import java .util .concurrent .Executor ;
33
41
import java .util .function .BiFunction ;
34
42
import java .util .function .Function ;
43
+ import java .util .stream .Collectors ;
44
+ import java .util .stream .IntStream ;
35
45
import java .util .stream .Stream ;
36
46
37
47
/**
40
50
* from the search. The extra round trip to the search shards is very cheap and is not subject to rejections
41
51
* which allows to fan out to more shards at the same time without running into rejections even if we are hitting a
42
52
* large portion of the clusters indices.
53
+ * This phase can also be used to pre-sort shards based on min/max values in each shard of the provided primary sort.
54
+ * When the query primary sort is perform on a field, this phase extracts the min/max value in each shard and
55
+ * sort them according to the provided order. This can be useful for instance to ensure that shards that contain recent
56
+ * data are executed first when sorting by descending timestamp.
43
57
*/
44
- final class CanMatchPreFilterSearchPhase extends AbstractSearchAsyncAction <SearchService . CanMatchResponse > {
58
+ final class CanMatchPreFilterSearchPhase extends AbstractSearchAsyncAction <CanMatchResponse > {
45
59
46
60
private final Function <GroupShardsIterator <SearchShardIterator >, SearchPhase > phaseFactory ;
47
61
private final GroupShardsIterator <SearchShardIterator > shardsIts ;
@@ -58,26 +72,26 @@ final class CanMatchPreFilterSearchPhase extends AbstractSearchAsyncAction<Searc
58
72
//We set max concurrent shard requests to the number of shards so no throttling happens for can_match requests
59
73
super ("can_match" , logger , searchTransportService , nodeIdToConnection , aliasFilter , concreteIndexBoosts , indexRoutings ,
60
74
executor , request , listener , shardsIts , timeProvider , clusterStateVersion , task ,
61
- new BitSetSearchPhaseResults (shardsIts .size ()), shardsIts .size (), clusters );
75
+ new CanMatchSearchPhaseResults (shardsIts .size ()), shardsIts .size (), clusters );
62
76
this .phaseFactory = phaseFactory ;
63
77
this .shardsIts = shardsIts ;
64
78
}
65
79
66
80
@ Override
67
81
protected void executePhaseOnShard (SearchShardIterator shardIt , ShardRouting shard ,
68
- SearchActionListener <SearchService . CanMatchResponse > listener ) {
82
+ SearchActionListener <CanMatchResponse > listener ) {
69
83
getSearchTransport ().sendCanMatch (getConnection (shardIt .getClusterAlias (), shard .currentNodeId ()),
70
84
buildShardSearchRequest (shardIt ), getTask (), listener );
71
85
}
72
86
73
87
@ Override
74
- protected SearchPhase getNextPhase (SearchPhaseResults <SearchService . CanMatchResponse > results ,
88
+ protected SearchPhase getNextPhase (SearchPhaseResults <CanMatchResponse > results ,
75
89
SearchPhaseContext context ) {
76
90
77
- return phaseFactory .apply (getIterator ((BitSetSearchPhaseResults ) results , shardsIts ));
91
+ return phaseFactory .apply (getIterator ((CanMatchSearchPhaseResults ) results , shardsIts ));
78
92
}
79
93
80
- private GroupShardsIterator <SearchShardIterator > getIterator (BitSetSearchPhaseResults results ,
94
+ private GroupShardsIterator <SearchShardIterator > getIterator (CanMatchSearchPhaseResults results ,
81
95
GroupShardsIterator <SearchShardIterator > shardsIts ) {
82
96
int cardinality = results .getNumPossibleMatches ();
83
97
FixedBitSet possibleMatches = results .getPossibleMatches ();
@@ -86,6 +100,7 @@ private GroupShardsIterator<SearchShardIterator> getIterator(BitSetSearchPhaseRe
86
100
// to produce a valid search result with all the aggs etc.
87
101
possibleMatches .set (0 );
88
102
}
103
+ SearchSourceBuilder source = getRequest ().source ();
89
104
int i = 0 ;
90
105
for (SearchShardIterator iter : shardsIts ) {
91
106
if (possibleMatches .get (i ++)) {
@@ -94,24 +109,48 @@ private GroupShardsIterator<SearchShardIterator> getIterator(BitSetSearchPhaseRe
94
109
iter .resetAndSkip ();
95
110
}
96
111
}
97
- return shardsIts ;
112
+ if (shouldSortShards (results .minAndMaxes ) == false ) {
113
+ return shardsIts ;
114
+ }
115
+ FieldSortBuilder fieldSort = FieldSortBuilder .getPrimaryFieldSortOrNull (source );
116
+ return new GroupShardsIterator <>(sortShards (shardsIts , results .minAndMaxes , fieldSort .order ()), false );
98
117
}
99
118
100
- private static final class BitSetSearchPhaseResults extends SearchPhaseResults <SearchService .CanMatchResponse > {
119
+ private static List <SearchShardIterator > sortShards (GroupShardsIterator <SearchShardIterator > shardsIts ,
120
+ MinAndMax <?>[] minAndMaxes ,
121
+ SortOrder order ) {
122
+ return IntStream .range (0 , shardsIts .size ())
123
+ .boxed ()
124
+ .sorted (shardComparator (shardsIts , minAndMaxes , order ))
125
+ .map (ord -> shardsIts .get (ord ))
126
+ .collect (Collectors .toList ());
127
+ }
101
128
129
+ private static boolean shouldSortShards (MinAndMax <?>[] minAndMaxes ) {
130
+ return Arrays .stream (minAndMaxes ).anyMatch (Objects ::nonNull );
131
+ }
132
+
133
+ private static Comparator <Integer > shardComparator (GroupShardsIterator <SearchShardIterator > shardsIts ,
134
+ MinAndMax <?>[] minAndMaxes ,
135
+ SortOrder order ) {
136
+ final Comparator <Integer > comparator = Comparator .comparing (index -> minAndMaxes [index ], MinAndMax .getComparator (order ));
137
+ return comparator .thenComparing (index -> shardsIts .get (index ).shardId ());
138
+ }
139
+
140
+ private static final class CanMatchSearchPhaseResults extends SearchPhaseResults <CanMatchResponse > {
102
141
private final FixedBitSet possibleMatches ;
142
+ private final MinAndMax <?>[] minAndMaxes ;
103
143
private int numPossibleMatches ;
104
144
105
- BitSetSearchPhaseResults (int size ) {
145
+ CanMatchSearchPhaseResults (int size ) {
106
146
super (size );
107
147
possibleMatches = new FixedBitSet (size );
148
+ minAndMaxes = new MinAndMax [size ];
108
149
}
109
150
110
151
@ Override
111
- void consumeResult (SearchService .CanMatchResponse result ) {
112
- if (result .canMatch ()) {
113
- consumeShardFailure (result .getShardIndex ());
114
- }
152
+ void consumeResult (CanMatchResponse result ) {
153
+ consumeResult (result .getShardIndex (), result .canMatch (), result .minAndMax ());
115
154
}
116
155
117
156
@ Override
@@ -120,12 +159,18 @@ boolean hasResult(int shardIndex) {
120
159
}
121
160
122
161
@ Override
123
- synchronized void consumeShardFailure (int shardIndex ) {
162
+ void consumeShardFailure (int shardIndex ) {
124
163
// we have to carry over shard failures in order to account for them in the response.
125
- possibleMatches .set (shardIndex );
126
- numPossibleMatches ++;
164
+ consumeResult (shardIndex , true , null );
127
165
}
128
166
167
+ synchronized void consumeResult (int shardIndex , boolean canMatch , MinAndMax <?> minAndMax ) {
168
+ if (canMatch ) {
169
+ possibleMatches .set (shardIndex );
170
+ numPossibleMatches ++;
171
+ }
172
+ minAndMaxes [shardIndex ] = minAndMax ;
173
+ }
129
174
130
175
synchronized int getNumPossibleMatches () {
131
176
return numPossibleMatches ;
@@ -136,7 +181,7 @@ synchronized FixedBitSet getPossibleMatches() {
136
181
}
137
182
138
183
@ Override
139
- Stream <SearchService . CanMatchResponse > getSuccessfulResults () {
184
+ Stream <CanMatchResponse > getSuccessfulResults () {
140
185
return Stream .empty ();
141
186
}
142
187
}
0 commit comments