From 9ee6a1918798f11e5548e7c51b27a3a68cee7735 Mon Sep 17 00:00:00 2001 From: Mark Tozzi Date: Tue, 5 Jul 2022 09:11:22 -0400 Subject: [PATCH] Add ability to select execution mode for cardinality aggregation (#87704) Plumbs through a new parameter for the cardinality aggregation, to allow configuring the execution mode. This can have significant impacts on speed and memory usage. This PR exposes three collection modes and two heuristics that we can tune going forward. All of these are treated as hints and can be silently ignored, e.g. if not applicable to the given field type. I've change the default behavior to optimize for time, which potentially uses more memory. Users can override this for the old behavior if needed. --- .../metrics/cardinality-aggregation.asciidoc | 21 +++ .../170_cardinality_metric.yml | 132 ++++++++++++++++++ .../bucket/terms/TermsAggregatorFactory.java | 2 +- .../CardinalityAggregationBuilder.java | 39 +++++- .../metrics/CardinalityAggregator.java | 16 ++- .../metrics/CardinalityAggregatorFactory.java | 115 ++++++++++++++- .../CardinalityAggregatorSupplier.java | 1 + .../metrics/CardinalityAggregatorTests.java | 7 +- .../metrics/CardinalityTests.java | 7 + .../xpack/spatial/SpatialPlugin.java | 16 ++- x-pack/qa/runtime-fields/build.gradle | 5 + 11 files changed, 344 insertions(+), 17 deletions(-) diff --git a/docs/reference/aggregations/metrics/cardinality-aggregation.asciidoc b/docs/reference/aggregations/metrics/cardinality-aggregation.asciidoc index 0dcad9709858..48c45c574ae9 100644 --- a/docs/reference/aggregations/metrics/cardinality-aggregation.asciidoc +++ b/docs/reference/aggregations/metrics/cardinality-aggregation.asciidoc @@ -237,3 +237,24 @@ POST /sales/_search?size=0 -------------------------------------------------- // TEST[setup:sales] <1> Documents without a value in the `tag` field will fall into the same bucket as documents that have the value `N/A`. +==== Execution Hint + +There are different mechanisms by which cardinality aggregations can be executed: + + - by using field values directly (`direct`) + - by using global ordinals of the field and resolving those values after + finishing a shard (`global_ordinals`) + - by using segment ordinal values and resolving those values after each + segment (`segment_ordinals`) + +Additionally, there are two "heuristic based" modes. These modes will cause +Elasticsearch to use some data about the state of the index to choose an +appropriate execution method. The two heuristics are: + - `save_time_heuristic` - this is the default in Elasticsearch 8.4 and later. + - `save_memory_heuristic` - this was the default in Elasticsearch 8.3 and + earlier + +When not specified, Elasticsearch will apply a heuristic to chose the +appropriate mode. Also note that some data (i.e. non-ordinal fields), `direct` +is the only option, and the hint will be ignored in these cases. Generally +speaking, it should not be necessary to set this value. diff --git a/rest-api-spec/src/yamlRestTest/resources/rest-api-spec/test/search.aggregation/170_cardinality_metric.yml b/rest-api-spec/src/yamlRestTest/resources/rest-api-spec/test/search.aggregation/170_cardinality_metric.yml index f35c85034f0b..7f1fc24ef9fb 100644 --- a/rest-api-spec/src/yamlRestTest/resources/rest-api-spec/test/search.aggregation/170_cardinality_metric.yml +++ b/rest-api-spec/src/yamlRestTest/resources/rest-api-spec/test/search.aggregation/170_cardinality_metric.yml @@ -286,3 +286,135 @@ setup: - gt: { profile.shards.0.aggregations.0.breakdown.collect: 0 } - gt: { profile.shards.0.aggregations.0.breakdown.build_aggregation: 0 } - gt: { profile.shards.0.aggregations.0.breakdown.post_collection: 0 } +--- +"profiler string save memory hint": + - skip: + version: " - 8.3.99" + reason: execution hints introduced in 8.4.0 + - do: + search: + body: + profile: true + size: 0 + aggs: + distinct_string: + cardinality: + field: string_field + execution_hint: save_memory_heuristic + - match: { aggregations.distinct_string.value: 1 } + - match: { profile.shards.0.aggregations.0.type: "GlobalOrdCardinalityAggregator" } + - gt: { profile.shards.0.aggregations.0.breakdown.initialize: 0 } + - gt: { profile.shards.0.aggregations.0.breakdown.build_leaf_collector: 0 } + - gt: { profile.shards.0.aggregations.0.breakdown.collect: 0 } + - gt: { profile.shards.0.aggregations.0.breakdown.build_aggregation: 0 } + - gt: { profile.shards.0.aggregations.0.breakdown.post_collection: 0 } +--- +"profiler string save time hint": + - skip: + version: " - 8.3.99" + reason: execution hints introduced in 8.4.0 + - do: + search: + body: + profile: true + size: 0 + aggs: + distinct_string: + cardinality: + field: string_field + execution_hint: save_time_heuristic + - match: { aggregations.distinct_string.value: 1 } + - match: { profile.shards.0.aggregations.0.type: "GlobalOrdCardinalityAggregator" } + - gt: { profile.shards.0.aggregations.0.breakdown.initialize: 0 } + - gt: { profile.shards.0.aggregations.0.breakdown.build_leaf_collector: 0 } + - gt: { profile.shards.0.aggregations.0.breakdown.collect: 0 } + - gt: { profile.shards.0.aggregations.0.breakdown.build_aggregation: 0 } + - gt: { profile.shards.0.aggregations.0.breakdown.post_collection: 0 } +--- +"profiler string global ords hint": + - skip: + version: " - 8.3.99" + reason: execution hints introduced in 8.4.0 + - do: + search: + body: + profile: true + size: 0 + aggs: + distinct_string: + cardinality: + field: string_field + execution_hint: global_ordinals + - match: { aggregations.distinct_string.value: 1 } + - match: { profile.shards.0.aggregations.0.type: "GlobalOrdCardinalityAggregator" } + - gt: { profile.shards.0.aggregations.0.breakdown.initialize: 0 } + - gt: { profile.shards.0.aggregations.0.breakdown.build_leaf_collector: 0 } + - gt: { profile.shards.0.aggregations.0.breakdown.collect: 0 } + - gt: { profile.shards.0.aggregations.0.breakdown.build_aggregation: 0 } + - gt: { profile.shards.0.aggregations.0.breakdown.post_collection: 0 } +--- +"profiler string segment ords hint": + - skip: + version: " - 8.3.99" + reason: execution hints introduced in 8.4.0 + - do: + search: + body: + profile: true + size: 0 + aggs: + distinct_string: + cardinality: + field: string_field + execution_hint: segment_ordinals + - match: { aggregations.distinct_string.value: 1 } + - match: { profile.shards.0.aggregations.0.type: "CardinalityAggregator" } + - gt: { profile.shards.0.aggregations.0.breakdown.initialize: 0 } + - gt: { profile.shards.0.aggregations.0.breakdown.build_leaf_collector: 0 } + - gt: { profile.shards.0.aggregations.0.breakdown.collect: 0 } + - gt: { profile.shards.0.aggregations.0.breakdown.build_aggregation: 0 } + - gt: { profile.shards.0.aggregations.0.breakdown.post_collection: 0 } + - gt: { profile.shards.0.aggregations.0.debug.ordinals_collectors_used: 0 } + - match: { profile.shards.0.aggregations.0.debug.ordinals_collectors_overhead_too_high: 0 } + - match: { profile.shards.0.aggregations.0.debug.string_hashing_collectors_used: 0 } +--- +"profiler string direct hint": + - skip: + version: " - 8.3.99" + reason: execution hints introduced in 8.4.0 + - do: + search: + body: + profile: true + size: 0 + aggs: + distinct_string: + cardinality: + field: string_field + execution_hint: direct + - match: { aggregations.distinct_string.value: 1 } + - match: { profile.shards.0.aggregations.0.type: "CardinalityAggregator" } + - gt: { profile.shards.0.aggregations.0.breakdown.initialize: 0 } + - gt: { profile.shards.0.aggregations.0.breakdown.build_leaf_collector: 0 } + - gt: { profile.shards.0.aggregations.0.breakdown.collect: 0 } + - gt: { profile.shards.0.aggregations.0.breakdown.build_aggregation: 0 } + - gt: { profile.shards.0.aggregations.0.breakdown.post_collection: 0 } + - match: { profile.shards.0.aggregations.0.debug.ordinals_collectors_used: 0 } + - match: { profile.shards.0.aggregations.0.debug.ordinals_collectors_overhead_too_high: 0 } + - gt: { profile.shards.0.aggregations.0.debug.string_hashing_collectors_used: 0 } +--- +"invalid execution hint throws": + - skip: + version: " - 8.3.99" + reason: execution hints introduced in 8.4.0 + - do: + catch: /Invalid execution mode for cardinality aggregation/ + search: + body: + profile: true + size: 0 + aggs: + distinct_string: + cardinality: + field: string_field + execution_hint: bogus diff --git a/server/src/main/java/org/elasticsearch/search/aggregations/bucket/terms/TermsAggregatorFactory.java b/server/src/main/java/org/elasticsearch/search/aggregations/bucket/terms/TermsAggregatorFactory.java index 955bb256266a..904d8b72d70d 100644 --- a/server/src/main/java/org/elasticsearch/search/aggregations/bucket/terms/TermsAggregatorFactory.java +++ b/server/src/main/java/org/elasticsearch/search/aggregations/bucket/terms/TermsAggregatorFactory.java @@ -108,7 +108,7 @@ public class TermsAggregatorFactory extends ValuesSourceAggregatorFactory { execution = ExecutionMode.fromString(executionHint); } // In some cases, using ordinals is just not supported: override it - if (valuesSource instanceof ValuesSource.Bytes.WithOrdinals == false) { + if (valuesSource.hasOrdinals() == false) { execution = ExecutionMode.MAP; } if (execution == null) { diff --git a/server/src/main/java/org/elasticsearch/search/aggregations/metrics/CardinalityAggregationBuilder.java b/server/src/main/java/org/elasticsearch/search/aggregations/metrics/CardinalityAggregationBuilder.java index 30aff7017866..db3ea3a0f62e 100644 --- a/server/src/main/java/org/elasticsearch/search/aggregations/metrics/CardinalityAggregationBuilder.java +++ b/server/src/main/java/org/elasticsearch/search/aggregations/metrics/CardinalityAggregationBuilder.java @@ -39,6 +39,7 @@ public final class CardinalityAggregationBuilder extends ValuesSourceAggregation private static final ParseField REHASH = new ParseField("rehash").withAllDeprecated("no replacement - values will always be rehashed"); public static final ParseField PRECISION_THRESHOLD_FIELD = new ParseField("precision_threshold"); + public static final ParseField EXECUTION_HINT_FIELD_NAME = new ParseField("execution_hint"); public static final ObjectParser PARSER = ObjectParser.fromBuilder( NAME, @@ -48,6 +49,7 @@ public final class CardinalityAggregationBuilder extends ValuesSourceAggregation ValuesSourceAggregationBuilder.declareFields(PARSER, true, false, false); PARSER.declareLong(CardinalityAggregationBuilder::precisionThreshold, CardinalityAggregationBuilder.PRECISION_THRESHOLD_FIELD); PARSER.declareLong((b, v) -> {/*ignore*/}, REHASH); + PARSER.declareString(CardinalityAggregationBuilder::executionHint, EXECUTION_HINT_FIELD_NAME); } public static void registerAggregators(ValuesSourceRegistry.Builder builder) { @@ -56,6 +58,8 @@ public final class CardinalityAggregationBuilder extends ValuesSourceAggregation private Long precisionThreshold = null; + private String executionHint = null; + public CardinalityAggregationBuilder(String name) { super(name); } @@ -67,6 +71,7 @@ public final class CardinalityAggregationBuilder extends ValuesSourceAggregation ) { super(clone, factoriesBuilder, metadata); this.precisionThreshold = clone.precisionThreshold; + this.executionHint = clone.executionHint; } @Override @@ -82,6 +87,9 @@ public final class CardinalityAggregationBuilder extends ValuesSourceAggregation if (in.readBoolean()) { precisionThreshold = in.readLong(); } + if (in.getVersion().onOrAfter(Version.V_8_4_0)) { + executionHint = in.readOptionalString(); + } } @Override @@ -96,6 +104,9 @@ public final class CardinalityAggregationBuilder extends ValuesSourceAggregation if (hasPrecisionThreshold) { out.writeLong(precisionThreshold); } + if (out.getVersion().onOrAfter(Version.V_8_4_0)) { + out.writeOptionalString(executionHint); + } } @Override @@ -126,6 +137,26 @@ public final class CardinalityAggregationBuilder extends ValuesSourceAggregation return precisionThreshold; } + /** + * Get the execution hint. This is an optional user specified hint that + * will be used to decide on the specific collection algorithm. Since this + * is a hint, the implementation may choose to ignore it (typically when + * the specified method is not applicable to the given field type) + */ + public String ExecutionHint() { + return executionHint; + } + + /** + * Set the execution hint. This is an optional user specified hint that + * will be used to decide on the specific collection algorithm. Since this + * is a hint, the implementation may choose to ignore it (typically when + * the specified method is not applicable to the given field type) + */ + public void executionHint(String executionHint) { + this.executionHint = executionHint; + } + @Override protected CardinalityAggregatorFactory innerBuild( AggregationContext context, @@ -139,6 +170,7 @@ public final class CardinalityAggregationBuilder extends ValuesSourceAggregation name, config, precisionThreshold, + executionHint, context, parent, subFactoriesBuilder, @@ -152,12 +184,15 @@ public final class CardinalityAggregationBuilder extends ValuesSourceAggregation if (precisionThreshold != null) { builder.field(PRECISION_THRESHOLD_FIELD.getPreferredName(), precisionThreshold); } + if (executionHint != null) { + builder.field(EXECUTION_HINT_FIELD_NAME.getPreferredName(), executionHint); + } return builder; } @Override public int hashCode() { - return Objects.hash(super.hashCode(), precisionThreshold); + return Objects.hash(super.hashCode(), precisionThreshold, executionHint); } @Override @@ -166,7 +201,7 @@ public final class CardinalityAggregationBuilder extends ValuesSourceAggregation if (obj == null || getClass() != obj.getClass()) return false; if (super.equals(obj) == false) return false; CardinalityAggregationBuilder other = (CardinalityAggregationBuilder) obj; - return Objects.equals(precisionThreshold, other.precisionThreshold); + return Objects.equals(precisionThreshold, other.precisionThreshold) && Objects.equals(executionHint, other.executionHint); } @Override diff --git a/server/src/main/java/org/elasticsearch/search/aggregations/metrics/CardinalityAggregator.java b/server/src/main/java/org/elasticsearch/search/aggregations/metrics/CardinalityAggregator.java index 1b584a3a3f6a..4f5020c9b2f9 100644 --- a/server/src/main/java/org/elasticsearch/search/aggregations/metrics/CardinalityAggregator.java +++ b/server/src/main/java/org/elasticsearch/search/aggregations/metrics/CardinalityAggregator.java @@ -43,6 +43,7 @@ import java.util.function.BiConsumer; public class CardinalityAggregator extends NumericMetricsAggregator.SingleValue { private final int precision; + private final CardinalityAggregatorFactory.ExecutionMode executionMode; private final ValuesSource valuesSource; // Expensive to initialize, so we only initialize it when we have an actual value source @@ -61,6 +62,7 @@ public class CardinalityAggregator extends NumericMetricsAggregator.SingleValue String name, ValuesSourceConfig valuesSourceConfig, int precision, + CardinalityAggregatorFactory.ExecutionMode executionMode, AggregationContext context, Aggregator parent, Map metadata @@ -70,6 +72,7 @@ public class CardinalityAggregator extends NumericMetricsAggregator.SingleValue this.valuesSource = valuesSourceConfig.hasValues() ? valuesSourceConfig.getValuesSource() : null; this.precision = precision; this.counts = valuesSource == null ? null : new HyperLogLogPlusPlus(precision, context.bigArrays(), 1); + this.executionMode = executionMode; } @Override @@ -99,14 +102,15 @@ public class CardinalityAggregator extends NumericMetricsAggregator.SingleValue return new EmptyCollector(); } - final long ordinalsMemoryUsage = OrdinalsCollector.memoryOverhead(maxOrd); - final long countsMemoryUsage = HyperLogLogPlusPlus.memoryUsage(precision); - // only use ordinals if they don't increase memory usage by more than 25% - if (ordinalsMemoryUsage < countsMemoryUsage / 4) { + if (executionMode.useSegmentOrdinals(maxOrd, precision)) { ordinalsCollectorsUsed++; return new OrdinalsCollector(counts, ordinalValues, bigArrays()); } - ordinalsCollectorsOverheadTooHigh++; + + if (executionMode.isHeuristicBased()) { + // if we could have used segment ordinals, and it was our heuristic that made the choice not to, increment the counter + ordinalsCollectorsOverheadTooHigh++; + } } stringHashingCollectorsUsed++; @@ -229,7 +233,7 @@ public class CardinalityAggregator extends NumericMetricsAggregator.SingleValue } - private static class OrdinalsCollector extends Collector { + static class OrdinalsCollector extends Collector { private static final long SHALLOW_FIXEDBITSET_SIZE = RamUsageEstimator.shallowSizeOfInstance(FixedBitSet.class); diff --git a/server/src/main/java/org/elasticsearch/search/aggregations/metrics/CardinalityAggregatorFactory.java b/server/src/main/java/org/elasticsearch/search/aggregations/metrics/CardinalityAggregatorFactory.java index a275a93aaea3..4481254aba36 100644 --- a/server/src/main/java/org/elasticsearch/search/aggregations/metrics/CardinalityAggregatorFactory.java +++ b/server/src/main/java/org/elasticsearch/search/aggregations/metrics/CardinalityAggregatorFactory.java @@ -22,17 +22,118 @@ import org.elasticsearch.search.aggregations.support.ValuesSourceRegistry; import java.io.IOException; import java.util.List; +import java.util.Locale; import java.util.Map; -class CardinalityAggregatorFactory extends ValuesSourceAggregatorFactory { +public class CardinalityAggregatorFactory extends ValuesSourceAggregatorFactory { + + public enum ExecutionMode { + GLOBAL_ORDINALS(false) { + @Override + public boolean useGlobalOrdinals(AggregationContext context, ValuesSource.Bytes.WithOrdinals source, int precision) { + return true; + } + + @Override + public boolean useSegmentOrdinals(long maxOrd, int precision) { + return false; + } + }, + SEGMENT_ORDINALS(false) { + @Override + public boolean useGlobalOrdinals(AggregationContext context, ValuesSource.Bytes.WithOrdinals source, int precision) { + return false; + } + + @Override + public boolean useSegmentOrdinals(long maxOrd, int precision) { + return true; + } + }, + DIRECT(false) { + @Override + public boolean useGlobalOrdinals(AggregationContext context, ValuesSource.Bytes.WithOrdinals source, int precision) { + return false; + } + + @Override + public boolean useSegmentOrdinals(long maxOrd, int precision) { + return false; + } + }, + SAVE_MEMORY_HEURISTIC(true) { + @Override + public boolean useGlobalOrdinals(AggregationContext context, ValuesSource.Bytes.WithOrdinals source, int precision) + throws IOException { + return useGlobalOrds(context, source, precision); + } + + @Override + public boolean useSegmentOrdinals(long maxOrd, int precision) { + final long ordinalsMemoryUsage = CardinalityAggregator.OrdinalsCollector.memoryOverhead(maxOrd); + final long countsMemoryUsage = HyperLogLogPlusPlus.memoryUsage(precision); + // only use ordinals if they don't increase memory usage by more than 25% + if (ordinalsMemoryUsage < countsMemoryUsage / 4) { + return true; + } + return false; + } + }, + SAVE_TIME_HEURISTIC(true) { + @Override + public boolean useGlobalOrdinals(AggregationContext context, ValuesSource.Bytes.WithOrdinals source, int precision) + throws IOException { + return useGlobalOrds(context, source, precision); + } + + @Override + public boolean useSegmentOrdinals(long maxOrd, int precision) { + // Using segment ordinals is much faster than using the direct collector, even when it uses more memory + return true; + } + }; + + public static ExecutionMode fromString(String value) { + if (value == null) { + return null; + } + try { + return ExecutionMode.valueOf(value.toUpperCase(Locale.ROOT)); + } catch (IllegalArgumentException e) { + throw new IllegalArgumentException( + "Invalid execution mode for cardinality aggregation. Got [" + + value + + "]" + + "expected one of [global_ordinal, segment_ordinal, direct]" + ); + } + } + + boolean isHeuristicBased; + + ExecutionMode(boolean isHeuristicBased) { + this.isHeuristicBased = isHeuristicBased; + } + + public boolean isHeuristicBased() { + return isHeuristicBased; + } + + public abstract boolean useGlobalOrdinals(AggregationContext context, ValuesSource.Bytes.WithOrdinals source, int precision) + throws IOException; + + public abstract boolean useSegmentOrdinals(long maxOrd, int precision); + } private final Long precisionThreshold; private final CardinalityAggregatorSupplier aggregatorSupplier; + private final ExecutionMode executionMode; CardinalityAggregatorFactory( String name, ValuesSourceConfig config, Long precisionThreshold, + String executionHint, AggregationContext context, AggregatorFactory parent, AggregatorFactories.Builder subFactoriesBuilder, @@ -43,17 +144,19 @@ class CardinalityAggregatorFactory extends ValuesSourceAggregatorFactory { this.aggregatorSupplier = aggregatorSupplier; this.precisionThreshold = precisionThreshold; + // For BWC reasons, the parameter is nullable. + this.executionMode = executionHint == null ? ExecutionMode.SAVE_TIME_HEURISTIC : ExecutionMode.fromString(executionHint); } public static void registerAggregators(ValuesSourceRegistry.Builder builder) { builder.register( CardinalityAggregationBuilder.REGISTRY_KEY, CoreValuesSourceType.ALL_CORE, - (name, valuesSourceConfig, precision, context, parent, metadata) -> { + (name, valuesSourceConfig, precision, executionMode, context, parent, metadata) -> { // check global ords if (valuesSourceConfig.hasValues()) { if (valuesSourceConfig.getValuesSource()instanceof final ValuesSource.Bytes.WithOrdinals source) { - if (useGlobalOrds(context, source, precision)) { + if (executionMode.useGlobalOrdinals(context, source, precision)) { final long maxOrd = source.globalMaxOrd(context.searcher()); return new GlobalOrdCardinalityAggregator( name, @@ -68,7 +171,7 @@ class CardinalityAggregatorFactory extends ValuesSourceAggregatorFactory { } } // fallback in the default aggregator - return new CardinalityAggregator(name, valuesSourceConfig, precision, context, parent, metadata); + return new CardinalityAggregator(name, valuesSourceConfig, precision, executionMode, context, parent, metadata); }, true ); @@ -91,13 +194,13 @@ class CardinalityAggregatorFactory extends ValuesSourceAggregatorFactory { @Override protected Aggregator createUnmapped(Aggregator parent, Map metadata) throws IOException { - return new CardinalityAggregator(name, config, precision(), context, parent, metadata); + return new CardinalityAggregator(name, config, precision(), null, context, parent, metadata); } @Override protected Aggregator doCreateInternal(Aggregator parent, CardinalityUpperBound cardinality, Map metadata) throws IOException { - return aggregatorSupplier.build(name, config, precision(), context, parent, metadata); + return aggregatorSupplier.build(name, config, precision(), executionMode, context, parent, metadata); } private int precision() { diff --git a/server/src/main/java/org/elasticsearch/search/aggregations/metrics/CardinalityAggregatorSupplier.java b/server/src/main/java/org/elasticsearch/search/aggregations/metrics/CardinalityAggregatorSupplier.java index c664ad8ae7e2..54051de7f811 100644 --- a/server/src/main/java/org/elasticsearch/search/aggregations/metrics/CardinalityAggregatorSupplier.java +++ b/server/src/main/java/org/elasticsearch/search/aggregations/metrics/CardinalityAggregatorSupplier.java @@ -20,6 +20,7 @@ public interface CardinalityAggregatorSupplier { String name, ValuesSourceConfig valuesSourceConfig, int precision, + CardinalityAggregatorFactory.ExecutionMode executionMode, AggregationContext context, Aggregator parent, Map metadata diff --git a/server/src/test/java/org/elasticsearch/search/aggregations/metrics/CardinalityAggregatorTests.java b/server/src/test/java/org/elasticsearch/search/aggregations/metrics/CardinalityAggregatorTests.java index 63b4a423afe2..e4ae38fcaed5 100644 --- a/server/src/test/java/org/elasticsearch/search/aggregations/metrics/CardinalityAggregatorTests.java +++ b/server/src/test/java/org/elasticsearch/search/aggregations/metrics/CardinalityAggregatorTests.java @@ -56,6 +56,7 @@ import java.io.IOException; import java.util.Collections; import java.util.HashMap; import java.util.List; +import java.util.Locale; import java.util.Map; import java.util.Set; import java.util.function.Consumer; @@ -669,12 +670,16 @@ public class CardinalityAggregatorTests extends AggregatorTestCase { } private void testAggregation( - AggregationBuilder aggregationBuilder, + CardinalityAggregationBuilder aggregationBuilder, Query query, CheckedConsumer buildIndex, Consumer verify, MappedFieldType... fieldTypes ) throws IOException { testCase(aggregationBuilder, query, buildIndex, verify, fieldTypes); + for (CardinalityAggregatorFactory.ExecutionMode mode : CardinalityAggregatorFactory.ExecutionMode.values()) { + aggregationBuilder.executionHint(mode.toString().toLowerCase(Locale.ROOT)); + testCase(aggregationBuilder, query, buildIndex, verify, fieldTypes); + } } } diff --git a/server/src/test/java/org/elasticsearch/search/aggregations/metrics/CardinalityTests.java b/server/src/test/java/org/elasticsearch/search/aggregations/metrics/CardinalityTests.java index c86035d8d520..9e4ad42a5fb6 100644 --- a/server/src/test/java/org/elasticsearch/search/aggregations/metrics/CardinalityTests.java +++ b/server/src/test/java/org/elasticsearch/search/aggregations/metrics/CardinalityTests.java @@ -10,6 +10,8 @@ package org.elasticsearch.search.aggregations.metrics; import org.elasticsearch.search.aggregations.BaseAggregationTestCase; +import java.util.List; + public class CardinalityTests extends BaseAggregationTestCase { @Override @@ -20,6 +22,11 @@ public class CardinalityTests extends BaseAggregationTestCase new CardinalityAggregator( + name, + valuesSourceConfig, + precision, + // Force execution mode to null + null, + context, + parent, + metadata + ), + true + ); } private ContextParser checkLicense(ContextParser realParser, LicensedFeature.Momentary feature) { diff --git a/x-pack/qa/runtime-fields/build.gradle b/x-pack/qa/runtime-fields/build.gradle index a066d7460307..41bdd762b127 100644 --- a/x-pack/qa/runtime-fields/build.gradle +++ b/x-pack/qa/runtime-fields/build.gradle @@ -95,6 +95,11 @@ subprojects { 'search.aggregation/20_terms/string profiler via global ordinals native implementation', 'search.aggregation/20_terms/Global ordinals are loaded with the global_ordinals execution hint', 'search.aggregation/170_cardinality_metric/profiler string', + 'search.aggregation/170_cardinality_metric/profiler string segment ords hint', + 'search.aggregation/170_cardinality_metric/profiler string global ords hint', + 'search.aggregation/170_cardinality_metric/profiler string direct hint', + 'search.aggregation/170_cardinality_metric/profiler string save memory hint', + 'search.aggregation/170_cardinality_metric/profiler string save time hint', 'search.aggregation/235_composite_sorted/*', // timeseries dimensions can't be runtime fields 'search.aggregation/450_time_series/*',