Skip to content

sql: bound INTERSECT plan size by ordering its inputs - #37897

Merged
ggevay merged 1 commit into
MaterializeInc:mainfrom
ggevay:intersect-fix
Jul 27, 2026
Merged

sql: bound INTERSECT plan size by ordering its inputs#37897
ggevay merged 1 commit into
MaterializeInc:mainfrom
ggevay:intersect-fix

Conversation

@ggevay

@ggevay ggevay commented Jul 27, 2026

Copy link
Copy Markdown
Contributor

Planning duplicates an INTERSECT's left input, so a left-deep chain doubles the plan at every level. A 35-way chain needs 2^35 HIR nodes, which exhausted environmentd's memory during planning and wedged the coordinator in an incident.

INTERSECT is commutative, so put the cheaper input on the left. A subtree is then only duplicated when it is the smaller of the two, which bounds the plan at O(n^log2(3)) instead of O(2^n). The swap is not observable: column names come from the left scope either way, and both inputs are already cast to a common type before this point.

The regression test uses a full 35-way chain because a shorter one would pass either way. The unfixed planner still returns the correct answer at 12 levels, so only a chain long enough to exhaust memory distinguishes the two.

@ggevay ggevay added the A-ADAPTER Topics related to the ADAPTER layer label Jul 27, 2026
@ggevay
ggevay marked this pull request as ready for review July 27, 2026 17:40
@ggevay
ggevay requested a review from a team as a code owner July 27, 2026 17:40
Planning duplicates an INTERSECT's left input, so a left-deep chain doubles the
plan at every level. A 35-way chain needs 2^35 HIR nodes, which exhausted
environmentd's memory during planning and wedged the coordinator (incident-1160).

INTERSECT is commutative, so put the cheaper input on the left. A subtree is
then only duplicated when it is the smaller of the two, which bounds the plan at
O(n^log2(3)) instead of O(2^n). The swap is not observable: column names come
from the left scope either way, and both inputs are already cast to a common
type before this point.

The regression test uses a full 35-way chain because a shorter one would pass
either way. The unfixed planner still returns the correct answer at 12 levels,
so only a chain long enough to exhaust memory distinguishes the two.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011d7RFuATJRYwYDWjZEs2Sg

@DAlperin DAlperin left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

LGTM, thanks!

@ggevay
ggevay merged commit 0b58648 into MaterializeInc:main Jul 27, 2026
124 checks passed
@ggevay ggevay added the release-blocker Critical issue that should block *any* release if not fixed label Jul 27, 2026
bosconi pushed a commit that referenced this pull request Jul 29, 2026
Adds the one user-facing entry from the rc.3 cherry-pick: #37897, which fixes
queries with many chained INTERSECT operations exhausting environmentd memory
during planning.

Dates are unchanged: the provisional estimate is anchored on the rc.1 tag and
has not elapsed.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
maheshwarip pushed a commit that referenced this pull request Jul 31, 2026
Adds the one user-facing entry from the rc.3 cherry-pick: #37897, which fixes
queries with many chained INTERSECT operations exhausting environmentd memory
during planning.

Dates are unchanged: the provisional estimate is anchored on the rc.1 tag and
has not elapsed.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

A-ADAPTER Topics related to the ADAPTER layer release-blocker Critical issue that should block *any* release if not fixed

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants