Skip to content

More CommonSubplanOptimizer improvements#20168

Merged
lnkuiper merged 34 commits intoduckdb:mainfrom
lnkuiper:common_subplan
Dec 15, 2025
Merged

More CommonSubplanOptimizer improvements#20168
lnkuiper merged 34 commits intoduckdb:mainfrom
lnkuiper:common_subplan

Conversation

@lnkuiper
Copy link
Collaborator

This PR improves the CommonSubplanOptimizer, and should put it in "maintenance mode", at least for now, and I think I'm done improving it for a while.

Multiple Nested Matching

This PR allows multiple subplans to be matched, rather than just a single subplan, and allows nesting. Take the following query, for example:

explain
select distinct range from range(10)
union all
select distinct range from range(10)
union all
select range % 2 as range from (select distinct range from range(10)) group by range
union all
select range % 2 as range from (select distinct range from range(10)) group by range
union all
select count(*) from (select range % 2 as range from (select distinct range from range(10)) group by range)
union all
select count(*) from (select range % 2 as range from (select distinct range from range(10)) group by range);

This query unions 6 queries together. Each query occurs twice, and is nested in the next query. With the optimizer disabled, this yields the following plan:

┌───────────────────────────┐
│           UNION           ├──────────────┬────────────────────────────┬────────────────────────────┬────────────────────────────┬────────────────────────────┐
└─────────────┬─────────────┘              │                            │                            │                            │                            │
┌─────────────┴─────────────┐┌─────────────┴─────────────┐┌─────────────┴─────────────┐┌─────────────┴─────────────┐┌─────────────┴─────────────┐┌─────────────┴─────────────┐
│       HASH_GROUP_BY       ││       HASH_GROUP_BY       ││         PROJECTION        ││         PROJECTION        ││    UNGROUPED_AGGREGATE    ││    UNGROUPED_AGGREGATE    │
│    ────────────────────   ││    ────────────────────   ││    ────────────────────   ││    ────────────────────   ││    ────────────────────   ││    ────────────────────   │
│         Groups: #0        ││         Groups: #0        ││           range           ││           range           ││        Aggregates:        ││        Aggregates:        │
│                           ││                           ││                           ││                           ││        count_star()       ││        count_star()       │
│          ~10 rows         ││          ~10 rows         ││          ~6 rows          ││          ~6 rows          ││                           ││                           │
└─────────────┬─────────────┘└─────────────┬─────────────┘└─────────────┬─────────────┘└─────────────┬─────────────┘└─────────────┬─────────────┘└─────────────┬─────────────┘
┌─────────────┴─────────────┐┌─────────────┴─────────────┐┌─────────────┴─────────────┐┌─────────────┴─────────────┐┌─────────────┴─────────────┐┌─────────────┴─────────────┐
│         PROJECTION        ││         PROJECTION        ││       HASH_GROUP_BY       ││       HASH_GROUP_BY       ││         PROJECTION        ││         PROJECTION        │
│    ────────────────────   ││    ────────────────────   ││    ────────────────────   ││    ────────────────────   ││    ────────────────────   ││    ────────────────────   │
│           range           ││           range           ││         Groups: #0        ││         Groups: #0        ││             42            ││             42            │
│                           ││                           ││                           ││                           ││                           ││                           │
│          ~10 rows         ││          ~10 rows         ││          ~6 rows          ││          ~6 rows          ││          ~6 rows          ││          ~6 rows          │
└─────────────┬─────────────┘└─────────────┬─────────────┘└─────────────┬─────────────┘└─────────────┬─────────────┘└─────────────┬─────────────┘└─────────────┬─────────────┘
┌─────────────┴─────────────┐┌─────────────┴─────────────┐┌─────────────┴─────────────┐┌─────────────┴─────────────┐┌─────────────┴─────────────┐┌─────────────┴─────────────┐
│           RANGE           ││           RANGE           ││         PROJECTION        ││         PROJECTION        ││       HASH_GROUP_BY       ││       HASH_GROUP_BY       │
│    ────────────────────   ││    ────────────────────   ││    ────────────────────   ││    ────────────────────   ││    ────────────────────   ││    ────────────────────   │
│      Function: RANGE      ││      Function: RANGE      ││           range           ││           range           ││         Groups: #0        ││         Groups: #0        │
│                           ││                           ││                           ││                           ││                           ││                           │
│          ~10 rows         ││          ~10 rows         ││          ~10 rows         ││          ~10 rows         ││          ~6 rows          ││          ~6 rows          │
└───────────────────────────┘└───────────────────────────┘└─────────────┬─────────────┘└─────────────┬─────────────┘└─────────────┬─────────────┘└─────────────┬─────────────┘
                                                          ┌─────────────┴─────────────┐┌─────────────┴─────────────┐┌─────────────┴─────────────┐┌─────────────┴─────────────┐
                                                          │       HASH_GROUP_BY       ││       HASH_GROUP_BY       ││         PROJECTION        ││         PROJECTION        │
                                                          │    ────────────────────   ││    ────────────────────   ││    ────────────────────   ││    ────────────────────   │
                                                          │         Groups: #0        ││         Groups: #0        ││           range           ││           range           │
                                                          │                           ││                           ││                           ││                           │
                                                          │          ~10 rows         ││          ~10 rows         ││          ~10 rows         ││          ~10 rows         │
                                                          └─────────────┬─────────────┘└─────────────┬─────────────┘└─────────────┬─────────────┘└─────────────┬─────────────┘
                                                          ┌─────────────┴─────────────┐┌─────────────┴─────────────┐┌─────────────┴─────────────┐┌─────────────┴─────────────┐
                                                          │         PROJECTION        ││         PROJECTION        ││       HASH_GROUP_BY       ││       HASH_GROUP_BY       │
                                                          │    ────────────────────   ││    ────────────────────   ││    ────────────────────   ││    ────────────────────   │
                                                          │           range           ││           range           ││         Groups: #0        ││         Groups: #0        │
                                                          │                           ││                           ││                           ││                           │
                                                          │          ~10 rows         ││          ~10 rows         ││          ~10 rows         ││          ~10 rows         │
                                                          └─────────────┬─────────────┘└─────────────┬─────────────┘└─────────────┬─────────────┘└─────────────┬─────────────┘
                                                          ┌─────────────┴─────────────┐┌─────────────┴─────────────┐┌─────────────┴─────────────┐┌─────────────┴─────────────┐
                                                          │           RANGE           ││           RANGE           ││         PROJECTION        ││         PROJECTION        │
                                                          │    ────────────────────   ││    ────────────────────   ││    ────────────────────   ││    ────────────────────   │
                                                          │      Function: RANGE      ││      Function: RANGE      ││           range           ││           range           │
                                                          │                           ││                           ││                           ││                           │
                                                          │          ~10 rows         ││          ~10 rows         ││          ~10 rows         ││          ~10 rows         │
                                                          └───────────────────────────┘└───────────────────────────┘└─────────────┬─────────────┘└─────────────┬─────────────┘
                                                                                                                    ┌─────────────┴─────────────┐┌─────────────┴─────────────┐
                                                                                                                    │           RANGE           ││           RANGE           │
                                                                                                                    │    ────────────────────   ││    ────────────────────   │
                                                                                                                    │      Function: RANGE      ││      Function: RANGE      │
                                                                                                                    │                           ││                           │
                                                                                                                    │          ~10 rows         ││          ~10 rows         │
                                                                                                                    └───────────────────────────┘└───────────────────────────┘

As we can see, there is a lot of redundance here. With this PR (and the optimizer enabled, of course), we get the following plan:

┌───────────────────────┐
│          CTE          │
│    ────────────────   │
│       CTE Name:       │
│   __common_subplan_1  │
│                       ├────────────┐
│    Table Index: 79    │            │
│                       │            │
│        ~0 rows        │            │
└───────────┬───────────┘            │
┌───────────┴───────────┐┌───────────┴───────────┐
│     HASH_GROUP_BY     ││          CTE          │
│    ────────────────   ││    ────────────────   │
│       Groups: #0      ││       CTE Name:       │
│                       ││   __common_subplan_2  │
│                       ││                       ├────────────┐
│                       ││    Table Index: 86    │            │
│                       ││                       │            │
│        ~10 rows       ││        ~0 rows        │            │
└───────────┬───────────┘└───────────┬───────────┘            │
┌───────────┴───────────┐┌───────────┴───────────┐┌───────────┴───────────┐
│       PROJECTION      ││     HASH_GROUP_BY     ││          CTE          │
│    ────────────────   ││    ────────────────   ││    ────────────────   │
│         range         ││       Groups: #0      ││       CTE Name:       │
│                       ││                       ││   __common_subplan_3  │
│                       ││                       ││                       ├────────────┐
│                       ││                       ││    Table Index: 91    │            │
│                       ││                       ││                       │            │
│        ~10 rows       ││        ~6 rows        ││        ~0 rows        │            │
└───────────┬───────────┘└───────────┬───────────┘└───────────┬───────────┘            │
┌───────────┴───────────┐┌───────────┴───────────┐┌───────────┴───────────┐┌───────────┴───────────┐
│         RANGE         ││       PROJECTION      ││  UNGROUPED_AGGREGATE  ││         UNION         │
│    ────────────────   ││    ────────────────   ││    ────────────────   ││                       │
│    Function: RANGE    ││         range         ││      Aggregates:      ││                       ├────────────┬────────────────────────┬────────────────────────┬────────────────────────┬────────────────────────┐
│                       ││                       ││      count_star()     ││                       │            │                        │                        │                        │                        │
│        ~10 rows       ││        ~10 rows       ││                       ││                       │            │                        │                        │                        │                        │
└───────────────────────┘└───────────┬───────────┘└───────────┬───────────┘└───────────┬───────────┘            │                        │                        │                        │                        │
                         ┌───────────┴───────────┐┌───────────┴───────────┐┌───────────┴───────────┐┌───────────┴───────────┐┌───────────┴───────────┐┌───────────┴───────────┐┌───────────┴───────────┐┌───────────┴───────────┐
                         │        CTE_SCAN       ││       PROJECTION      ││        CTE_SCAN       ││        CTE_SCAN       ││       PROJECTION      ││       PROJECTION      ││        CTE_SCAN       ││        CTE_SCAN       │
                         │    ────────────────   ││    ────────────────   ││    ────────────────   ││    ────────────────   ││    ────────────────   ││    ────────────────   ││    ────────────────   ││    ────────────────   │
                         │     CTE Index: 79     ││           42          ││     CTE Index: 79     ││     CTE Index: 79     ││         range         ││         range         ││     CTE Index: 91     ││     CTE Index: 91     │
                         │                       ││                       ││                       ││                       ││                       ││                       ││                       ││                       │
                         │        ~10 rows       ││        ~6 rows        ││        ~10 rows       ││        ~10 rows       ││        ~6 rows        ││        ~6 rows        ││         ~1 row        ││         ~1 row        │
                         └───────────────────────┘└───────────┬───────────┘└───────────────────────┘└───────────────────────┘└───────────┬───────────┘└───────────┬───────────┘└───────────────────────┘└───────────────────────┘
                                                  ┌───────────┴───────────┐                                                  ┌───────────┴───────────┐┌───────────┴───────────┐
                                                  │        CTE_SCAN       │                                                  │        CTE_SCAN       ││        CTE_SCAN       │
                                                  │    ────────────────   │                                                  │    ────────────────   ││    ────────────────   │
                                                  │     CTE Index: 86     │                                                  │     CTE Index: 86     ││     CTE Index: 86     │
                                                  │                       │                                                  │                       ││                       │
                                                  │        ~6 rows        │                                                  │        ~6 rows        ││        ~6 rows        │
                                                  └───────────────────────┘                                                  └───────────────────────┘└───────────────────────┘

Which has 3 CTEs, 8 CTE scans, and only 3 aggregations (instead of the original 12!).

Fuzzy Plan Matching

Something that can show up in query plans is an "almost" exact subplan match. Before this PR, an exact match was required. With this PR, we can do "fuzzy" matching, where the plan is mostly the same, save for some selected columns. If we have the following query, for example:

-- Create build table
create table build as
select range a, range * 2 b, range * 3 c, range * 4 d
from (select range::utinyint as range from range(11))
where range % 5 = 0;
-- Create probe table
create table probe as
select range e, range * 2 f, range * 3 g, range * 4 h
from (select range::utinyint as range from range(11));
-- View that joins the two
create view my_view as
from probe join build on (build.a = probe.e);
-- Select greatest of all columns, unioned with greatest of just two columns
explain
select greatest(a, b, c, d, e, f, g, h) from my_view
union all
select greatest(b, f) from my_view;

Here, one of the unioned queries selects all columns, and the other selects just two columns. As we can see, the join (coming from the view) in the second query is "contained" in the join in first query, as the first query selects all columns that the second query needs.

We currently get the following plan (the optimizer doesn't trigger):

┌───────────────────────────┐
│           UNION           ├───────────────────────────────────────────┐
└─────────────┬─────────────┘                                           │
┌─────────────┴─────────────┐                             ┌─────────────┴─────────────┐
│         PROJECTION        │                             │         PROJECTION        │
│    ────────────────────   │                             │    ────────────────────   │
│ greatest(a, b, c, d, e, f,│                             │       greatest(b, f)      │
│            g, h)          │                             │                           │
│                           │                             │                           │
│          ~3 rows          │                             │          ~3 rows          │
└─────────────┬─────────────┘                             └─────────────┬─────────────┘
┌─────────────┴─────────────┐                             ┌─────────────┴─────────────┐
│         PROJECTION        │                             │         HASH_JOIN         │
│    ────────────────────   │                             │    ────────────────────   │
│             e             │                             │      Join Type: INNER     │
│             f             │                             │     Conditions: e = a     │
│             g             │                             │                           │
│             h             │                             │                           │
│             a             │                             │                           ├──────────────┐
│             b             │                             │                           │              │
│             c             │                             │                           │              │
│             d             │                             │                           │              │
│                           │                             │                           │              │
│          ~3 rows          │                             │          ~3 rows          │              │
└─────────────┬─────────────┘                             └─────────────┬─────────────┘              │
┌─────────────┴─────────────┐                             ┌─────────────┴─────────────┐┌─────────────┴─────────────┐
│         HASH_JOIN         │                             │         SEQ_SCAN          ││         SEQ_SCAN          │
│    ────────────────────   │                             │    ────────────────────   ││    ────────────────────   │
│      Join Type: INNER     │                             │        Table: probe       ││        Table: build       │
│     Conditions: e = a     │                             │   Type: Sequential Scan   ││   Type: Sequential Scan   │
│                           │                             │                           ││                           │
│                           ├──────────────┐              │        Projections:       ││        Projections:       │
│                           │              │              │             e             ││             a             │
│                           │              │              │             f             ││             b             │
│                           │              │              │                           ││                           │
│          ~3 rows          │              │              │          ~11 rows         ││          ~3 rows          │
└─────────────┬─────────────┘              │              └───────────────────────────┘└───────────────────────────┘
┌─────────────┴─────────────┐┌─────────────┴─────────────┐
│         SEQ_SCAN          ││         SEQ_SCAN          │
│    ────────────────────   ││    ────────────────────   │
│        Table: probe       ││        Table: build       │
│   Type: Sequential Scan   ││   Type: Sequential Scan   │
│                           ││                           │
│        Projections:       ││        Projections:       │
│             e             ││             a             │
│             f             ││             b             │
│             g             ││             c             │
│             h             ││             d             │
│                           ││                           │
│          ~11 rows         ││          ~3 rows          │
└───────────────────────────┘└───────────────────────────┘

With the improvements to the optimizer in this PR, we now get this plan:

┌───────────────────────────┐
│            CTE            │
│    ────────────────────   │
│         CTE Name:         │
│     __common_subplan_1    │
│                           ├───────────────────────────────────────────┐
│      Table Index: 29      │                                           │
│                           │                                           │
│          ~0 rows          │                                           │
└─────────────┬─────────────┘                                           │
┌─────────────┴─────────────┐                             ┌─────────────┴─────────────┐
│         HASH_JOIN         │                             │           UNION           │
│    ────────────────────   │                             │                           │
│      Join Type: INNER     │                             │                           │
│     Conditions: e = a     ├──────────────┐              │                           ├──────────────┐
│                           │              │              │                           │              │
│          ~3 rows          │              │              │                           │              │
└─────────────┬─────────────┘              │              └─────────────┬─────────────┘              │
┌─────────────┴─────────────┐┌─────────────┴─────────────┐┌─────────────┴─────────────┐┌─────────────┴─────────────┐
│         SEQ_SCAN          ││         SEQ_SCAN          ││         PROJECTION        ││         PROJECTION        │
│    ────────────────────   ││    ────────────────────   ││    ────────────────────   ││    ────────────────────   │
│        Table: probe       ││        Table: build       ││ greatest(a, b, c, d, e, f,││       greatest(b, f)      │
│   Type: Sequential Scan   ││   Type: Sequential Scan   ││            g, h)          ││                           │
│                           ││                           ││                           ││                           │
│        Projections:       ││        Projections:       ││                           ││                           │
│             e             ││             a             ││                           ││                           │
│             f             ││             b             ││                           ││                           │
│             g             ││             c             ││                           ││                           │
│             h             ││             d             ││                           ││                           │
│                           ││                           ││                           ││                           │
│          ~11 rows         ││          ~3 rows          ││          ~3 rows          ││          ~3 rows          │
└───────────────────────────┘└───────────────────────────┘└─────────────┬─────────────┘└─────────────┬─────────────┘
                                                          ┌─────────────┴─────────────┐┌─────────────┴─────────────┐
                                                          │         PROJECTION        ││         PROJECTION        │
                                                          │    ────────────────────   ││    ────────────────────   │
                                                          │             e             ││             #1            │
                                                          │             f             ││             #4            │
                                                          │             g             ││                           │
                                                          │             h             ││                           │
                                                          │             a             ││                           │
                                                          │             b             ││                           │
                                                          │             c             ││                           │
                                                          │             d             ││                           │
                                                          │                           ││                           │
                                                          │          ~3 rows          ││          ~0 rows          │
                                                          └─────────────┬─────────────┘└─────────────┬─────────────┘
                                                          ┌─────────────┴─────────────┐┌─────────────┴─────────────┐
                                                          │          CTE_SCAN         ││          CTE_SCAN         │
                                                          │    ────────────────────   ││    ────────────────────   │
                                                          │       CTE Index: 29       ││       CTE Index: 29       │
                                                          │                           ││                           │
                                                          │          ~3 rows          ││          ~3 rows          │
                                                          └───────────────────────────┘└───────────────────────────┘

As we can see, the join is materialized as a CTE, and scanned twice. Columns that aren't needed are projected out after the CTE scan.

Benchmark Improvements

With the changes, we now trigger more subplan elimination on TPC-DS and TPC-H. Here are some results that I collected on my laptop.

TPC-DS SF100 improvements:
Q61: 0.61s -> 0.33s (~1.8x)
Q70: 0.81s -> 0.55s (~1.5x)

TPC-H SF100 improvements:
Q11: 0.16s -> 0.12s (~1.3x)

Notes

I needed a lot of indirection to get the column bindings to match with the fuzzy plan matching, which required a lot of unordered_maps. To avoid doing so many allocations, I've also implemented arena_unordered_map, so that the number of allocations grow logarithmically. I'm not sure how much this helped, but it's just something I wanted to do as we are trying to reduce allocations. Overall, this optimizer now takes ~10% of total optimization time.

Laurens Kuiper added 30 commits November 11, 2025 14:16
…mon subplan optimizer in filter pullup test so it still tests the right behaviour
@lnkuiper lnkuiper merged commit c68ff72 into duckdb:main Dec 15, 2025
56 checks passed
@lnkuiper lnkuiper deleted the common_subplan branch December 29, 2025 13:09
github-actions bot pushed a commit to duckdb/duckdb-r that referenced this pull request Feb 24, 2026
github-actions bot added a commit to duckdb/duckdb-r that referenced this pull request Feb 24, 2026
More `CommonSubplanOptimizer` improvements (duckdb/duckdb#20168)

Co-authored-by: krlmlr <krlmlr@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant