Skip to content

Commit e1679bf

Browse files
authored
Create weights lazily in filter and filters aggregation (#26983)
Previous to this change the weights for the filter and filters aggregation were created in the `Filter(s)AggregatorFactory` which meant that they were created regardless of whether the aggregator actually collects any documents. This meant that for filters that are expensive to initialise, requests would not be quick when the query of the request was (or effectively was) a `match_none` query. This change maintains a single Weight instance for each filter across parent buckets but passes a weight supplier to the aggregator instances which will create the weight on first call and then return that instance for subsequent calls.
1 parent ab94150 commit e1679bf

File tree

7 files changed

+69
-23
lines changed

7 files changed

+69
-23
lines changed

core/src/main/java/org/elasticsearch/search/aggregations/bucket/filter/FilterAggregator.java

+4-3
Original file line numberDiff line numberDiff line change
@@ -35,16 +35,17 @@
3535
import java.io.IOException;
3636
import java.util.List;
3737
import java.util.Map;
38+
import java.util.function.Supplier;
3839

3940
/**
4041
* Aggregate all docs that match a filter.
4142
*/
4243
public class FilterAggregator extends BucketsAggregator implements SingleBucketAggregator {
4344

44-
private final Weight filter;
45+
private final Supplier<Weight> filter;
4546

4647
public FilterAggregator(String name,
47-
Weight filter,
48+
Supplier<Weight> filter,
4849
AggregatorFactories factories,
4950
SearchContext context,
5051
Aggregator parent, List<PipelineAggregator> pipelineAggregators,
@@ -57,7 +58,7 @@ public FilterAggregator(String name,
5758
public LeafBucketCollector getLeafCollector(LeafReaderContext ctx,
5859
final LeafBucketCollector sub) throws IOException {
5960
// no need to provide deleted docs to the filter
60-
final Bits bits = Lucene.asSequentialAccessBits(ctx.reader().maxDoc(), filter.scorerSupplier(ctx));
61+
final Bits bits = Lucene.asSequentialAccessBits(ctx.reader().maxDoc(), filter.get().scorerSupplier(ctx));
6162
return new LeafBucketCollectorBase(sub, null) {
6263
@Override
6364
public void collect(int doc, long bucket) throws IOException {

core/src/main/java/org/elasticsearch/search/aggregations/bucket/filter/FilterAggregatorFactory.java

+26-5
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import org.apache.lucene.search.Query;
2424
import org.apache.lucene.search.Weight;
2525
import org.elasticsearch.index.query.QueryBuilder;
26+
import org.elasticsearch.search.aggregations.AggregationInitializationException;
2627
import org.elasticsearch.search.aggregations.Aggregator;
2728
import org.elasticsearch.search.aggregations.AggregatorFactories;
2829
import org.elasticsearch.search.aggregations.AggregatorFactory;
@@ -35,20 +36,40 @@
3536

3637
public class FilterAggregatorFactory extends AggregatorFactory<FilterAggregatorFactory> {
3738

38-
final Weight weight;
39+
private Weight weight;
40+
private Query filter;
3941

4042
public FilterAggregatorFactory(String name, QueryBuilder filterBuilder, SearchContext context,
4143
AggregatorFactory<?> parent, AggregatorFactories.Builder subFactoriesBuilder, Map<String, Object> metaData) throws IOException {
4244
super(name, context, parent, subFactoriesBuilder, metaData);
43-
IndexSearcher contextSearcher = context.searcher();
44-
Query filter = filterBuilder.toFilter(context.getQueryShardContext());
45-
weight = contextSearcher.createNormalizedWeight(filter, false);
45+
filter = filterBuilder.toFilter(context.getQueryShardContext());
46+
}
47+
48+
/**
49+
* Returns the {@link Weight} for this filter aggregation, creating it if
50+
* necessary. This is done lazily so that the {@link Weight} is only created
51+
* if the aggregation collects documents reducing the overhead of the
52+
* aggregation in teh case where no documents are collected.
53+
*
54+
* Note that as aggregations are initialsed and executed in a serial manner,
55+
* no concurrency considerations are necessary here.
56+
*/
57+
public Weight getWeight() {
58+
if (weight == null) {
59+
IndexSearcher contextSearcher = context.searcher();
60+
try {
61+
weight = contextSearcher.createNormalizedWeight(filter, false);
62+
} catch (IOException e) {
63+
throw new AggregationInitializationException("Failed to initialse filter", e);
64+
}
65+
}
66+
return weight;
4667
}
4768

4869
@Override
4970
public Aggregator createInternal(Aggregator parent, boolean collectsFromSingleBucket, List<PipelineAggregator> pipelineAggregators,
5071
Map<String, Object> metaData) throws IOException {
51-
return new FilterAggregator(name, weight, factories, context, parent, pipelineAggregators, metaData);
72+
return new FilterAggregator(name, () -> this.getWeight(), factories, context, parent, pipelineAggregators, metaData);
5273
}
5374

5475
}

core/src/main/java/org/elasticsearch/search/aggregations/bucket/filter/FiltersAggregator.java

+6-4
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@
4545
import java.util.List;
4646
import java.util.Map;
4747
import java.util.Objects;
48+
import java.util.function.Supplier;
4849

4950
public class FiltersAggregator extends BucketsAggregator {
5051

@@ -115,13 +116,13 @@ public boolean equals(Object obj) {
115116
}
116117

117118
private final String[] keys;
118-
private Weight[] filters;
119+
private Supplier<Weight[]> filters;
119120
private final boolean keyed;
120121
private final boolean showOtherBucket;
121122
private final String otherBucketKey;
122123
private final int totalNumKeys;
123124

124-
public FiltersAggregator(String name, AggregatorFactories factories, String[] keys, Weight[] filters, boolean keyed,
125+
public FiltersAggregator(String name, AggregatorFactories factories, String[] keys, Supplier<Weight[]> filters, boolean keyed,
125126
String otherBucketKey, SearchContext context, Aggregator parent, List<PipelineAggregator> pipelineAggregators,
126127
Map<String, Object> metaData) throws IOException {
127128
super(name, factories, context, parent, pipelineAggregators, metaData);
@@ -141,6 +142,7 @@ public FiltersAggregator(String name, AggregatorFactories factories, String[] ke
141142
public LeafBucketCollector getLeafCollector(LeafReaderContext ctx,
142143
final LeafBucketCollector sub) throws IOException {
143144
// no need to provide deleted docs to the filter
145+
Weight[] filters = this.filters.get();
144146
final Bits[] bits = new Bits[filters.length];
145147
for (int i = 0; i < filters.length; ++i) {
146148
bits[i] = Lucene.asSequentialAccessBits(ctx.reader().maxDoc(), filters[i].scorerSupplier(ctx));
@@ -164,7 +166,7 @@ public void collect(int doc, long bucket) throws IOException {
164166

165167
@Override
166168
public InternalAggregation buildAggregation(long owningBucketOrdinal) throws IOException {
167-
List<InternalFilters.InternalBucket> buckets = new ArrayList<>(filters.length);
169+
List<InternalFilters.InternalBucket> buckets = new ArrayList<>(keys.length);
168170
for (int i = 0; i < keys.length; i++) {
169171
long bucketOrd = bucketOrd(owningBucketOrdinal, i);
170172
InternalFilters.InternalBucket bucket = new InternalFilters.InternalBucket(keys[i], bucketDocCount(bucketOrd),
@@ -184,7 +186,7 @@ public InternalAggregation buildAggregation(long owningBucketOrdinal) throws IOE
184186
@Override
185187
public InternalAggregation buildEmptyAggregation() {
186188
InternalAggregations subAggs = buildEmptySubAggregations();
187-
List<InternalFilters.InternalBucket> buckets = new ArrayList<>(filters.length);
189+
List<InternalFilters.InternalBucket> buckets = new ArrayList<>(keys.length);
188190
for (int i = 0; i < keys.length; i++) {
189191
InternalFilters.InternalBucket bucket = new InternalFilters.InternalBucket(keys[i], 0, subAggs, keyed);
190192
buckets.add(bucket);

core/src/main/java/org/elasticsearch/search/aggregations/bucket/filter/FiltersAggregatorFactory.java

+30-6
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import org.apache.lucene.search.IndexSearcher;
2323
import org.apache.lucene.search.Query;
2424
import org.apache.lucene.search.Weight;
25+
import org.elasticsearch.search.aggregations.AggregationInitializationException;
2526
import org.elasticsearch.search.aggregations.Aggregator;
2627
import org.elasticsearch.search.aggregations.AggregatorFactories;
2728
import org.elasticsearch.search.aggregations.AggregatorFactory;
@@ -36,7 +37,8 @@
3637
public class FiltersAggregatorFactory extends AggregatorFactory<FiltersAggregatorFactory> {
3738

3839
private final String[] keys;
39-
final Weight[] weights;
40+
private final Query[] filters;
41+
private Weight[] weights;
4042
private final boolean keyed;
4143
private final boolean otherBucket;
4244
private final String otherBucketKey;
@@ -48,21 +50,43 @@ public FiltersAggregatorFactory(String name, List<KeyedFilter> filters, boolean
4850
this.keyed = keyed;
4951
this.otherBucket = otherBucket;
5052
this.otherBucketKey = otherBucketKey;
51-
IndexSearcher contextSearcher = context.searcher();
52-
weights = new Weight[filters.size()];
5353
keys = new String[filters.size()];
54+
this.filters = new Query[filters.size()];
5455
for (int i = 0; i < filters.size(); ++i) {
5556
KeyedFilter keyedFilter = filters.get(i);
5657
this.keys[i] = keyedFilter.key();
57-
Query filter = keyedFilter.filter().toFilter(context.getQueryShardContext());
58-
this.weights[i] = contextSearcher.createNormalizedWeight(filter, false);
58+
this.filters[i] = keyedFilter.filter().toFilter(context.getQueryShardContext());
5959
}
6060
}
6161

62+
/**
63+
* Returns the {@link Weight}s for this filter aggregation, creating it if
64+
* necessary. This is done lazily so that the {@link Weight}s are only
65+
* created if the aggregation collects documents reducing the overhead of
66+
* the aggregation in the case where no documents are collected.
67+
*
68+
* Note that as aggregations are initialsed and executed in a serial manner,
69+
* no concurrency considerations are necessary here.
70+
*/
71+
public Weight[] getWeights() {
72+
if (weights == null) {
73+
try {
74+
IndexSearcher contextSearcher = context.searcher();
75+
weights = new Weight[filters.length];
76+
for (int i = 0; i < filters.length; ++i) {
77+
this.weights[i] = contextSearcher.createNormalizedWeight(filters[i], false);
78+
}
79+
} catch (IOException e) {
80+
throw new AggregationInitializationException("Failed to initialse filters for aggregation [" + name() + "]", e);
81+
}
82+
}
83+
return weights;
84+
}
85+
6286
@Override
6387
public Aggregator createInternal(Aggregator parent, boolean collectsFromSingleBucket, List<PipelineAggregator> pipelineAggregators,
6488
Map<String, Object> metaData) throws IOException {
65-
return new FiltersAggregator(name, factories, keys, weights, keyed, otherBucket ? otherBucketKey : null, context, parent,
89+
return new FiltersAggregator(name, factories, keys, () -> getWeights(), keyed, otherBucket ? otherBucketKey : null, context, parent,
6690
pipelineAggregators, metaData);
6791
}
6892

core/src/main/java/org/elasticsearch/search/aggregations/bucket/nested/NestedAggregator.java

+1
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
package org.elasticsearch.search.aggregations.bucket.nested;
2020

2121
import com.carrotsearch.hppc.LongArrayList;
22+
2223
import org.apache.lucene.index.IndexReaderContext;
2324
import org.apache.lucene.index.LeafReaderContext;
2425
import org.apache.lucene.index.ReaderUtil;

core/src/test/java/org/elasticsearch/search/aggregations/bucket/filter/FilterAggregatorTests.java

+1-4
Original file line numberDiff line numberDiff line change
@@ -36,9 +36,6 @@
3636
import org.elasticsearch.index.query.QueryBuilders;
3737
import org.elasticsearch.search.aggregations.AggregatorFactory;
3838
import org.elasticsearch.search.aggregations.AggregatorTestCase;
39-
import org.elasticsearch.search.aggregations.bucket.filter.FilterAggregationBuilder;
40-
import org.elasticsearch.search.aggregations.bucket.filter.FilterAggregatorFactory;
41-
import org.elasticsearch.search.aggregations.bucket.filter.InternalFilter;
4239
import org.hamcrest.Matchers;
4340
import org.junit.Before;
4441

@@ -121,7 +118,7 @@ public void testParsedAsFilter() throws IOException {
121118
AggregatorFactory<?> factory = createAggregatorFactory(builder, indexSearcher, fieldType);
122119
assertThat(factory, Matchers.instanceOf(FilterAggregatorFactory.class));
123120
FilterAggregatorFactory filterFactory = (FilterAggregatorFactory) factory;
124-
Query parsedQuery = filterFactory.weight.getQuery();
121+
Query parsedQuery = filterFactory.getWeight().getQuery();
125122
assertThat(parsedQuery, Matchers.instanceOf(BooleanQuery.class));
126123
assertEquals(2, ((BooleanQuery) parsedQuery).clauses().size());
127124
// means the bool query has been parsed as a filter, if it was a query minShouldMatch would

core/src/test/java/org/elasticsearch/search/aggregations/bucket/filter/FiltersAggregatorTests.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -214,7 +214,7 @@ public void testParsedAsFilter() throws IOException {
214214
AggregatorFactory<?> factory = createAggregatorFactory(builder, indexSearcher, fieldType);
215215
assertThat(factory, Matchers.instanceOf(FiltersAggregatorFactory.class));
216216
FiltersAggregatorFactory filtersFactory = (FiltersAggregatorFactory) factory;
217-
Query parsedQuery = filtersFactory.weights[0].getQuery();
217+
Query parsedQuery = filtersFactory.getWeights()[0].getQuery();
218218
assertThat(parsedQuery, Matchers.instanceOf(BooleanQuery.class));
219219
assertEquals(2, ((BooleanQuery) parsedQuery).clauses().size());
220220
// means the bool query has been parsed as a filter, if it was a query minShouldMatch would

0 commit comments

Comments
 (0)