diff --git a/CHANGELOG.md b/CHANGELOG.md index 7c6419fef1038..79491875c53dc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -175,7 +175,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), - Performance improvement for MultiTerm Queries on Keyword fields ([#7057](https://github.com/opensearch-project/OpenSearch/issues/7057)) - Refactor common parts from the Rounding class into a separate 'round' package ([#11023](https://github.com/opensearch-project/OpenSearch/issues/11023)) - Performance improvement for date histogram aggregations without sub-aggregations ([#11083](https://github.com/opensearch-project/OpenSearch/pull/11083)) -- Apply the fast filter optimization to composite aggregation ([#11505](https://github.com/opensearch-project/OpenSearch/pull/11083)) +- Apply the fast filter optimization to composite aggregation of date histogram source ([#11505](https://github.com/opensearch-project/OpenSearch/pull/11083)) - Disable concurrent aggs for Diversified Sampler and Sampler aggs ([#11087](https://github.com/opensearch-project/OpenSearch/issues/11087)) - Made leader/follower check timeout setting dynamic ([#10528](https://github.com/opensearch-project/OpenSearch/pull/10528)) - Improved performance of numeric exact-match queries ([#11209](https://github.com/opensearch-project/OpenSearch/pull/11209)) diff --git a/server/src/main/java/org/opensearch/search/aggregations/bucket/FastFilterRewriteHelper.java b/server/src/main/java/org/opensearch/search/aggregations/bucket/FastFilterRewriteHelper.java index 59781aff9b822..34a28fd04d1a3 100644 --- a/server/src/main/java/org/opensearch/search/aggregations/bucket/FastFilterRewriteHelper.java +++ b/server/src/main/java/org/opensearch/search/aggregations/bucket/FastFilterRewriteHelper.java @@ -48,6 +48,7 @@ *
  • date histogram : date range filter. * Applied: DateHistogramAggregator, AutoDateHistogramAggregator, CompositeAggregator
  • * + * * @opensearch.internal */ public class FastFilterRewriteHelper { @@ -55,7 +56,7 @@ public class FastFilterRewriteHelper { private static final int MAX_NUM_FILTER_BUCKETS = 1024; private static final Map, Function> queryWrappers; - // Initialize the wrappers map for unwrapping the query + // Initialize the wrapper map for unwrapping the query static { queryWrappers = new HashMap<>(); queryWrappers.put(ConstantScoreQuery.class, q -> ((ConstantScoreQuery) q).getQuery()); @@ -77,9 +78,9 @@ private static Query unwrapIntoConcreteQuery(Query query) { } /** - * Finds the min and max bounds for segments within the passed search context + * Finds the min and max bounds of field values for the shard */ - private static long[] getIndexBoundsFromLeaves(final SearchContext context, final String fieldName) throws IOException { + private static long[] getIndexBounds(final SearchContext context, final String fieldName) throws IOException { final List leaves = context.searcher().getIndexReader().leaves(); long min = Long.MAX_VALUE, max = Long.MIN_VALUE; // Since the query does not specify bounds for aggregation, we can @@ -103,7 +104,7 @@ private static long[] getIndexBoundsFromLeaves(final SearchContext context, fina */ public static long[] getAggregationBounds(final SearchContext context, final String fieldName) throws IOException { final Query cq = unwrapIntoConcreteQuery(context.query()); - final long[] indexBounds = getIndexBoundsFromLeaves(context, fieldName); + final long[] indexBounds = getIndexBounds(context, fieldName); if (cq instanceof PointRangeQuery) { final PointRangeQuery prq = (PointRangeQuery) cq; // Ensure that the query and aggregation are on the same field @@ -117,8 +118,14 @@ public static long[] getAggregationBounds(final SearchContext context, final Str } else if (cq instanceof MatchAllDocsQuery) { return indexBounds; } - - return null; + // Check if the top-level query (which may be a PRQ on another field) is functionally match-all + Weight weight = context.searcher().createWeight(context.query(), ScoreMode.COMPLETE_NO_SCORES, 1f); + for (LeafReaderContext ctx : context.searcher().getIndexReader().leaves()) { + if (weight.count(ctx) != ctx.reader().numDocs()) { + return null; + } + } + return indexBounds; } /** @@ -180,33 +187,55 @@ protected String toString(int dimension, byte[] value) { } /** - * @param computeBounds get the lower and upper bound of the field in a shard search - * @param roundingFunction produce Rounding that contains interval of date range. - * Rounding is computed dynamically using the bounds in AutoDateHistogram - * @param preparedRoundingSupplier produce PreparedRounding to round values at call-time + * Context object to do fast filter optimization */ - public static void buildFastFilter( - SearchContext context, - CheckedFunction computeBounds, - Function roundingFunction, - Supplier preparedRoundingSupplier, - FastFilterContext fastFilterContext - ) throws IOException { - assert fastFilterContext.fieldType instanceof DateFieldMapper.DateFieldType; - DateFieldMapper.DateFieldType fieldType = (DateFieldMapper.DateFieldType) fastFilterContext.fieldType; - final long[] bounds = computeBounds.apply(fastFilterContext); // TODO b do we need to pass in the context? or specific things - if (bounds != null) { + public static class FastFilterContext { + private Weight[] filters = null; + public AggregationType aggregationType; + + public FastFilterContext() {} + + private void setFilters(Weight[] filters) { + this.filters = filters; + } + + public void setAggregationType(AggregationType aggregationType) { + this.aggregationType = aggregationType; + } + + public boolean isRewriteable(final Object parent, final int subAggLength) { + return aggregationType.isRewriteable(parent, subAggLength); + } + + /** + * This filter build method is for date histogram aggregation type + * + * @param computeBounds get the lower and upper bound of the field in a shard search + * @param roundingFunction produce Rounding that contains interval of date range. + * Rounding is computed dynamically using the bounds in AutoDateHistogram + * @param preparedRoundingSupplier produce PreparedRounding to round values at call-time + */ + public void buildFastFilter( + SearchContext context, + CheckedFunction computeBounds, + Function roundingFunction, + Supplier preparedRoundingSupplier + ) throws IOException { + assert this.aggregationType instanceof DateHistogramAggregationType; + DateHistogramAggregationType aggregationType = (DateHistogramAggregationType) this.aggregationType; + DateFieldMapper.DateFieldType fieldType = aggregationType.getFieldType(); + final long[] bounds = computeBounds.apply(aggregationType); + if (bounds == null) return; + final Rounding rounding = roundingFunction.apply(bounds); final OptionalLong intervalOpt = Rounding.getInterval(rounding); - if (intervalOpt.isEmpty()) { - return; - } + if (intervalOpt.isEmpty()) return; final long interval = intervalOpt.getAsLong(); // afterKey is the last bucket key in previous response, while the bucket key // is the start of the bucket values, so add the interval - if (fastFilterContext.afterKey != -1) { - bounds[0] = fastFilterContext.afterKey + interval; + if (aggregationType instanceof CompositeAggregationType && ((CompositeAggregationType) aggregationType).afterKey != -1) { + bounds[0] = ((CompositeAggregationType) aggregationType).afterKey + interval; } final Weight[] filters = FastFilterRewriteHelper.createFilterForAggregations( @@ -218,103 +247,81 @@ public static void buildFastFilter( bounds[0], bounds[1] ); - fastFilterContext.setFilters(filters); + this.setFilters(filters); } } /** - * Encapsulates metadata about a value source needed to rewrite + * Different types have different pre-conditions, filter building logic, etc. */ - public static class FastFilterContext { - private boolean missing = false; // TODO b confirm UT that can catch this - private boolean hasScript = false; - private boolean showOtherBucket = false; + public interface AggregationType { + boolean isRewriteable(Object parent, int subAggLength); + } + /** + * For date histogram aggregation + */ + public static class DateHistogramAggregationType implements AggregationType { private final MappedFieldType fieldType; + private final boolean missing; + private final boolean hasScript; - private long afterKey = -1L; - private int size = Integer.MAX_VALUE; // only used by composite aggregation for pagination - private Weight[] filters = null; - - private final Type type; - - private RoundingValuesSource valuesSource = null; - - public FastFilterContext(MappedFieldType fieldType) { + public DateHistogramAggregationType(MappedFieldType fieldType, boolean missing, boolean hasScript) { this.fieldType = fieldType; - this.type = Type.DATE_HISTO; + this.missing = missing; + this.hasScript = hasScript; } - public FastFilterContext(CompositeValuesSourceConfig[] sourceConfigs, CompositeKey rawAfterKey, List formats) { - if (sourceConfigs.length == 1 && sourceConfigs[0].valuesSource() instanceof RoundingValuesSource) { - this.fieldType = sourceConfigs[0].fieldType(); - this.valuesSource = (RoundingValuesSource) sourceConfigs[0].valuesSource(); - this.missing = sourceConfigs[0].missingBucket(); - this.hasScript = sourceConfigs[0].hasScript(); - if (rawAfterKey != null) { - assert rawAfterKey.size() == 1 && formats.size() == 1; - this.afterKey = formats.get(0).parseLong(rawAfterKey.get(0).toString(), false, () -> { - throw new IllegalArgumentException("now() is not supported in [after] key"); - }); - } - } else { - this.fieldType = null; + @Override + public boolean isRewriteable(Object parent, int subAggLength) { + if (parent == null && subAggLength == 0 && !missing && !hasScript) { + return fieldType != null && fieldType instanceof DateFieldMapper.DateFieldType; } - this.type = Type.DATE_HISTO; - } - - public FastFilterContext(Type type) { - this.fieldType = null; - this.type = type; + return false; } public DateFieldMapper.DateFieldType getFieldType() { assert fieldType instanceof DateFieldMapper.DateFieldType; return (DateFieldMapper.DateFieldType) fieldType; } + } - public RoundingValuesSource getDateHistogramSource() { - return valuesSource; - } - - public void setSize(int size) { + /** + * For composite aggregation with date histogram as a source + */ + public static class CompositeAggregationType extends DateHistogramAggregationType { + private final RoundingValuesSource valuesSource; + private long afterKey = -1L; + private final int size; + + public CompositeAggregationType( + CompositeValuesSourceConfig[] sourceConfigs, + CompositeKey rawAfterKey, + List formats, + int size + ) { + super(sourceConfigs[0].fieldType(), sourceConfigs[0].missingBucket(), sourceConfigs[0].hasScript()); + this.valuesSource = (RoundingValuesSource) sourceConfigs[0].valuesSource(); this.size = size; + if (rawAfterKey != null) { + assert rawAfterKey.size() == 1 && formats.size() == 1; + this.afterKey = formats.get(0).parseLong(rawAfterKey.get(0).toString(), false, () -> { + throw new IllegalArgumentException("now() is not supported in [after] key"); + }); + } } - public void setFilters(Weight[] filters) { - this.filters = filters; - } - - public void setMissing(boolean missing) { - this.missing = missing; - } - - public void setHasScript(boolean hasScript) { - this.hasScript = hasScript; + public Rounding getRounding() { + return valuesSource.getRounding(); } - public void setShowOtherBucket(boolean showOtherBucket) { - this.showOtherBucket = showOtherBucket; - } - - public boolean isRewriteable(final Object parent, final int subAggLength) { - if (parent == null && subAggLength == 0 && !missing && !hasScript) { - if (type == Type.FILTERS) { - return !showOtherBucket; - } else if (type == Type.DATE_HISTO) { - return fieldType != null && fieldType instanceof DateFieldMapper.DateFieldType; - } - } - return false; + public Rounding.Prepared getRoundingPreparer() { + return valuesSource.getPreparedRounding(); } + } - /** - * Different types have different pre-conditions, filter building logic, etc. - */ - public enum Type { - FILTERS, - DATE_HISTO - } + public static boolean isCompositeAggRewriteable(CompositeValuesSourceConfig[] sourceConfigs) { + return sourceConfigs.length == 1 && sourceConfigs[0].valuesSource() instanceof RoundingValuesSource; } public static long getBucketOrd(long bucketOrd) { @@ -326,7 +333,7 @@ public static long getBucketOrd(long bucketOrd) { } /** - * This should be executed for each segment + * This is executed for each segment by passing the leaf reader context * * @param incrementDocCount takes in the bucket key value and the bucket count */ @@ -339,7 +346,6 @@ public static boolean tryFastFilterAggregation( if (fastFilterContext.filters == null) return false; final Weight[] filters = fastFilterContext.filters; - // TODO b refactor the type conversion to the context final int[] counts = new int[filters.length]; int i; for (i = 0; i < filters.length; i++) { @@ -352,18 +358,23 @@ public static boolean tryFastFilterAggregation( } int s = 0; + int size = Integer.MAX_VALUE; for (i = 0; i < filters.length; i++) { if (counts[i] > 0) { long bucketKey = i; // the index of filters is the key for filters aggregation - if (fastFilterContext.type == FastFilterContext.Type.DATE_HISTO) { - final DateFieldMapper.DateFieldType fieldType = (DateFieldMapper.DateFieldType) fastFilterContext.fieldType; + if (fastFilterContext.aggregationType instanceof DateHistogramAggregationType) { + final DateFieldMapper.DateFieldType fieldType = ((DateHistogramAggregationType) fastFilterContext.aggregationType) + .getFieldType(); bucketKey = fieldType.convertNanosToMillis( NumericUtils.sortableBytesToLong(((PointRangeQuery) filters[i].getQuery()).getLowerPoint(), 0) ); + if (fastFilterContext.aggregationType instanceof CompositeAggregationType) { + size = ((CompositeAggregationType) fastFilterContext.aggregationType).size; + } } incrementDocCount.accept(bucketKey, counts[i]); s++; - if (s > fastFilterContext.size) return true; + if (s > size) return true; } } diff --git a/server/src/main/java/org/opensearch/search/aggregations/bucket/composite/CompositeAggregator.java b/server/src/main/java/org/opensearch/search/aggregations/bucket/composite/CompositeAggregator.java index e79e4602a3a2d..36f94dc833a0b 100644 --- a/server/src/main/java/org/opensearch/search/aggregations/bucket/composite/CompositeAggregator.java +++ b/server/src/main/java/org/opensearch/search/aggregations/bucket/composite/CompositeAggregator.java @@ -163,20 +163,23 @@ final class CompositeAggregator extends BucketsAggregator { this.queue = new CompositeValuesCollectorQueue(context.bigArrays(), sources, size, rawAfterKey); this.rawAfterKey = rawAfterKey; - fastFilterContext = new FastFilterRewriteHelper.FastFilterContext(sourceConfigs, rawAfterKey, formats); + fastFilterContext = new FastFilterRewriteHelper.FastFilterContext(); + if (!FastFilterRewriteHelper.isCompositeAggRewriteable(sourceConfigs)) return; + fastFilterContext.setAggregationType( + new FastFilterRewriteHelper.CompositeAggregationType(sourceConfigs, rawAfterKey, formats, size) + ); if (fastFilterContext.isRewriteable(parent, subAggregators.length)) { - // Currently the filter rewrite is only supported for date histograms - RoundingValuesSource dateHistogramSource = fastFilterContext.getDateHistogramSource(); - preparedRounding = dateHistogramSource.getPreparedRounding(); // bucketOrds is the data structure for saving date histogram results bucketOrds = LongKeyedBucketOrds.build(context.bigArrays(), CardinalityUpperBound.ONE); - fastFilterContext.setSize(size); - FastFilterRewriteHelper.buildFastFilter( + // Currently the filter rewrite is only supported for date histograms + FastFilterRewriteHelper.CompositeAggregationType aggregationType = + (FastFilterRewriteHelper.CompositeAggregationType) fastFilterContext.aggregationType; + preparedRounding = aggregationType.getRoundingPreparer(); + fastFilterContext.buildFastFilter( context, fc -> FastFilterRewriteHelper.getAggregationBounds(context, fc.getFieldType().name()), - x -> dateHistogramSource.getRounding(), - () -> preparedRounding, - fastFilterContext + x -> aggregationType.getRounding(), + () -> preparedRounding ); } } @@ -513,9 +516,14 @@ private void processLeafFromQuery(LeafReaderContext ctx, Sort indexSortPrefix) t @Override protected LeafBucketCollector getLeafCollector(LeafReaderContext ctx, LeafBucketCollector sub) throws IOException { - boolean optimized = FastFilterRewriteHelper.tryFastFilterAggregation(ctx, fastFilterContext, (key, count) -> { - incrementBucketDocCount(FastFilterRewriteHelper.getBucketOrd(bucketOrds.add(0, preparedRounding.round(key))), count); - }); + boolean optimized = FastFilterRewriteHelper.tryFastFilterAggregation( + ctx, + fastFilterContext, + (key, count) -> incrementBucketDocCount( + FastFilterRewriteHelper.getBucketOrd(bucketOrds.add(0, preparedRounding.round(key))), + count + ) + ); if (optimized) throw new CollectionTerminatedException(); finishLeaf(); diff --git a/server/src/main/java/org/opensearch/search/aggregations/bucket/histogram/AutoDateHistogramAggregator.java b/server/src/main/java/org/opensearch/search/aggregations/bucket/histogram/AutoDateHistogramAggregator.java index 7a0e49c275542..0ea820abbedf4 100644 --- a/server/src/main/java/org/opensearch/search/aggregations/bucket/histogram/AutoDateHistogramAggregator.java +++ b/server/src/main/java/org/opensearch/search/aggregations/bucket/histogram/AutoDateHistogramAggregator.java @@ -156,18 +156,22 @@ private AutoDateHistogramAggregator( this.roundingPreparer = roundingPreparer; this.preparedRounding = prepareRounding(0); - fastFilterContext = new FastFilterRewriteHelper.FastFilterContext(valuesSourceConfig.fieldType()); - fastFilterContext.setMissing(valuesSourceConfig.missing() != null); - fastFilterContext.setHasScript(valuesSourceConfig.script() != null); + fastFilterContext = new FastFilterRewriteHelper.FastFilterContext(); + fastFilterContext.setAggregationType( + new FastFilterRewriteHelper.DateHistogramAggregationType( + valuesSourceConfig.fieldType(), + valuesSourceConfig.missing() != null, + valuesSourceConfig.script() != null + ) + ); if (fastFilterContext.isRewriteable(parent, subAggregators.length)) { - FastFilterRewriteHelper.buildFastFilter( + fastFilterContext.buildFastFilter( context, fc -> FastFilterRewriteHelper.getAggregationBounds(context, fc.getFieldType().name()), b -> getMinimumRounding(b[0], b[1]), // Passing prepared rounding as supplier to ensure the correct prepared // rounding is set as it is done during getMinimumRounding - () -> preparedRounding, - fastFilterContext + () -> preparedRounding ); } } @@ -222,9 +226,14 @@ public final LeafBucketCollector getLeafCollector(LeafReaderContext ctx, LeafBuc return LeafBucketCollector.NO_OP_COLLECTOR; } - boolean optimized = FastFilterRewriteHelper.tryFastFilterAggregation(ctx, fastFilterContext, (key, count) -> { - incrementBucketDocCount(FastFilterRewriteHelper.getBucketOrd(getBucketOrds().add(0, preparedRounding.round(key))), count); - }); + boolean optimized = FastFilterRewriteHelper.tryFastFilterAggregation( + ctx, + fastFilterContext, + (key, count) -> incrementBucketDocCount( + FastFilterRewriteHelper.getBucketOrd(getBucketOrds().add(0, preparedRounding.round(key))), + count + ) + ); if (optimized) throw new CollectionTerminatedException(); final SortedNumericDocValues values = valuesSource.longValues(ctx); diff --git a/server/src/main/java/org/opensearch/search/aggregations/bucket/histogram/DateHistogramAggregator.java b/server/src/main/java/org/opensearch/search/aggregations/bucket/histogram/DateHistogramAggregator.java index afcb711a11ba1..b95bd093b82a6 100644 --- a/server/src/main/java/org/opensearch/search/aggregations/bucket/histogram/DateHistogramAggregator.java +++ b/server/src/main/java/org/opensearch/search/aggregations/bucket/histogram/DateHistogramAggregator.java @@ -115,15 +115,20 @@ class DateHistogramAggregator extends BucketsAggregator implements SizedBucketAg bucketOrds = LongKeyedBucketOrds.build(context.bigArrays(), cardinality); - fastFilterContext = new FastFilterRewriteHelper.FastFilterContext(valuesSourceConfig.fieldType()); - fastFilterContext.setMissing(valuesSourceConfig.missing() != null); - fastFilterContext.setHasScript(valuesSourceConfig.script() != null); + fastFilterContext = new FastFilterRewriteHelper.FastFilterContext(); + fastFilterContext.setAggregationType( + new FastFilterRewriteHelper.DateHistogramAggregationType( + valuesSourceConfig.fieldType(), + valuesSourceConfig.missing() != null, + valuesSourceConfig.script() != null + ) + ); if (fastFilterContext.isRewriteable(parent, subAggregators.length)) { - FastFilterRewriteHelper.buildFastFilter(context, this::computeBounds, x -> rounding, () -> preparedRounding, fastFilterContext); + fastFilterContext.buildFastFilter(context, this::computeBounds, x -> rounding, () -> preparedRounding); } } - private long[] computeBounds(final FastFilterRewriteHelper.FastFilterContext fieldContext) throws IOException { + private long[] computeBounds(final FastFilterRewriteHelper.DateHistogramAggregationType fieldContext) throws IOException { final long[] bounds = FastFilterRewriteHelper.getAggregationBounds(context, fieldContext.getFieldType().name()); if (bounds != null) { // Update min/max limit if user specified any hard bounds @@ -149,9 +154,14 @@ public LeafBucketCollector getLeafCollector(LeafReaderContext ctx, LeafBucketCol return LeafBucketCollector.NO_OP_COLLECTOR; } - boolean optimized = FastFilterRewriteHelper.tryFastFilterAggregation(ctx, fastFilterContext, (key, count) -> { - incrementBucketDocCount(FastFilterRewriteHelper.getBucketOrd(bucketOrds.add(0, preparedRounding.round(key))), count); - }); + boolean optimized = FastFilterRewriteHelper.tryFastFilterAggregation( + ctx, + fastFilterContext, + (key, count) -> incrementBucketDocCount( + FastFilterRewriteHelper.getBucketOrd(bucketOrds.add(0, preparedRounding.round(key))), + count + ) + ); if (optimized) throw new CollectionTerminatedException(); SortedNumericDocValues values = valuesSource.longValues(ctx);