Skip to content

ORCA: reduce planning time of parallel plans - #2228

Open
yjhjstz wants to merge 2 commits into
apache:mainfrom
yjhjstz:feat/orca-child-opt-memo
Open

yjhjstz wants to merge 2 commits into
apache:mainfrom
yjhjstz:feat/orca-child-opt-memo

Conversation

@yjhjstz

@yjhjstz yjhjstz commented Nov 2, 2026

Copy link
Copy Markdown
Member

Changes

  • Skip redundant part-prop requests . Hash and nested-loop joins enumerate a DPE and a non-DPE partition propagation request for every other request. With no partition consumers below the join and no propagation required from above, both give every child an empty spec, so the second one optimizes the same child contexts again. Such requests are now rejected in CEngine::FCheckReqdProps before any child is optimized.
  • Reuse child contexts of a request . A request of a group expression is optimized once per context of its group, and under contexts that differ only in the required distribution it usually reaches the same child contexts. The child contexts a request reaches are stored on the group expression, keyed by request number, search stage and the parent's requirements apart from distribution. A later request with an equal key reuses them after recomputing each child's required distribution (Ped) and checking that the child context has a plan, is not the context itself, and is the one a lookup of its requirements finds. As when the children are optimized, the plan properties of earlier children are derived again for the recheck, so it does not change the properties cached on their cost contexts. Cost bound checks after each child are replayed. CTE sequences and multi-stage search are left out.
    • optimizer_enable_child_opt_memo (developer GUC, default on)
    • optimizer_verify_child_opt_memo (developer GUC, default off): optimizes the children anyway and logs any difference from the reused contexts

Planning time

Baseline 13 join/aggregation/subquery/DPE queries. "Part-prop only" is HEAD with optimizer_enable_child_opt_memo=off.

Serial geomean / sum Parallel geomean / sum
Part-prop only −0.6% / −0.8% −8.7% / −12.2%
HEAD −8.9% / −10.1% −20.2% / −25.7%

Parallel/serial planning time ratio (geomean): 1.572 (baseline), 1.445 (part-prop only), 1.377 (HEAD).

Query Serial, ms Δ Parallel, ms Δ
star4 71.7 → 59.3 −17.2% 137.8 → 93.0 −32.5%
star6 122.3 → 108.4 −11.4% 239.6 → 166.0 −30.7%
star8 223.2 → 208.5 −6.6% 365.3 → 279.7 −23.4%
left3 37.3 → 35.4 −5.3% 49.9 → 42.1 −15.6%
semi 55.5 → 51.3 −7.6% 90.7 → 73.0 −19.6%
anti 29.9 → 29.0 −2.8% 39.6 → 35.8 −9.8%
agg 83.9 → 69.4 −17.4% 184.9 → 120.7 −34.7%
twokey 83.1 → 71.3 −14.1% 176.7 → 121.6 −31.2%
union 14.8 → 14.5 −2.0% 16.9 → 16.1 −4.5%
dpe 11.7 → 11.3 −3.6% 12.7 → 12.2 −3.8%
dpe3 39.1 → 33.7 −13.7% 55.3 → 45.6 −17.5%
corr1 22.6 → 21.4 −5.3% 33.3 → 28.4 −14.5%
corr2 52.8 → 49.0 −7.2% 87.4 → 72.8 −16.6%

What does this PR do?

Type of Change

  • Bug fix (non-breaking change)
  • New feature (non-breaking change)
  • Breaking change (fix or feature with breaking changes)
  • Documentation update

Breaking Changes

Test Plan

  • Unit tests added/updated
  • Integration tests added/updated
  • Passed make installcheck
  • Passed make -C src/test installcheck-cbdb-parallel

Impact

Performance:

User-facing changes:

Dependencies:

Checklist

Additional Context

CI Skip Instructions


@yjhjstz
yjhjstz marked this pull request as ready for review November 2, 2026 19:06
@Alena0704
Alena0704 self-requested a review November 3, 2026 08:39

@my-ship-it my-ship-it left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Great work!

This memoization for child contexts and skipping redundant part-prop requests is a good optimization, and the benchmark results look really promising.

@Alena0704 Alena0704 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Thank you for your work on this! Looks good to me. Great job!

Hash and nested-loop joins create two partition propagation requests
for every combination of their other property requests: request 0
may push DPE predicates to a child, request 1 passes the incoming
requirement through. Parallel hash joins always create both, and so
does every nested-loop join.

When the join has no partition consumers below it and its parent
requires no partition propagation, both requests produce an empty
spec for every child. The second request then optimizes exactly the
same child contexts as the first one, only for its cost context to
be dropped as a duplicate. With parallel plans enabled this repeats
a large share of the optimization jobs of every join group.

Reject such requests in CEngine::FCheckReqdProps, before any child
is optimized, and add CPartitionPropagationSpec::IsEmpty() for the
check.

Measured on a debug build with star, outer, semi, anti, aggregate,
multi-key, union and DPE queries: plans and costs are unchanged,
and ORCA planning time with parallel plans drops by 10-19% on the
join-heavy queries.

Claude-Session: https://claude.ai/code/session_01M1CxTMjApWd2XBG3C4wnyi
Claude-Session: https://claude.ai/code/session_01ALBeQzzkBwDJDY1UJ65JrA
(cherry picked from commit f4d28f67ae59dc572bb78d46b087b7cbc9bbec65)
A request of a group expression is optimized once per context of its
group. Under contexts that differ only in the required distribution, it
usually computes the same child requirements and reaches the same child
contexts again, after computing every child requirement, looking up the
child contexts, waiting for the child jobs and deriving the plan
properties of each optimized child.

Store the child contexts a request reaches on its group expression,
keyed by the request number, the search stage and the parent's required
plan properties apart from distribution. A later request with an equal
key reuses them if it would reach the same contexts:
- Every child requirement except distribution only depends on the
  matching part of the parent's requirements, which the key makes
  equal, and on the plans of the children optimized before, which are
  the best plans of the same contexts. Only Ped() sees the whole parent
  requirement, so the required distribution of each child is computed
  again and compared. The plan properties of earlier children are
  derived again for this, as DerivePrevChildProps() does: some
  PdsRequired() implementations change the derived distribution of the
  first child in place, and the properties cached on a cost context are
  shared.
- A child context must have a plan, must not be the context itself, and
  must be the one a lookup of its requirements finds. A group can hold
  several contexts with equal requirements when a requirement changes
  after its context was inserted.
The cost bound checks made after each child are repeated with the
reused contexts. CTE sequences and multi-stage search are left out.

The developer GUC optimizer_enable_child_opt_memo (default on) turns the
memo off. optimizer_verify_child_opt_memo optimizes the children anyway
and logs where the result differs from the reused contexts.
optimizer_print_optimization_stats prints the memo counters.

In 13 join and aggregation queries the plans are identical with and
without the memo, and 11914 of 16127 lookups reuse child contexts with
parallel plans (5020 of 7870 serial). Planning time drops by 12.6%
geomean (15.4% in total) with parallel plans and by 8.3% (9.4%)
serial. Verify mode finds no difference there or in 25 related
regression tests, whose results do not change apart from run-to-run
noise.

Claude-Session: https://claude.ai/code/session_01M1CxTMjApWd2XBG3C4wnyi
Claude-Session: https://claude.ai/code/session_01ALBeQzzkBwDJDY1UJ65JrA
(cherry picked from commit 9bb35ccdd3c1e13809776bbe76189c1bc34175c4)
@yjhjstz
yjhjstz force-pushed the feat/orca-child-opt-memo branch from 1ded742 to 9747b44 Compare November 6, 2026 17:47
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.

3 participants