Browse Source

Minor RangeAgg optimization (#86935)

* Minor RangeAgg optimization

Shortcut for when we have single values in a range aggregation. This also
targets date histogram aggregations which often are converted to range
aggregations. Add profiling counts for the singleton and non-singleton
code paths.

Closes #84262
tmgordeeva 3 years ago
parent
commit
01a3af0969

+ 6 - 0
docs/changelog/86935.yaml

@@ -0,0 +1,6 @@
+pr: 86935
+summary: Minor `RangeAgg` optimization
+area: Aggregations
+type: enhancement
+issues:
+ - 84262

+ 7 - 1
server/src/internalClusterTest/java/org/elasticsearch/search/profile/aggregation/AggregationProfilerIT.java

@@ -776,7 +776,13 @@ public class AggregationProfilerIT extends ESIntegTestCase {
                     debug,
                     matchesMap().entry("delegate", "RangeAggregator.NoOverlap")
                         .entry("built_buckets", 1)
-                        .entry("delegate_debug", matchesMap().entry("ranges", 1).entry("average_docs_per_range", 10000.0))
+                        .entry(
+                            "delegate_debug",
+                            matchesMap().entry("ranges", 1)
+                                .entry("average_docs_per_range", 10000.0)
+                                .entry("singletons", greaterThan(0))
+                                .entry("non-singletons", 0)
+                        )
                 );
             }
         } finally {

+ 28 - 0
server/src/main/java/org/elasticsearch/search/aggregations/bucket/range/RangeAggregator.java

@@ -15,6 +15,8 @@ import org.elasticsearch.common.io.stream.StreamInput;
 import org.elasticsearch.common.io.stream.StreamOutput;
 import org.elasticsearch.common.io.stream.Writeable;
 import org.elasticsearch.core.CheckedFunction;
+import org.elasticsearch.index.fielddata.FieldData;
+import org.elasticsearch.index.fielddata.NumericDoubleValues;
 import org.elasticsearch.index.fielddata.SortedNumericDoubleValues;
 import org.elasticsearch.index.mapper.DateFieldMapper.DateFieldType;
 import org.elasticsearch.index.mapper.DateFieldMapper.Resolution;
@@ -93,6 +95,16 @@ public abstract class RangeAggregator extends BucketsAggregator {
 
     static final DoubleUnaryOperator IDENTITY = DoubleUnaryOperator.identity();
 
+    /**
+     * Count of ranges with single values.
+     */
+    private int singletonRanges;
+
+    /**
+     * Count of ranges with more than a single value.
+     */
+    private int nonsingletonRanges;
+
     public static class Range implements Writeable, ToXContentObject {
         public static final ParseField KEY_FIELD = new ParseField("key");
         public static final ParseField FROM_FIELD = new ParseField("from");
@@ -565,6 +577,8 @@ public abstract class RangeAggregator extends BucketsAggregator {
         super.collectDebugInfo(add);
         add.accept("ranges", ranges.length);
         add.accept("average_docs_per_range", averageDocsPerRange);
+        add.accept("singletons", singletonRanges);
+        add.accept("non-singletons", nonsingletonRanges);
     }
 
     public static class Unmapped<R extends RangeAggregator.Range> extends NonCollectingAggregator {
@@ -640,6 +654,20 @@ public abstract class RangeAggregator extends BucketsAggregator {
         @Override
         public LeafBucketCollector getLeafCollector(LeafReaderContext ctx, LeafBucketCollector sub) throws IOException {
             final SortedNumericDoubleValues values = ((ValuesSource.Numeric) this.valuesSource).doubleValues(ctx);
+            final NumericDoubleValues singleton = FieldData.unwrapSingleton(values);
+
+            if (singleton != null) {
+                super.singletonRanges++;
+                return new LeafBucketCollectorBase(sub, values) {
+                    @Override
+                    public void collect(int doc, long bucket) throws IOException {
+                        if (singleton.advanceExact(doc)) {
+                            NumericRangeAggregator.this.collect(sub, doc, singleton.doubleValue(), bucket, 0);
+                        }
+                    }
+                };
+            }
+            super.nonsingletonRanges++;
             return new LeafBucketCollectorBase(sub, values) {
                 @Override
                 public void collect(int doc, long bucket) throws IOException {

+ 7 - 1
server/src/test/java/org/elasticsearch/search/aggregations/bucket/histogram/DateHistogramAggregatorTests.java

@@ -993,7 +993,13 @@ public class DateHistogramAggregatorTests extends DateHistogramAggregatorTestCas
                     matchesMap().entry(
                         "d",
                         matchesMap().entry("delegate", "RangeAggregator.NoOverlap")
-                            .entry("delegate_debug", matchesMap().entry("ranges", 2).entry("average_docs_per_range", 5005.0))
+                            .entry(
+                                "delegate_debug",
+                                matchesMap().entry("ranges", 2)
+                                    .entry("average_docs_per_range", 5005.0)
+                                    .entry("singletons", greaterThan(1))
+                                    .entry("non-singletons", 0)
+                            )
                     )
                 );
             },

+ 18 - 2
server/src/test/java/org/elasticsearch/search/aggregations/bucket/range/RangeAggregatorTests.java

@@ -51,6 +51,7 @@ import static org.elasticsearch.test.MapMatcher.assertMap;
 import static org.elasticsearch.test.MapMatcher.matchesMap;
 import static org.hamcrest.Matchers.closeTo;
 import static org.hamcrest.Matchers.equalTo;
+import static org.hamcrest.Matchers.greaterThan;
 import static org.hamcrest.Matchers.hasSize;
 
 public class RangeAggregatorTests extends AggregatorTestCase {
@@ -649,7 +650,16 @@ public class RangeAggregatorTests extends AggregatorTestCase {
                 equalTo(List.of(totalDocs, 0L, 0L))
             );
             assertThat(impl, equalTo(RangeAggregator.NoOverlap.class));
-            assertMap(debug, matchesMap().entry("r", matchesMap().entry("ranges", 3).entry("average_docs_per_range", closeTo(6667, 1))));
+            assertMap(
+                debug,
+                matchesMap().entry(
+                    "r",
+                    matchesMap().entry("ranges", 3)
+                        .entry("average_docs_per_range", closeTo(6667, 1))
+                        .entry("singletons", greaterThan(1))
+                        .entry("non-singletons", 0)
+                )
+            );
         }, new NumberFieldMapper.NumberFieldType(NUMBER_FIELD_NAME, NumberFieldMapper.NumberType.INTEGER));
     }
 
@@ -690,7 +700,13 @@ public class RangeAggregatorTests extends AggregatorTestCase {
                 assertThat(impl, equalTo(RangeAggregator.NoOverlap.class));
                 assertMap(
                     debug,
-                    matchesMap().entry("r", matchesMap().entry("ranges", 3).entry("average_docs_per_range", closeTo(6667, 1)))
+                    matchesMap().entry(
+                        "r",
+                        matchesMap().entry("ranges", 3)
+                            .entry("average_docs_per_range", closeTo(6667, 1))
+                            .entry("singletons", 0)
+                            .entry("non-singletons", greaterThan(1))
+                    )
                 );
             },
             dummyFt,