-
Notifications
You must be signed in to change notification settings - Fork 25.6k
Aggregations cancellation after collection #120944
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 11 commits
b78f195
5751f45
ca451b8
18870c4
250bc9b
696fdfc
32f079c
a4cabaf
ec66b50
8181260
bdc6516
7eb1966
7e213b6
6454286
a0485b5
c8024e7
0231c5a
7d811a0
4a45481
f2b240f
6f51925
8359e4e
2869310
dc526a8
e11ac1e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| pr: 120944 | ||
| summary: Aggregations cancellation after collection | ||
| area: Aggregations | ||
| type: bug | ||
| issues: [] |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -129,6 +129,8 @@ | |
| import org.elasticsearch.search.SearchModule; | ||
| import org.elasticsearch.search.aggregations.AggregatorFactories.Builder; | ||
| import org.elasticsearch.search.aggregations.MultiBucketConsumerService.MultiBucketConsumer; | ||
| import org.elasticsearch.search.aggregations.bucket.MultiBucketsAggregation; | ||
| import org.elasticsearch.search.aggregations.bucket.SingleBucketAggregation; | ||
| import org.elasticsearch.search.aggregations.bucket.nested.NestedAggregationBuilder; | ||
| import org.elasticsearch.search.aggregations.metrics.MetricsAggregator; | ||
| import org.elasticsearch.search.aggregations.metrics.MultiValueAggregation; | ||
|
|
@@ -149,6 +151,7 @@ | |
| import org.elasticsearch.search.internal.SearchContext; | ||
| import org.elasticsearch.search.internal.ShardSearchRequest; | ||
| import org.elasticsearch.search.internal.SubSearchContext; | ||
| import org.elasticsearch.tasks.TaskCancelledException; | ||
| import org.elasticsearch.test.ESTestCase; | ||
| import org.elasticsearch.test.InternalAggregationTestCase; | ||
| import org.elasticsearch.threadpool.TestThreadPool; | ||
|
|
@@ -251,29 +254,12 @@ protected List<SearchPlugin> getSearchPlugins() { | |
| return List.of(); | ||
| } | ||
|
|
||
| /** | ||
| * Deprecated - this will be made private in a future update | ||
| */ | ||
| @Deprecated | ||
| protected <A extends Aggregator> A createAggregator( | ||
| AggregationBuilder aggregationBuilder, | ||
| IndexReader indexReader, | ||
| MappedFieldType... fieldTypes | ||
| ) throws IOException { | ||
| return createAggregator(aggregationBuilder, createAggregationContext(indexReader, new MatchAllDocsQuery(), fieldTypes)); | ||
| } | ||
|
|
||
| protected <A extends Aggregator> A createAggregator(AggregationBuilder aggregationBuilder, AggregationContext context) | ||
| throws IOException { | ||
| return createAggregator(new AggregatorFactories.Builder().addAggregator(aggregationBuilder), context); | ||
| } | ||
|
|
||
| /** | ||
| * Deprecated - this will be made private in a future update | ||
| */ | ||
| @Deprecated | ||
| protected <A extends Aggregator> A createAggregator(AggregatorFactories.Builder builder, AggregationContext context) | ||
| throws IOException { | ||
| private <A extends Aggregator> A createAggregator(AggregatorFactories.Builder builder, AggregationContext context) throws IOException { | ||
| Aggregator[] aggregators = builder.build(context, null).createTopLevelAggregators(); | ||
| assertThat(aggregators.length, equalTo(1)); | ||
| @SuppressWarnings("unchecked") | ||
|
|
@@ -310,11 +296,8 @@ protected AggregationContext createAggregationContext(IndexReader indexReader, Q | |
| * While {@linkplain AggregationContext} is {@link Releasable} the caller is | ||
| * not responsible for releasing it. Instead, it is released automatically in | ||
| * in {@link #cleanupReleasables()}. | ||
| * | ||
| * Deprecated - this will be made private in a future update | ||
| */ | ||
| @Deprecated | ||
| protected AggregationContext createAggregationContext( | ||
| private AggregationContext createAggregationContext( | ||
| IndexReader indexReader, | ||
| IndexSettings indexSettings, | ||
| Query query, | ||
|
|
@@ -346,6 +329,56 @@ private AggregationContext createAggregationContext( | |
| int maxBucket, | ||
| boolean isInSortOrderExecutionRequired, | ||
| MappedFieldType... fieldTypes | ||
| ) { | ||
| return createAggregationContext( | ||
| searcher, | ||
| indexSettings, | ||
| query, | ||
| breakerService, | ||
| bytesToPreallocate, | ||
| maxBucket, | ||
| isInSortOrderExecutionRequired, | ||
| () -> false, | ||
| fieldTypes | ||
| ); | ||
| } | ||
|
|
||
| /** | ||
| * Creates an aggregation context that will randomly report that the query has been cancelled | ||
| */ | ||
| private AggregationContext createCancellingAggregationContext( | ||
| IndexSearcher searcher, | ||
| IndexSettings indexSettings, | ||
| Query query, | ||
| CircuitBreakerService breakerService, | ||
| long bytesToPreallocate, | ||
| int maxBucket, | ||
| boolean isInSortOrderExecutionRequired, | ||
| MappedFieldType... fieldTypes | ||
| ) { | ||
| return createAggregationContext( | ||
| searcher, | ||
| indexSettings, | ||
| query, | ||
| breakerService, | ||
| bytesToPreallocate, | ||
| maxBucket, | ||
| isInSortOrderExecutionRequired, | ||
| () -> ESTestCase.random().nextInt(20) == 0, | ||
| fieldTypes | ||
| ); | ||
| } | ||
|
|
||
| private AggregationContext createAggregationContext( | ||
| IndexSearcher searcher, | ||
| IndexSettings indexSettings, | ||
| Query query, | ||
| CircuitBreakerService breakerService, | ||
| long bytesToPreallocate, | ||
| int maxBucket, | ||
| boolean isInSortOrderExecutionRequired, | ||
| Supplier<Boolean> isCancelled, | ||
| MappedFieldType... fieldTypes | ||
| ) { | ||
| MappingLookup mappingLookup = MappingLookup.fromMappers( | ||
| Mapping.EMPTY, | ||
|
|
@@ -409,7 +442,7 @@ public Iterable<MappedFieldType> dimensionFields() { | |
| bitsetFilterCache, | ||
| randomInt(), | ||
| () -> 0L, | ||
| () -> false, | ||
| isCancelled, | ||
| q -> q, | ||
| true, | ||
| isInSortOrderExecutionRequired | ||
|
|
@@ -536,9 +569,11 @@ protected <A extends InternalAggregation> A searchAndReduce(IndexReader reader, | |
| IndexSettings indexSettings = createIndexSettings(); | ||
| // First run it to find circuit breaker leaks on the aggregator | ||
| runWithCrankyCircuitBreaker(indexSettings, searcher, aggTestConfig); | ||
| // Second run it to the end | ||
| CircuitBreakerService breakerService = new NoneCircuitBreakerService(); | ||
| return searchAndReduce(indexSettings, searcher, breakerService, aggTestConfig); | ||
| // Next, try with random cancellations, again looking for leaks | ||
| runWithCancellingConfig(indexSettings, searcher, breakerService, aggTestConfig); | ||
| // Finally, run it to the end | ||
| return searchAndReduce(indexSettings, searcher, breakerService, aggTestConfig, this::createAggregationContext); | ||
| } | ||
|
|
||
| /** | ||
|
|
@@ -552,20 +587,51 @@ private void runWithCrankyCircuitBreaker(IndexSettings indexSettings, IndexSearc | |
| CircuitBreakerService crankyService = new CrankyCircuitBreakerService(); | ||
| for (int i = 0; i < 5; i++) { | ||
| try { | ||
| searchAndReduce(indexSettings, searcher, crankyService, aggTestConfig); | ||
| searchAndReduce(indexSettings, searcher, crankyService, aggTestConfig, this::createAggregationContext); | ||
| } catch (CircuitBreakingException e) { | ||
| // Circuit breaks from the cranky breaker are expected - it randomly fails, after all | ||
| assertThat(e.getMessage(), equalTo(CrankyCircuitBreakerService.ERROR_MESSAGE)); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| private void runWithCancellingConfig( | ||
| IndexSettings indexSettings, | ||
| IndexSearcher searcher, | ||
| CircuitBreakerService breakerService, | ||
| AggTestConfig aggTestConfig | ||
| ) throws IOException { | ||
| for (int i = 0; i < 5; i++) { | ||
| try { | ||
| searchAndReduce(indexSettings, searcher, breakerService, aggTestConfig, this::createCancellingAggregationContext); | ||
| } catch (TaskCancelledException e) { | ||
| // we don't want to expectThrows this because the randomizer might just never report cancellation, | ||
| // but it's also normal that it should throw here. | ||
| } | ||
| } | ||
| } | ||
|
|
||
| @FunctionalInterface | ||
| public interface AggregationcContextSupplier { | ||
| AggregationContext get( | ||
| IndexSearcher searcher, | ||
| IndexSettings indexSettings, | ||
| Query query, | ||
| CircuitBreakerService breakerService, | ||
| long bytesToPreallocate, | ||
| int maxBucket, | ||
| boolean isInSortOrderExecutionRequired, | ||
| MappedFieldType... fieldTypes | ||
| ); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That's a ton of stuff to pass in. I feel like some of it, like There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think it's fine actually - it lines up with how everything else is working now. |
||
| } | ||
|
|
||
| @SuppressWarnings("unchecked") | ||
| private <A extends InternalAggregation, C extends Aggregator> A searchAndReduce( | ||
| IndexSettings indexSettings, | ||
| IndexSearcher searcher, | ||
| CircuitBreakerService breakerService, | ||
| AggTestConfig aggTestConfig | ||
| AggTestConfig aggTestConfig, | ||
| AggregationcContextSupplier contextSupplier | ||
| ) throws IOException { | ||
| Query query = aggTestConfig.query(); | ||
| AggregatorFactories.Builder builder = new AggregatorFactories.Builder().addAggregator(aggTestConfig.builder()); | ||
|
|
@@ -591,7 +657,7 @@ private <A extends InternalAggregation, C extends Aggregator> A searchAndReduce( | |
| subSearchers[searcherIDX] = new ShardSearcher(leave, compCTX); | ||
| } | ||
| for (ShardSearcher subSearcher : subSearchers) { | ||
| AggregationContext context = createAggregationContext( | ||
| AggregationContext context = contextSupplier.get( | ||
| subSearcher, | ||
| indexSettings, | ||
| query, | ||
|
|
@@ -620,7 +686,7 @@ private <A extends InternalAggregation, C extends Aggregator> A searchAndReduce( | |
| } | ||
| } | ||
| } else { | ||
| AggregationContext context = createAggregationContext( | ||
| AggregationContext context = contextSupplier.get( | ||
| searcher, | ||
| indexSettings, | ||
| query, | ||
|
|
@@ -688,16 +754,47 @@ private <A extends InternalAggregation, C extends Aggregator> A searchAndReduce( | |
| assertRoundTrip(internalAggregation.copyResults()); | ||
| } | ||
| } | ||
| /* Verify that cancellation during final reduce correctly throws. | ||
| * We check reduce time cancellation only when consuming buckets. | ||
| */ | ||
| try { | ||
| // I can't remember if we mutate the InternalAggregations list, so make a defensive copy | ||
| List<InternalAggregations> internalAggsCopy = new ArrayList<>(internalAggs); | ||
| A internalAgg = doFinalReduce(maxBucket, bigArraysForReduction, builder, internalAggsCopy, true); | ||
| if (internalAgg instanceof MultiBucketsAggregation mb) { | ||
| // Empty mutli-bucket aggs are expected to return before even getting to the cancellation check | ||
| assertEquals("Got non-empty result for a cancelled reduction", 0, mb.getBuckets().size()); | ||
| } // other cases? | ||
| } catch (TaskCancelledException e) { | ||
| /* We may not always honor cancellation in reduce, for example if we are returning no results, so we can't | ||
| * just expectThrows here. | ||
| */ | ||
| } | ||
|
|
||
| // now do the final reduce | ||
| A internalAgg = doFinalReduce(maxBucket, bigArraysForReduction, builder, internalAggs, false); | ||
| assertRoundTrip(internalAgg); | ||
| if (aggTestConfig.builder instanceof ValuesSourceAggregationBuilder.MetricsAggregationBuilder<?>) { | ||
| verifyMetricNames((ValuesSourceAggregationBuilder.MetricsAggregationBuilder<?>) aggTestConfig.builder, internalAgg); | ||
| } | ||
| return internalAgg; | ||
| } | ||
|
|
||
| private <A extends InternalAggregation> A doFinalReduce( | ||
| int maxBucket, | ||
| BigArrays bigArraysForReduction, | ||
| Builder builder, | ||
| List<InternalAggregations> internalAggs, | ||
| boolean cancelled | ||
| ) throws IOException { | ||
| MultiBucketConsumer reduceBucketConsumer = new MultiBucketConsumer( | ||
| maxBucket, | ||
| new NoneCircuitBreakerService().getBreaker(CircuitBreaker.REQUEST) | ||
| ); | ||
| AggregationReduceContext reduceContext = new AggregationReduceContext.ForFinal( | ||
| bigArraysForReduction, | ||
| getMockScriptService(), | ||
| () -> false, | ||
| () -> cancelled, | ||
| builder, | ||
| reduceBucketConsumer | ||
| ); | ||
|
|
@@ -707,10 +804,6 @@ private <A extends InternalAggregation, C extends Aggregator> A searchAndReduce( | |
| assertRoundTrip(internalAgg); | ||
|
|
||
| doAssertReducedMultiBucketConsumer(internalAgg, reduceBucketConsumer); | ||
| assertRoundTrip(internalAgg); | ||
| if (aggTestConfig.builder instanceof ValuesSourceAggregationBuilder.MetricsAggregationBuilder<?>) { | ||
| verifyMetricNames((ValuesSourceAggregationBuilder.MetricsAggregationBuilder<?>) aggTestConfig.builder, internalAgg); | ||
| } | ||
| return internalAgg; | ||
| } | ||
|
|
||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: It is not clear to me why we are handling auto-date histogram in a special way. Could we add a comment explaining the why?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure! Its just that it spends a while spinning around doing extra stuff before it's ready for the normal build.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
And I found it from an actual bug report of folks that were trying to terminate aggs and the thread was stuck here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
you have not one but three typos in that sentence :D