Deprecate global_ordinals_hash and global_ordinals_low_cardinality (#26173)

* Deprecate global_ordinals_hash and global_ordinals_low_cardinality

This change deprecates the `global_ordinals_hash` and `global_ordinals_low_cardinality` and
makes the `global_ordinals` execution hint choose internally if global ords should be remapped or use the segment ord directly.
These hints are too sensitive and expert to be exposed and we should be able to take the right decision internally based on the agg tree.
This commit is contained in:
Jim Ferenczi 2017-08-21 19:12:27 +02:00 committed by GitHub
parent 654378f504
commit 977dcfe789
7 changed files with 127 additions and 245 deletions

View File

@ -30,6 +30,8 @@ import org.apache.lucene.search.TermQuery;
import org.apache.lucene.util.BytesRef;
import org.elasticsearch.common.ParseField;
import org.elasticsearch.common.lease.Releasable;
import org.elasticsearch.common.logging.DeprecationLogger;
import org.elasticsearch.common.logging.Loggers;
import org.elasticsearch.common.lucene.index.FilterableTermsEnum;
import org.elasticsearch.common.lucene.index.FreqTermsEnum;
import org.elasticsearch.index.mapper.MappedFieldType;
@ -53,12 +55,12 @@ import org.elasticsearch.search.aggregations.support.ValuesSourceConfig;
import org.elasticsearch.search.internal.SearchContext;
import java.io.IOException;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
public class SignificantTermsAggregatorFactory extends ValuesSourceAggregatorFactory<ValuesSource, SignificantTermsAggregatorFactory>
implements Releasable {
private static final DeprecationLogger DEPRECATION_LOGGER = new DeprecationLogger(Loggers.getLogger(SignificantTermsAggregatorFactory.class));
private final IncludeExclude includeExclude;
private final String executionHint;
@ -202,17 +204,13 @@ public class SignificantTermsAggregatorFactory extends ValuesSourceAggregatorFac
if (valuesSource instanceof ValuesSource.Bytes) {
ExecutionMode execution = null;
if (executionHint != null) {
execution = ExecutionMode.fromString(executionHint);
execution = ExecutionMode.fromString(executionHint, DEPRECATION_LOGGER);
}
if (!(valuesSource instanceof ValuesSource.Bytes.WithOrdinals)) {
if (valuesSource instanceof ValuesSource.Bytes.WithOrdinals == false) {
execution = ExecutionMode.MAP;
}
if (execution == null) {
if (Aggregator.descendsFromBucketAggregator(parent)) {
execution = ExecutionMode.GLOBAL_ORDINALS_HASH;
} else {
execution = ExecutionMode.GLOBAL_ORDINALS;
}
execution = ExecutionMode.GLOBAL_ORDINALS;
}
assert execution != null;
@ -291,44 +289,35 @@ public class SignificantTermsAggregatorFactory extends ValuesSourceAggregatorFac
Map<String, Object> metaData) throws IOException {
final IncludeExclude.OrdinalsFilter filter = includeExclude == null ? null : includeExclude.convertToOrdinalsFilter(format);
boolean remapGlobalOrd = true;
if (Aggregator.descendsFromBucketAggregator(parent) == false &&
factories == AggregatorFactories.EMPTY &&
includeExclude == null) {
/**
* We don't need to remap global ords iff this aggregator:
* - is not a child of a bucket aggregator AND
* - has no include/exclude rules AND
* - has no sub-aggregator
**/
remapGlobalOrd = false;
}
return new GlobalOrdinalsSignificantTermsAggregator(name, factories,
(ValuesSource.Bytes.WithOrdinals.FieldData) valuesSource, format, bucketCountThresholds, filter,
aggregationContext, parent, false, significanceHeuristic, termsAggregatorFactory, pipelineAggregators, metaData);
}
},
GLOBAL_ORDINALS_HASH(new ParseField("global_ordinals_hash")) {
@Override
Aggregator create(String name,
AggregatorFactories factories,
ValuesSource valuesSource,
DocValueFormat format,
TermsAggregator.BucketCountThresholds bucketCountThresholds,
IncludeExclude includeExclude,
SearchContext aggregationContext,
Aggregator parent,
SignificanceHeuristic significanceHeuristic,
SignificantTermsAggregatorFactory termsAggregatorFactory,
List<PipelineAggregator> pipelineAggregators,
Map<String, Object> metaData) throws IOException {
final IncludeExclude.OrdinalsFilter filter = includeExclude == null ? null : includeExclude.convertToOrdinalsFilter(format);
return new GlobalOrdinalsSignificantTermsAggregator(name, factories,
(ValuesSource.Bytes.WithOrdinals.FieldData) valuesSource, format, bucketCountThresholds, filter, aggregationContext, parent,
true, significanceHeuristic, termsAggregatorFactory, pipelineAggregators, metaData);
aggregationContext, parent, remapGlobalOrd, significanceHeuristic, termsAggregatorFactory, pipelineAggregators, metaData);
}
};
public static ExecutionMode fromString(String value) {
for (ExecutionMode mode : values()) {
if (mode.parseField.match(value)) {
return mode;
}
public static ExecutionMode fromString(String value, final DeprecationLogger deprecationLogger) {
if ("global_ordinals".equals(value)) {
return GLOBAL_ORDINALS;
} else if ("global_ordinals_hash".equals(value)) {
deprecationLogger.deprecated("global_ordinals_hash is deprecated. Please use [global_ordinals] instead.");
return GLOBAL_ORDINALS;
} else if ("map".equals(value)) {
return MAP;
}
throw new IllegalArgumentException("Unknown `execution_hint`: [" + value + "], expected any of " + Arrays.toString(values()));
throw new IllegalArgumentException("Unknown `execution_hint`: [" + value + "], expected any of [map, global_ordinals]");
}
private final ParseField parseField;

View File

@ -78,20 +78,20 @@ public class GlobalOrdinalsStringTermsAggregator extends AbstractStringTermsAggr
}
public GlobalOrdinalsStringTermsAggregator(String name, AggregatorFactories factories,
ValuesSource.Bytes.WithOrdinals valuesSource,
BucketOrder order,
DocValueFormat format,
BucketCountThresholds bucketCountThresholds,
IncludeExclude.OrdinalsFilter includeExclude,
SearchContext context,
Aggregator parent,
boolean forceRemapGlobalOrds,
SubAggCollectionMode collectionMode,
boolean showTermDocCountError,
List<PipelineAggregator> pipelineAggregators,
Map<String, Object> metaData) throws IOException {
ValuesSource.Bytes.WithOrdinals valuesSource,
BucketOrder order,
DocValueFormat format,
BucketCountThresholds bucketCountThresholds,
IncludeExclude.OrdinalsFilter includeExclude,
SearchContext context,
Aggregator parent,
boolean remapGlobalOrds,
SubAggCollectionMode collectionMode,
boolean showTermDocCountError,
List<PipelineAggregator> pipelineAggregators,
Map<String, Object> metaData) throws IOException {
super(name, factories, context, parent, order, format, bucketCountThresholds, collectionMode, showTermDocCountError,
pipelineAggregators, metaData);
pipelineAggregators, metaData);
this.valuesSource = valuesSource;
this.includeExclude = includeExclude;
final IndexReader reader = context.searcher().getIndexReader();
@ -100,18 +100,9 @@ public class GlobalOrdinalsStringTermsAggregator extends AbstractStringTermsAggr
this.valueCount = values.getValueCount();
this.lookupGlobalOrd = values::lookupOrd;
this.acceptedGlobalOrdinals = includeExclude != null ? includeExclude.acceptedGlobalOrdinals(values) : null;
/**
* Remap global ords to dense bucket ordinals if any sub-aggregator cannot be deferred.
* Sub-aggregators expect dense buckets and allocate memories based on this assumption.
* Deferred aggregators are safe because the selected ordinals are remapped when the buckets
* are replayed.
*/
boolean remapGlobalOrds = forceRemapGlobalOrds || Arrays.stream(subAggregators).anyMatch((a) -> shouldDefer(a) == false);
this.bucketOrds = remapGlobalOrds ? new LongHash(1, context.bigArrays()) : null;
}
boolean remapGlobalOrds() {
return bucketOrds != null;
}

View File

@ -21,6 +21,8 @@ package org.elasticsearch.search.aggregations.bucket.terms;
import org.apache.lucene.search.IndexSearcher;
import org.elasticsearch.common.ParseField;
import org.elasticsearch.common.logging.DeprecationLogger;
import org.elasticsearch.common.logging.Loggers;
import org.elasticsearch.search.DocValueFormat;
import org.elasticsearch.search.aggregations.AggregationExecutionException;
import org.elasticsearch.search.aggregations.Aggregator;
@ -41,18 +43,18 @@ import org.elasticsearch.search.aggregations.support.ValuesSourceConfig;
import org.elasticsearch.search.internal.SearchContext;
import java.io.IOException;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
public class TermsAggregatorFactory extends ValuesSourceAggregatorFactory<ValuesSource, TermsAggregatorFactory> {
private static final DeprecationLogger DEPRECATION_LOGGER = new DeprecationLogger(Loggers.getLogger(TermsAggregatorFactory.class));
private final BucketOrder order;
private final IncludeExclude includeExclude;
private final String executionHint;
private final SubAggCollectionMode collectMode;
private final TermsAggregator.BucketCountThresholds bucketCountThresholds;
private boolean showTermDocCountError;
private final boolean showTermDocCountError;
TermsAggregatorFactory(String name,
ValuesSourceConfig<ValuesSource> config,
@ -124,61 +126,15 @@ public class TermsAggregatorFactory extends ValuesSourceAggregatorFactory<Values
if (valuesSource instanceof ValuesSource.Bytes) {
ExecutionMode execution = null;
if (executionHint != null) {
execution = ExecutionMode.fromString(executionHint);
execution = ExecutionMode.fromString(executionHint, DEPRECATION_LOGGER);
}
// In some cases, using ordinals is just not supported: override it
if (!(valuesSource instanceof ValuesSource.Bytes.WithOrdinals)) {
if (valuesSource instanceof ValuesSource.Bytes.WithOrdinals == false) {
execution = ExecutionMode.MAP;
}
final long maxOrd;
final double ratio;
if (execution == null || execution.needsGlobalOrdinals()) {
ValuesSource.Bytes.WithOrdinals valueSourceWithOrdinals = (ValuesSource.Bytes.WithOrdinals) valuesSource;
IndexSearcher indexSearcher = context.searcher();
maxOrd = valueSourceWithOrdinals.globalMaxOrd(indexSearcher);
ratio = maxOrd / ((double) indexSearcher.getIndexReader().numDocs());
} else {
maxOrd = -1;
ratio = -1;
}
// Let's try to use a good default
final long maxOrd = getMaxOrd(valuesSource, context.searcher());
if (execution == null) {
// if there is a parent bucket aggregator the number of
// instances of this aggregator is going
// to be unbounded and most instances may only aggregate few
// documents, so use hashed based
// global ordinals to keep the bucket ords dense.
// Additionally, if using partitioned terms the regular global
// ordinals would be sparse so we opt for hash
// Finally if we are sorting by sub aggregations, then these
// aggregations cannot be deferred, so global_ordinals_hash is
// a safer choice as we won't use memory for sub aggregations
// for buckets that are not collected.
if (Aggregator.descendsFromBucketAggregator(parent) ||
(includeExclude != null && includeExclude.isPartitionBased()) ||
isAggregationSort(order)) {
execution = ExecutionMode.GLOBAL_ORDINALS_HASH;
} else {
if (factories == AggregatorFactories.EMPTY) {
if (ratio <= 0.5 && maxOrd <= 2048) {
// 0.5: At least we need reduce the number of global
// ordinals look-ups by half
// 2048: GLOBAL_ORDINALS_LOW_CARDINALITY has
// additional memory usage, which directly linked to
// maxOrd, so we need to limit.
execution = ExecutionMode.GLOBAL_ORDINALS_LOW_CARDINALITY;
} else {
execution = ExecutionMode.GLOBAL_ORDINALS;
}
} else {
execution = ExecutionMode.GLOBAL_ORDINALS;
}
}
execution = ExecutionMode.GLOBAL_ORDINALS;
}
SubAggCollectionMode cm = collectMode;
if (cm == null) {
@ -247,6 +203,19 @@ public class TermsAggregatorFactory extends ValuesSourceAggregatorFactory<Values
return SubAggCollectionMode.DEPTH_FIRST;
}
/**
* Get the maximum global ordinal value for the provided {@link ValuesSource} or -1
* if the values source is not an instance of {@link ValuesSource.Bytes.WithOrdinals}.
*/
static long getMaxOrd(ValuesSource source, IndexSearcher searcher) throws IOException {
if (source instanceof ValuesSource.Bytes.WithOrdinals) {
ValuesSource.Bytes.WithOrdinals valueSourceWithOrdinals = (ValuesSource.Bytes.WithOrdinals) source;
return valueSourceWithOrdinals.globalMaxOrd(searcher);
} else {
return -1;
}
}
public enum ExecutionMode {
MAP(new ParseField("map")) {
@ -265,18 +234,10 @@ public class TermsAggregatorFactory extends ValuesSourceAggregatorFactory<Values
boolean showTermDocCountError,
List<PipelineAggregator> pipelineAggregators,
Map<String, Object> metaData) throws IOException {
final IncludeExclude.StringFilter filter = includeExclude == null ? null : includeExclude.convertToStringFilter(format);
return new StringTermsAggregator(name, factories, valuesSource, order, format, bucketCountThresholds, filter,
context, parent, subAggCollectMode, showTermDocCountError, pipelineAggregators, metaData);
}
@Override
boolean needsGlobalOrdinals() {
return false;
}
},
GLOBAL_ORDINALS(new ParseField("global_ordinals")) {
@ -294,91 +255,61 @@ public class TermsAggregatorFactory extends ValuesSourceAggregatorFactory<Values
List<PipelineAggregator> pipelineAggregators,
Map<String, Object> metaData) throws IOException {
final IncludeExclude.OrdinalsFilter filter = includeExclude == null ? null : includeExclude.convertToOrdinalsFilter(format);
return new GlobalOrdinalsStringTermsAggregator(name, factories, (ValuesSource.Bytes.WithOrdinals) valuesSource, order,
format, bucketCountThresholds, filter, context, parent, false, subAggCollectMode, showTermDocCountError,
final long maxOrd = getMaxOrd(valuesSource, context.searcher());
assert maxOrd != -1;
final double ratio = maxOrd / ((double) context.searcher().getIndexReader().numDocs());
if (factories == AggregatorFactories.EMPTY &&
includeExclude == null &&
Aggregator.descendsFromBucketAggregator(parent) == false &&
ratio <= 0.5 && maxOrd <= 2048) {
/**
* We can use the low cardinality execution mode iff this aggregator:
* - has no sub-aggregator AND
* - is not a child of a bucket aggregator AND
* - At least we reduce the number of global ordinals look-ups by half (ration <= 0.5) AND
* - the maximum global ordinal is less than 2048 (LOW_CARDINALITY has additional memory usage,
* which directly linked to maxOrd, so we need to limit).
*/
return new GlobalOrdinalsStringTermsAggregator.LowCardinality(name, factories, (ValuesSource.Bytes.WithOrdinals) valuesSource, order,
format, bucketCountThresholds, context, parent, false, subAggCollectMode, showTermDocCountError,
pipelineAggregators, metaData);
}
@Override
boolean needsGlobalOrdinals() {
return true;
}
},
GLOBAL_ORDINALS_HASH(new ParseField("global_ordinals_hash")) {
@Override
Aggregator create(String name,
AggregatorFactories factories,
ValuesSource valuesSource,
BucketOrder order,
DocValueFormat format,
TermsAggregator.BucketCountThresholds bucketCountThresholds,
IncludeExclude includeExclude,
SearchContext context,
Aggregator parent,
SubAggCollectionMode subAggCollectMode,
boolean showTermDocCountError,
List<PipelineAggregator> pipelineAggregators,
Map<String, Object> metaData) throws IOException {
final IncludeExclude.OrdinalsFilter filter = includeExclude == null ? null : includeExclude.convertToOrdinalsFilter(format);
return new GlobalOrdinalsStringTermsAggregator(name, factories, (ValuesSource.Bytes.WithOrdinals) valuesSource,
order, format, bucketCountThresholds, filter, context, parent, true, subAggCollectMode,
showTermDocCountError, pipelineAggregators, metaData);
}
@Override
boolean needsGlobalOrdinals() {
return true;
}
},
GLOBAL_ORDINALS_LOW_CARDINALITY(new ParseField("global_ordinals_low_cardinality")) {
@Override
Aggregator create(String name,
AggregatorFactories factories,
ValuesSource valuesSource,
BucketOrder order,
DocValueFormat format,
TermsAggregator.BucketCountThresholds bucketCountThresholds,
IncludeExclude includeExclude,
SearchContext context,
Aggregator parent,
SubAggCollectionMode subAggCollectMode,
boolean showTermDocCountError,
List<PipelineAggregator> pipelineAggregators,
Map<String, Object> metaData) throws IOException {
if (includeExclude != null || factories.countAggregators() > 0
// we need the FieldData impl to be able to extract the
// segment to global ord mapping
|| valuesSource.getClass() != ValuesSource.Bytes.FieldData.class) {
return GLOBAL_ORDINALS.create(name, factories, valuesSource, order, format, bucketCountThresholds, includeExclude,
context, parent, subAggCollectMode, showTermDocCountError, pipelineAggregators, metaData);
}
return new GlobalOrdinalsStringTermsAggregator.LowCardinality(name, factories,
(ValuesSource.Bytes.WithOrdinals) valuesSource, order, format, bucketCountThresholds, context, parent,
false, subAggCollectMode, showTermDocCountError, pipelineAggregators, metaData);
}
@Override
boolean needsGlobalOrdinals() {
return true;
final IncludeExclude.OrdinalsFilter filter = includeExclude == null ? null : includeExclude.convertToOrdinalsFilter(format);
boolean remapGlobalOrds = true;
if (includeExclude == null &&
Aggregator.descendsFromBucketAggregator(parent) == false &&
(factories == AggregatorFactories.EMPTY ||
(isAggregationSort(order) == false && subAggCollectMode == SubAggCollectionMode.BREADTH_FIRST))) {
/**
* We don't need to remap global ords iff this aggregator:
* - has no include/exclude rules AND
* - is not a child of a bucket aggregator AND
* - has no sub-aggregator or only sub-aggregator that can be deferred ({@link SubAggCollectionMode#BREADTH_FIRST}).
**/
remapGlobalOrds = false;
}
return new GlobalOrdinalsStringTermsAggregator(name, factories, (ValuesSource.Bytes.WithOrdinals) valuesSource, order,
format, bucketCountThresholds, filter, context, parent, remapGlobalOrds, subAggCollectMode, showTermDocCountError,
pipelineAggregators, metaData);
}
};
public static ExecutionMode fromString(String value) {
for (ExecutionMode mode : values()) {
if (mode.parseField.match(value)) {
return mode;
}
public static ExecutionMode fromString(String value, final DeprecationLogger deprecationLogger) {
switch (value) {
case "global_ordinals":
return GLOBAL_ORDINALS;
case "global_ordinals_hash":
deprecationLogger.deprecated("[global_ordinals_hash] is deprecated. Please use [global_ordinals] instead.");
return GLOBAL_ORDINALS;
case "global_ordinals_low_cardinality":
deprecationLogger.deprecated("[global_ordinals_low_cardinality] is deprecated. Please use [global_ordinals] instead.");
return GLOBAL_ORDINALS;
case "map":
return MAP;
default:
throw new IllegalArgumentException("Unknown `execution_hint`: [" + value + "], expected any of [map, global_ordinals]");
}
throw new IllegalArgumentException("Unknown `execution_hint`: [" + value + "], expected any of " + Arrays.toString(values()));
}
private final ParseField parseField;
@ -401,8 +332,6 @@ public class TermsAggregatorFactory extends ValuesSourceAggregatorFactory<Values
List<PipelineAggregator> pipelineAggregators,
Map<String, Object> metaData) throws IOException;
abstract boolean needsGlobalOrdinals();
@Override
public String toString() {
return parseField.getPreferredName();

View File

@ -266,10 +266,6 @@ public class EquivalenceIT extends ESIntegTestCase {
.setIndicesOptions(IndicesOptions.lenientExpandOpen())
.execute().get());
TermsAggregatorFactory.ExecutionMode[] globalOrdinalModes = new TermsAggregatorFactory.ExecutionMode[] {
TermsAggregatorFactory.ExecutionMode.GLOBAL_ORDINALS_HASH, TermsAggregatorFactory.ExecutionMode.GLOBAL_ORDINALS
};
SearchResponse resp = client().prepareSearch("idx")
.addAggregation(
terms("long")
@ -294,14 +290,14 @@ public class EquivalenceIT extends ESIntegTestCase {
terms("string_global_ordinals")
.field("string_values")
.collectMode(randomFrom(SubAggCollectionMode.values()))
.executionHint(globalOrdinalModes[randomInt(globalOrdinalModes.length - 1)].toString())
.executionHint(TermsAggregatorFactory.ExecutionMode.GLOBAL_ORDINALS.toString())
.size(maxNumTerms)
.subAggregation(extendedStats("stats").field("num")))
.addAggregation(
terms("string_global_ordinals_doc_values")
.field("string_values.doc_values")
.collectMode(randomFrom(SubAggCollectionMode.values()))
.executionHint(globalOrdinalModes[randomInt(globalOrdinalModes.length - 1)].toString())
.executionHint(TermsAggregatorFactory.ExecutionMode.GLOBAL_ORDINALS.toString())
.size(maxNumTerms)
.subAggregation(extendedStats("stats").field("num")))
.execute().actionGet();

View File

@ -102,13 +102,6 @@ public class TermsAggregatorTests extends AggregatorTestCase {
globalAgg = (GlobalOrdinalsStringTermsAggregator) aggregator;
assertTrue(globalAgg.remapGlobalOrds());
aggregationBuilder = new TermsAggregationBuilder("_name", ValueType.STRING)
.field("string")
.executionHint("global_ordinals_hash");
aggregator = createAggregator(aggregationBuilder, indexSearcher, fieldType);
assertThat(aggregator, instanceOf(GlobalOrdinalsStringTermsAggregator.class));
globalAgg = (GlobalOrdinalsStringTermsAggregator) aggregator;
assertTrue(globalAgg.remapGlobalOrds());
indexReader.close();
directory.close();
}

View File

@ -472,29 +472,22 @@ It is possible (although rarely required) to filter the values for which buckets
`exclude` parameters which are based on a regular expression string or arrays of exact terms. This functionality mirrors the features
described in the <<search-aggregations-bucket-terms-aggregation,terms aggregation>> documentation.
===== Execution hint
==== Execution hint
There are different mechanisms by which terms aggregations can be executed:
- by using field values directly in order to aggregate data per-bucket (`map`)
- by using ordinals of the field and preemptively allocating one bucket per ordinal value (`global_ordinals`)
- by using ordinals of the field and dynamically allocating one bucket per ordinal value (`global_ordinals_hash`)
- by using global ordinals of the field and allocating one bucket per global ordinal (`global_ordinals`)
Elasticsearch tries to have sensible defaults so this is something that generally doesn't need to be configured.
`map` should only be considered when very few documents match a query. Otherwise the ordinals-based execution modes
are significantly faster. By default, `map` is only used when running an aggregation on scripts, since they don't have
`global_ordinals` is the default option for `keyword` field, it uses global ordinals to allocates buckets dynamically
so memory usage is linear to the number of values of the documents that are part of the aggregation scope.
`map` should only be considered when very few documents match a query. Otherwise the ordinals-based execution mode
is significantly faster. By default, `map` is only used when running an aggregation on scripts, since they don't have
ordinals.
`global_ordinals` is the second fastest option, but the fact that it preemptively allocates buckets can be memory-intensive,
especially if you have one or more sub aggregations. It is used by default on top-level terms aggregations.
`global_ordinals_hash` on the contrary to `global_ordinals` and `global_ordinals_low_cardinality` allocates buckets dynamically
so memory usage is linear to the number of values of the documents that are part of the aggregation scope. It is used by default
in inner aggregations.
[source,js]
--------------------------------------------------
@ -510,6 +503,6 @@ in inner aggregations.
}
--------------------------------------------------
<1> the possible values are `map`, `global_ordinals` and `global_ordinals_hash`
<1> the possible values are `map`, `global_ordinals`
Please note that Elasticsearch will ignore this execution hint if it is not applicable.

View File

@ -729,26 +729,17 @@ collection mode need to replay the query on the second pass but only for the doc
There are different mechanisms by which terms aggregations can be executed:
- by using field values directly in order to aggregate data per-bucket (`map`)
- by using ordinals of the field and preemptively allocating one bucket per ordinal value (`global_ordinals`)
- by using ordinals of the field and dynamically allocating one bucket per ordinal value (`global_ordinals_hash`)
- by using per-segment ordinals to compute counts and remap these counts to global counts using global ordinals (`global_ordinals_low_cardinality`)
- by using global ordinals of the field and allocating one bucket per global ordinal (`global_ordinals`)
Elasticsearch tries to have sensible defaults so this is something that generally doesn't need to be configured.
`map` should only be considered when very few documents match a query. Otherwise the ordinals-based execution modes
are significantly faster. By default, `map` is only used when running an aggregation on scripts, since they don't have
`global_ordinals` is the default option for `keyword` field, it uses global ordinals to allocates buckets dynamically
so memory usage is linear to the number of values of the documents that are part of the aggregation scope.
`map` should only be considered when very few documents match a query. Otherwise the ordinals-based execution mode
is significantly faster. By default, `map` is only used when running an aggregation on scripts, since they don't have
ordinals.
`global_ordinals_low_cardinality` only works for leaf terms aggregations but is usually the fastest execution mode. Memory
usage is linear with the number of unique values in the field, so it is only enabled by default on low-cardinality fields.
`global_ordinals` is the second fastest option, but the fact that it preemptively allocates buckets can be memory-intensive,
especially if you have one or more sub aggregations. It is used by default on top-level terms aggregations.
`global_ordinals_hash` on the contrary to `global_ordinals` and `global_ordinals_low_cardinality` allocates buckets dynamically
so memory usage is linear to the number of values of the documents that are part of the aggregation scope. It is used by default
in inner aggregations.
[source,js]
--------------------------------------------------
{
@ -763,7 +754,7 @@ in inner aggregations.
}
--------------------------------------------------
<1> The possible values are `map`, `global_ordinals`, `global_ordinals_hash` and `global_ordinals_low_cardinality`
<1> The possible values are `map`, `global_ordinals`
Please note that Elasticsearch will ignore this execution hint if it is not applicable and that there is no backward compatibility guarantee on these hints.