Skip to content
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

Do not plan unnecessary Sort #818

Merged
merged 3 commits into from May 28, 2019
Merged

Conversation

martint
Copy link
Member

@martint martint commented May 26, 2019

Avoid planning unnecessary sort operations. This fixes #759 and is an alternative to #810.

@martint
Copy link
Member Author

martint commented May 26, 2019

Examples:

ORDER BY at top-level

EXPLAIN
WITH t(x) AS (VALUES 1,2,3) 
SELECT * FROM t 
ORDER BY x
 - Output[x]
     - RemoteMerge[field ASC_NULLS_LAST]
         - LocalMerge[field ASC_NULLS_LAST]
             - PartialSort[field ASC_NULLS_LAST]
                 - RemoteExchange[REPARTITION]
                     - Values

Unnecessary ORDER BY in subquery query

EXPLAIN 
WITH t(x) AS (VALUES 1,2,3) 
SELECT * FROM (
    SELECT * FROM t 
    ORDER BY x
)
- Output[x]
     - Values

Necessary ORDER BY in subquery

EXPLAIN 
WITH t(x) AS (VALUES 1,2,3) 
SELECT * FROM (
    SELECT * FROM t 
    ORDER BY x 
    LIMIT 1
)
- Output[x]
     - TopN[1 by (field ASC_NULLS_LAST)]
         - LocalExchange[SINGLE] ()
             - TopNPartial[1 by (field ASC_NULLS_LAST)]
                 - LocalExchange[ROUND_ROBIN] ()
                     - Values

Unnecessary ORDER BY in scalar subquery

EXPLAIN 
WITH t(x) AS (VALUES 1,2,3) 
SELECT (SELECT x FROM t ORDER BY x) 
FROM (VALUES 10)
 - Output[_col0]
     - EnforceSingleRow
         - Values

Necessary ORDER BY in scalar subquery

EXPLAIN 
WITH t(x) AS (VALUES 1,2,3) 
SELECT (SELECT x FROM t ORDER BY x LIMIT 1) 
FROM (VALUES 10)
 - Output[_col0]
     - EnforceSingleRow
         - TopN[1 by (field_1 ASC_NULLS_LAST)]
             - LocalExchange[SINGLE] ()
                 - TopNPartial[1 by (field_1 ASC_NULLS_LAST)]
                     - LocalExchange[ROUND_ROBIN] ()
                         - Values

Unnecessary ORDER BY in aggregation query

EXPLAIN 
WITH t(x) AS (VALUES 1,2,3) 
SELECT array_agg(x) 
FROM (
    SELECT * FROM t 
    ORDER BY x
)
 - Output[_col0]
     - Aggregate
         - Values

Necessary ORDER BY in UNION ALL query

EXPLAIN
VALUES 1 
UNION ALL 
VALUES 2 
ORDER BY 1
 - Output[_col0]
     - RemoteMerge[field_1 ASC_NULLS_LAST]
         - LocalMerge[field_1 ASC_NULLS_LAST]
             - PartialSort[field_1 ASC_NULLS_LAST]
                 - RemoteExchange[REPARTITION]
                     - LocalExchange[ROUND_ROBIN] ()
                         - Values
                         - Values

Unnecessary ORDER BY in UNION ALL query

EXPLAIN
SELECT * FROM (
    VALUES 1 
    UNION ALL 
    VALUES 2 
    ORDER BY 1
)
 - Output[_col0]
     - LocalExchange[ROUND_ROBIN] ()
         - Values
         - Values

@martint martint force-pushed the orderby-analysis branch 4 times, most recently from e6a4b4f to a23f858 Compare May 26, 2019 05:53
@martint
Copy link
Member Author

martint commented May 26, 2019

With warnings:

Screen Shot 2019-05-25 at 11 33 27 PM

@martint martint removed the WIP label May 26, 2019
@martint martint force-pushed the orderby-analysis branch 2 times, most recently from fd1d0ff to 9c6f62f Compare May 26, 2019 15:13
@martint martint requested a review from sopel39 May 26, 2019 17:21
if (sourceScope.getOuterQueryParent().isPresent() && !node.getLimit().isPresent() && !node.getOffset().isPresent()) {
// not the root scope and ORDER BY is ineffective
analysis.markRedundantOrderBy(orderBy);
warningCollector.add(new PrestoWarning(REDUNDANT_ORDER_BY, "ORDER BY in subquery has no effect"));
Copy link
Member

Choose a reason for hiding this comment

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

as above

@@ -121,6 +121,7 @@
private boolean preferPartialAggregation = true;
private boolean optimizeTopNRowNumber = true;
private boolean workProcessorPipelines;
private boolean removeRedundantSort = true;
Copy link
Member

Choose a reason for hiding this comment

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

You added getter/setter last, but this isn't the last field. This class is messy.

Copy link
Member

Choose a reason for hiding this comment

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

Let's think again about example that you also mentioned in your blog:

SELECT *, row_number() OVER ()
FROM (SELECT * FROM nation ORDER BY name DESC)

the proper way of writing this is:

SELECT *, row_number() OVER (ORDER BY name DESC)
FROM nation

Is the proper way a drop-in replacement?
Before the change, the old way would use distributed sort.
Does the standard-compliant version use distributed sort too?

Copy link
Member

Choose a reason for hiding this comment

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

If the proper way is not equally performant yet, we shouldn't have the feature on by default just yet.

Copy link
Member Author

Choose a reason for hiding this comment

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

I believe we added distributed sort specifically for the unpartitioned window function case, so I think that should be the case. I’ll double check

Copy link
Member Author

Choose a reason for hiding this comment

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

You added getter/setter last, but this isn't the last field.

I added it right below "work processor pipelines" in both cases. The other ones are out of order.

This class is messy.

100% agree

Copy link
Member Author

@martint martint May 26, 2019

Choose a reason for hiding this comment

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

Is the proper way a drop-in replacement?
Before the change, the old way would use distributed sort.

Regardless, a query like this is just broken, semantically speaking. Almost every case I've seen is people relying on ORDER BY for aggregation queries, since 1) we didn't use to support ordered aggregates until sometime last year, 2) window functions have always supported ORDER BY.

For anyone relying on that behavior, there's the fallback of disabling the config flag (for now).

Copy link
Member Author

Choose a reason for hiding this comment

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

It turns out we currently don't run distributed sorts for unpartitioned window functions with an ORDER BY clause. Here's a PR to add it: #821

Copy link
Member

@sopel39 sopel39 May 27, 2019

Choose a reason for hiding this comment

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

I believe we added distributed sort specifically for the unpartitioned window function case, so I think that should be the case. I’ll double check

Distributed sort utilizes entire cluster memory, while WindowOperator does not (it collects partition rows).

Keep in mind that unordered row_number window function was replaced with RowNumberNode operator (See: io.prestosql.sql.planner.optimizations.WindowFilterPushDown.Rewriter#canReplaceWithRowNumber). This means that such hacky:

SELECT *, row_number() OVER ()
FROM (SELECT * FROM nation ORDER BY name DESC)

query utilizes distributed sort and skips WindowOperator completely. However, for:

SELECT *, row_number() OVER (ORDER BY name DESC)
FROM nation

It will still execute WindowOperator which will collect all partition data (thus mitigating benefits of distributed sort).

Maybe we should improve WindowFilterPushDown rule too

Copy link
Member

Choose a reason for hiding this comment

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

@sopel39 good point. So in #821 when insert Sort below WindowNode, we could convert the WindowNode to RowNumberNode (in row_number() case of course). (I think we should move continue this discussion under #821 though).

Copy link
Member Author

Choose a reason for hiding this comment

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

BTW, for this shape:

SELECT *, row_number() OVER ()
FROM (SELECT * FROM nation ORDER BY name DESC)

even if the ORDER BY weren't removed, there's no guarantee that the row_number() function (or any other window function, for that matter) will not load all the data in memory and shuffle it in some way before producing its results. So it could break at any time for other reasons unrelated to this optimization, so I'm not too concerned about it, especially since we have an escape hatch.

@martint
Copy link
Member Author

martint commented May 27, 2019

Updated.

Copy link
Member

@findepi findepi left a comment

Choose a reason for hiding this comment

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

LGTM except for "Clean up analysis of ORDER BY" commit which I didn't review.

The calls to analysis.setOrderByExpressions() were spread out across
the caller and the analyzeOrderBy methods. This puts them in a single
place, so it's easier to reason about. Also, inline unnecessary
methods
According to the SQL specification, ORDER BY is only meaningful for the immediate
query expression that contains it. Since the only operations that are affected
by ORDER BY are FETCH FIRST/LIMIT and OFFSET, an ORDER BY clause in a subquery
has no effect.

With this change, the analyzer marks those ORDER BY clauses as redundant and the
planner skips adding a Sort node.

If a redundant ORDER BY is present, the analyzer will emit a warning.

To ease the transition for users that may rely on the old behavior, the new
behavior can be disabled via a session property or config option.
@martint martint merged commit 9a1fdf2 into trinodb:master May 28, 2019
@findepi findepi modified the milestones: 313, 312 Jun 3, 2019
fgwang7w added a commit to fgwang7w/presto that referenced this pull request Dec 14, 2020
Cherry-pick of trinodb/trino#818

Co-author: Martin Traverso <mtraverso@gmail.com>
fgwang7w added a commit to fgwang7w/presto that referenced this pull request Dec 28, 2020
Cherry-pick of trinodb/trino#818

Co-author: Martin Traverso <mtraverso@gmail.com>
rschlussel pushed a commit to prestodb/presto that referenced this pull request Dec 28, 2020
Cherry-pick of trinodb/trino#818

Co-author: Martin Traverso <mtraverso@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Development

Successfully merging this pull request may close these issues.

Remove futile sort operations in sub queries
5 participants