Skip to content

Commit 075a7bf

Browse files
Add "optimized" as a sort parameter
1 parent 105bc74 commit 075a7bf

File tree

7 files changed

+117
-9
lines changed

7 files changed

+117
-9
lines changed

docs/reference/search/request/sort.asciidoc

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -236,6 +236,54 @@ POST /index_long,index_double/_search
236236
To avoid overflow, the conversion to `date_nanos` cannot be applied on dates before
237237
1970 and after 2262 as nanoseconds are represented as longs.
238238

239+
240+
==== Sort Optimization
241+
experimental[]
242+
243+
Sort on `long`, `date` and `date_nanos` fields can be optionally optimized.
244+
The optimization instead of sorting all field values, will underneath use the
245+
<<query-dsl-distance-feature-query, distance_feature>>
246+
query that can efficiently skip non-competitive hits.
247+
For the optimization to work, the field must be indexed and
248+
doc-values must be activated.
249+
Depending on the data this could bring significant speedups, while
250+
some performance regression is also possible.
251+
252+
The optimization is disabled by default. To enable optimization,
253+
set the `optimized` parameter to `true`.
254+
This example shows how to enable optimization on a `long` field:
255+
256+
[source,js]
257+
--------------------------------------------------
258+
PUT /index_long
259+
{
260+
"mappings": {
261+
"properties": {
262+
"my_long": { "type": "long" }
263+
}
264+
}
265+
}
266+
--------------------------------------------------
267+
// CONSOLE
268+
269+
[source,js]
270+
--------------------------------------------------
271+
POST /index_long/_search
272+
{
273+
"sort" : [
274+
{
275+
"my_long" : {
276+
"optimized": true,
277+
"order" : "asc"
278+
}
279+
}
280+
]
281+
}
282+
--------------------------------------------------
283+
// CONSOLE
284+
// TEST[continued]
285+
286+
239287
[[nested-sorting]]
240288
==== Sorting within nested objects.
241289

server/src/main/java/org/elasticsearch/search/query/QueryPhase.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -356,6 +356,8 @@ private static Query tryRewriteLongSort(SearchContext searchContext, IndexReader
356356
if (searchContext.collapse() != null) return null;
357357
if (searchContext.trackScores()) return null;
358358
if (searchContext.aggregations() != null) return null;
359+
if (searchContext.sort().optimized == null) return null;
360+
if (searchContext.sort().optimized[0] == false) return null;
359361
Sort sort = searchContext.sort().sort;
360362
SortField sortField = sort.getSort()[0];
361363
if (SortField.Type.LONG.equals(IndexSortConfig.getSortFieldType(sortField)) == false) return null;

server/src/main/java/org/elasticsearch/search/sort/FieldSortBuilder.java

Lines changed: 32 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ public class FieldSortBuilder extends SortBuilder<FieldSortBuilder> {
6060
public static final ParseField SORT_MODE = new ParseField("mode");
6161
public static final ParseField UNMAPPED_TYPE = new ParseField("unmapped_type");
6262
public static final ParseField NUMERIC_TYPE = new ParseField("numeric_type");
63+
public static final ParseField OPTIMIZED_FIELD = new ParseField("optimized");
6364

6465
/**
6566
* special field name to sort by index order
@@ -86,6 +87,8 @@ public class FieldSortBuilder extends SortBuilder<FieldSortBuilder> {
8687

8788
private NestedSortBuilder nestedSort;
8889

90+
private boolean optimized = false;
91+
8992
/** Copy constructor. */
9093
public FieldSortBuilder(FieldSortBuilder template) {
9194
this(template.fieldName);
@@ -101,6 +104,7 @@ public FieldSortBuilder(FieldSortBuilder template) {
101104
this.setNestedSort(template.getNestedSort());
102105
}
103106
this.numericType = template.numericType;
107+
this.optimized(template.optimized);
104108
}
105109

106110
/**
@@ -131,6 +135,9 @@ public FieldSortBuilder(StreamInput in) throws IOException {
131135
if (in.getVersion().onOrAfter(Version.V_7_2_0)) {
132136
numericType = in.readOptionalString();
133137
}
138+
if (in.getVersion().onOrAfter(Version.V_8_0_0)) {
139+
optimized = in.readOptionalBoolean();
140+
}
134141
}
135142

136143
@Override
@@ -146,6 +153,9 @@ public void writeTo(StreamOutput out) throws IOException {
146153
if (out.getVersion().onOrAfter(Version.V_7_2_0)) {
147154
out.writeOptionalString(numericType);
148155
}
156+
if (out.getVersion().onOrAfter(Version.V_8_0_0)) {
157+
out.writeOptionalBoolean(optimized);
158+
}
149159
}
150160

151161
/** Returns the document field this sort should be based on. */
@@ -316,6 +326,20 @@ public FieldSortBuilder setNumericType(String numericType) {
316326
return this;
317327
}
318328

329+
/**
330+
* Sets if the sort should be optimized
331+
* Applicable only for numeric long, date and date_nano fields.
332+
*/
333+
public FieldSortBuilder optimized(boolean optimized) {
334+
this.optimized = optimized;
335+
return this;
336+
}
337+
338+
/** Returns if the sort is optimized. */
339+
public boolean optimized() {
340+
return optimized;
341+
}
342+
319343
@Override
320344
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
321345
builder.startObject();
@@ -342,6 +366,9 @@ public XContentBuilder toXContent(XContentBuilder builder, Params params) throws
342366
if (numericType != null) {
343367
builder.field(NUMERIC_TYPE.getPreferredName(), numericType);
344368
}
369+
if (optimized) {
370+
builder.field(OPTIMIZED_FIELD.getPreferredName(), optimized);
371+
}
345372
builder.endObject();
346373
builder.endObject();
347374
return builder;
@@ -425,7 +452,7 @@ public SortFieldAndFormat build(QueryShardContext context) throws IOException {
425452
} else {
426453
field = fieldData.sortField(missing, localSortMode, nested, reverse);
427454
}
428-
return new SortFieldAndFormat(field, fieldType.docValueFormat(null, null));
455+
return new SortFieldAndFormat(field, fieldType.docValueFormat(null, null), optimized);
429456
}
430457
}
431458

@@ -444,13 +471,14 @@ public boolean equals(Object other) {
444471
&& Objects.equals(this.nestedPath, builder.nestedPath) && Objects.equals(this.missing, builder.missing)
445472
&& Objects.equals(this.order, builder.order) && Objects.equals(this.sortMode, builder.sortMode)
446473
&& Objects.equals(this.unmappedType, builder.unmappedType) && Objects.equals(this.nestedSort, builder.nestedSort))
447-
&& Objects.equals(this.numericType, builder.numericType);
474+
&& Objects.equals(this.numericType, builder.numericType)
475+
&& this.optimized == builder.optimized;
448476
}
449477

450478
@Override
451479
public int hashCode() {
452480
return Objects.hash(this.fieldName, this.nestedFilter, this.nestedPath, this.nestedSort, this.missing, this.order, this.sortMode,
453-
this.unmappedType, this.numericType);
481+
this.unmappedType, this.numericType, this.optimized);
454482
}
455483

456484
@Override
@@ -488,6 +516,7 @@ public static FieldSortBuilder fromXContent(XContentParser parser, String fieldN
488516
}, NESTED_FILTER_FIELD);
489517
PARSER.declareObject(FieldSortBuilder::setNestedSort, (p, c) -> NestedSortBuilder.fromXContent(p), NESTED_FIELD);
490518
PARSER.declareString((b, v) -> b.setNumericType(v), NUMERIC_TYPE);
519+
PARSER.declareBoolean(FieldSortBuilder::optimized, OPTIMIZED_FIELD);
491520
}
492521

493522
@Override

server/src/main/java/org/elasticsearch/search/sort/SortAndFormats.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ public final class SortAndFormats {
2525

2626
public final Sort sort;
2727
public final DocValueFormat[] formats;
28+
public final boolean[] optimized;
2829

2930
public SortAndFormats(Sort sort, DocValueFormat[] formats) {
3031
if (sort.getSort().length != formats.length) {
@@ -33,6 +34,17 @@ public SortAndFormats(Sort sort, DocValueFormat[] formats) {
3334
}
3435
this.sort = sort;
3536
this.formats = formats;
37+
this.optimized = null;
38+
}
39+
40+
public SortAndFormats(Sort sort, DocValueFormat[] formats, boolean[] optimized) {
41+
if (sort.getSort().length != formats.length) {
42+
throw new IllegalArgumentException("Number of sort field mismatch: "
43+
+ sort.getSort().length + " != " + formats.length);
44+
}
45+
this.sort = sort;
46+
this.formats = formats;
47+
this.optimized = optimized;
3648
}
3749

3850
}

server/src/main/java/org/elasticsearch/search/sort/SortBuilder.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -143,10 +143,13 @@ private static void parseCompoundSortField(XContentParser parser, List<SortBuild
143143
public static Optional<SortAndFormats> buildSort(List<SortBuilder<?>> sortBuilders, QueryShardContext context) throws IOException {
144144
List<SortField> sortFields = new ArrayList<>(sortBuilders.size());
145145
List<DocValueFormat> sortFormats = new ArrayList<>(sortBuilders.size());
146+
boolean[] optimized = new boolean[sortBuilders.size()];
147+
int i = 0;
146148
for (SortBuilder<?> builder : sortBuilders) {
147149
SortFieldAndFormat sf = builder.build(context);
148150
sortFields.add(sf.field);
149151
sortFormats.add(sf.format);
152+
optimized[i++] = sf.optimized;
150153
}
151154
if (!sortFields.isEmpty()) {
152155
// optimize if we just sort on score non reversed, we don't really
@@ -165,7 +168,8 @@ public static Optional<SortAndFormats> buildSort(List<SortBuilder<?>> sortBuilde
165168
if (sort) {
166169
return Optional.of(new SortAndFormats(
167170
new Sort(sortFields.toArray(new SortField[sortFields.size()])),
168-
sortFormats.toArray(new DocValueFormat[sortFormats.size()])));
171+
sortFormats.toArray(new DocValueFormat[sortFormats.size()]),
172+
optimized));
169173
}
170174
}
171175
return Optional.empty();

server/src/main/java/org/elasticsearch/search/sort/SortFieldAndFormat.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,18 @@ public final class SortFieldAndFormat {
2727

2828
public final SortField field;
2929
public final DocValueFormat format;
30+
public final boolean optimized;
3031

3132
public SortFieldAndFormat(SortField field, DocValueFormat format) {
3233
this.field = Objects.requireNonNull(field);
3334
this.format = Objects.requireNonNull(format);
35+
this.optimized = false;
36+
}
37+
38+
public SortFieldAndFormat(SortField field, DocValueFormat format, boolean optimized) {
39+
this.field = Objects.requireNonNull(field);
40+
this.format = Objects.requireNonNull(format);
41+
this.optimized = optimized;
3442
}
3543

3644
}

server/src/test/java/org/elasticsearch/search/query/QueryPhaseTests.java

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -673,7 +673,8 @@ public void testNumericLongOrDateSortOptimization() throws Exception {
673673
final SortField sortFieldLong = new SortField(fieldNameLong, SortField.Type.LONG);
674674
sortFieldLong.setMissingValue(Long.MAX_VALUE);
675675
final Sort longSort = new Sort(sortFieldLong);
676-
SortAndFormats sortAndFormats = new SortAndFormats(longSort, new DocValueFormat[]{DocValueFormat.RAW});
676+
boolean[] optimized = new boolean[]{true};
677+
SortAndFormats sortAndFormats = new SortAndFormats(longSort, new DocValueFormat[]{DocValueFormat.RAW}, optimized);
677678
searchContext.sort(sortAndFormats);
678679
searchContext.parsedQuery(new ParsedQuery(new MatchAllDocsQuery()));
679680
searchContext.setTask(new SearchTask(123L, "", "", "", null, Collections.emptyMap()));
@@ -685,22 +686,25 @@ public void testNumericLongOrDateSortOptimization() throws Exception {
685686
final SortField sortFieldDate = new SortField(fieldNameDate, SortField.Type.LONG);
686687
DocValueFormat dateFormat = fieldTypeDate.docValueFormat(null, null);
687688
final Sort longDateSort = new Sort(sortFieldLong, sortFieldDate);
688-
sortAndFormats = new SortAndFormats(longDateSort, new DocValueFormat[]{DocValueFormat.RAW, dateFormat});
689+
optimized = new boolean[]{true, false};
690+
sortAndFormats = new SortAndFormats(longDateSort, new DocValueFormat[]{DocValueFormat.RAW, dateFormat}, optimized);
689691
searchContext.sort(sortAndFormats);
690692
QueryPhase.execute(searchContext, searcher, checkCancelled -> {});
691693
assertSortResults(searchContext.queryResult().topDocs().topDocs, (long) numDocs, true);
692694

693695
// 3. Test a sort on date field
694696
sortFieldDate.setMissingValue(Long.MAX_VALUE);
695697
final Sort dateSort = new Sort(sortFieldDate);
696-
sortAndFormats = new SortAndFormats(dateSort, new DocValueFormat[]{dateFormat});
698+
optimized = new boolean[]{true};
699+
sortAndFormats = new SortAndFormats(dateSort, new DocValueFormat[]{dateFormat}, optimized);
697700
searchContext.sort(sortAndFormats);
698701
QueryPhase.execute(searchContext, searcher, checkCancelled -> {});
699702
assertSortResults(searchContext.queryResult().topDocs().topDocs, (long) numDocs, false);
700703

701704
// 4. Test a sort on date field + long field
702705
final Sort dateLongSort = new Sort(sortFieldDate, sortFieldLong);
703-
sortAndFormats = new SortAndFormats(dateLongSort, new DocValueFormat[]{dateFormat, DocValueFormat.RAW});
706+
optimized = new boolean[]{true, false};
707+
sortAndFormats = new SortAndFormats(dateLongSort, new DocValueFormat[]{dateFormat, DocValueFormat.RAW}, optimized);
704708
searchContext.sort(sortAndFormats);
705709
QueryPhase.execute(searchContext, searcher, checkCancelled -> {});
706710
assertSortResults(searchContext.queryResult().topDocs().topDocs, (long) numDocs, true);
@@ -785,7 +789,8 @@ public void testNumericLongSortOptimizationDocsHaveTheSameValue() throws Excepti
785789
final SortField sortFieldLong = new SortField(fieldNameLong, SortField.Type.LONG);
786790
sortFieldLong.setMissingValue(Long.MAX_VALUE);
787791
final Sort longSort = new Sort(sortFieldLong);
788-
SortAndFormats sortAndFormats = new SortAndFormats(longSort, new DocValueFormat[]{DocValueFormat.RAW});
792+
boolean[] optimized = new boolean[]{true};
793+
SortAndFormats sortAndFormats = new SortAndFormats(longSort, new DocValueFormat[]{DocValueFormat.RAW}, optimized);
789794
searchContext.sort(sortAndFormats);
790795
searchContext.parsedQuery(new ParsedQuery(new MatchAllDocsQuery()));
791796
searchContext.setTask(new SearchTask(123L, "", "", "", null, Collections.emptyMap()));

0 commit comments

Comments
 (0)