Skip to content

ESQL: Change the order of the optimization rules #124335

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

Merged
merged 13 commits into from
Mar 13, 2025

Conversation

astefan
Copy link
Contributor

@astefan astefan commented Mar 7, 2025

In a query like INLINESTATS max_lang = MAX(languages) BY y = gender the "rename" of gender as y was replaced by an eval y = gender and then integrated back as a "projection" inside the aggregate itself. ReplaceAggregateNestedExpressionWithEval and CombineProjections seem to work one against the other. The former adds the eval while the latter eliminates it and puts it back in the groupings of the aggregation.

I have changed the order in which ReplaceOrderByExpressionWithEval and PropagateInlineEvals rules were called in the logical optimizer so that the eval mentioned above is not integrated back in the aggregation.

@astefan astefan added >bug :Analytics/ES|QL AKA ESQL auto-backport Automatically create backport pull requests when merged v9.0.1 v9.1.0 labels Mar 7, 2025
@elasticsearchmachine elasticsearchmachine added the Team:Analytics Meta label for analytical engine team (ESQL/Aggs/Geo) label Mar 7, 2025
@elasticsearchmachine
Copy link
Collaborator

Pinging @elastic/es-analytical-engine (Team:Analytics)

@elasticsearchmachine
Copy link
Collaborator

Hi @astefan, I've created a changelog YAML for you.

@@ -695,3 +695,115 @@ emp_no:integer | languages:integer | gender:keyword | max_lang:integer | y:keywo
10012 | 5 | null | 5 | null
10014 | 5 | null | 5 | null
;

Copy link
Contributor Author

@astefan astefan Mar 7, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some of the following tests fail with the same error. This is likely due to something that @alex-spies caught in a previous review, meaning too much is sent to the data nodes for processing, instead of being done on the coordinator. This is also tied to scenarios where multiple inlinestats commands are used in a query.

@@ -34,7 +34,6 @@ protected LogicalPlan rule(InlineJoin plan) {
// check if there's any grouping that uses a reference on the right side
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Only a small cleanup here.

@astefan astefan requested review from alex-spies and costin March 7, 2025 14:49
Copy link
Member

@costin costin left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch.
To double check my understanding : the eval being integrated as a project is fine (since the eval is just an alias) however in the case of inline the eval is needed stand alone to be propagated on the left side so the join works by returning the grouped column with the right alias.

Did I miss anything?
A logical optimization test might be useful that calls the two rules (three) rules one after the other to showcase the side effect.

Thanks!

Copy link
Contributor

@alex-spies alex-spies left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is nice and should be merged for the added test coverage alone.

However, did you maybe consider a solution where we make ReplaceAggregateNestedExpressionWithEval aware of whether it is applied inside the right hand side of InlineJoin? The root problem seems to me that this rule creates an Eval that should be applied not on the the right hand side, but to the left hand side of an InlineJoin - and we require a whole additional optimizer rule, namely PropagateInlineEvals applied in the correct order without runs of ReplaceAliasingEvalWithProject resp. CombineProjections for this to work - which I find a little brittle.

if (groupingRefs.isEmpty()) {
return p;
}

// find their declaration and remove it
// TODO: this doesn't take into account aliasing
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This comment may be stale now.

FROM employees
| KEEP emp_no, languages, gender, first_name
| RENAME first_name as f
| INLINESTATS max_lang = MAX(languages) BY y = gender, l = languages, f = left(f, 1)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Subtle semantic question:

The implementation assumes that expressions in the BY clause take effect in the main branch of the join as well. I.e. INLINESTATS ... BY ..., f = left(f, 1) means that the attribute f in the left hand side gets overwritten before being aggregated by/joined on.

But there's a different way to understand this, and it's actually useful: INLINESTATS ... BY ..., f = left(f, 1) could mean that f is overwritten only on the right hand side where the aggregation happens, and then is matched up with the left hand side.

I don't think we want the latter to be the semantics here. But that could be useful in case that e.g. the left hand side has a counter field that indicates the current row (or similar) - then INLINESTATS ... BY counter = counter + 1 would actually shift the stats by 1 row.

@astefan
Copy link
Contributor Author

astefan commented Mar 12, 2025

To double check my understanding : the eval being integrated as a project is fine (since the eval is just an alias) however in the case of inline the eval is needed stand alone to be propagated on the left side so the join works by returning the grouped column with the right alias.

Yes.

However, did you maybe consider a solution where we make ReplaceAggregateNestedExpressionWithEval aware of whether it is applied inside the right hand side of InlineJoin? The root problem seems to me that this rule creates an Eval that should be applied not on the the right hand side, but to the left hand side of an InlineJoin - and we require a whole additional optimizer rule, namely PropagateInlineEvals applied in the correct order without runs of ReplaceAliasingEvalWithProject resp. CombineProjections for this to work - which I find a little brittle.

I did consider another solution, but I discarded it because it didn't feel the right approach.
My first solution to this bug was to change PropagateInlineEval to also consider Projects like the ones created by ReplaceAliasingEvalWithProject for an eval that comes from ReplaceAggregateNestedExpressionWithEval. This is for grouping by expressions, ie inlinestats ... by y = gender. But this rule is about evals in any form, extending it to Project even if this type of eval was only an aliasing felt wrong. Testing more complicated queries that perform various forms of aliasing in the inlinestats ... by .... groupings part seem to support the current chosen solution in the PR.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just realized that it's probably a good idea to add tests for INLINESTATS ... BY x = bucket(...) and INLINESTATS ... BY x = CATEGORIZE(...). I think the latter cannot work because the join key in BY x = CATEGORIZE(...) is computed during the aggregation, whereas INLINESTATS requires the join key to be present before that.

Cc @jan-elastic , I think we'll have to start out with a limitation where INLINESTATS can't use CATEGORIZE, at least at first; to enable this, I think we'd somehow have to grab the categorizer from the first phase of the query (which computes the STATS) and make it available to the second phase of the query (which performs the joining with every row we see).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FWIW, I can live with the fact that the first version of INLINESTATS doesn't work with CATEGORIZE.

Just open a GitHub issue for that and it can be resolved later.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I opened #124717

@alex-spies
Copy link
Contributor

alex-spies commented Mar 12, 2025

I did consider another solution, but I discarded it because it didn't feel the right approach.
My first solution to this bug was to change PropagateInlineEval to also consider Projects like the ones created by ReplaceAliasingEvalWithProject for an eval that comes from ReplaceAggregateNestedExpressionWithEval. This is for grouping by expressions, ie inlinestats ... by y = gender. But this rule is about evals in any form, extending it to Project even if this type of eval was only an aliasing felt wrong. Testing more complicated queries that perform various forms of aliasing in the inlinestats ... by .... groupings part seem to support the current chosen solution in the PR.

This is not the alternative I'm proposing. What I observed is that PropagateInlineEvals just fixes a mistake made by ReplaceAggregateNestedExpressionWithEval: the latter is not aware of InlineJoin and thus adds its extra EVAL for an expression like INLINESTATS ... BY y = foo(...) to the wrong side of the join. Then PropagateInlineEvals scoops that EVAL up and throws it onto the correct side, but has to be more defensive and needs to carefully pick apart the EVAL that we could've known doesn't belong there in the first place.

Or to put it differently and bluntly: I think PropagateInlineEvals shouldn't exist, rather we should fix ReplaceAggregateNestedExpressionWithEval.

But that blows up the scope of this PR. I'll make a note to revisit this later - also, the general shape of the code for INLINESTATS may change over the course of the next weeks, so it's not worth pursuing this now.

Copy link
Contributor

@alex-spies alex-spies left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM. Thanks for the iterations, Andrei!

@astefan astefan merged commit c48f9a9 into elastic:main Mar 13, 2025
17 checks passed
@astefan astefan deleted the inlinestats_pickup3 branch March 13, 2025 05:46
@elasticsearchmachine
Copy link
Collaborator

💚 Backport successful

Status Branch Result
9.0

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
:Analytics/ES|QL AKA ESQL >bug Team:Analytics Meta label for analytical engine team (ESQL/Aggs/Geo) v9.0.1 v9.1.0
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants