Skip to content

[SPARK-58323][SQL] Materialize AliasAware output ordering and partitioning to avoid StackOverflowError#57502

Closed
peter-toth wants to merge 2 commits into
apache:masterfrom
peter-toth:SPARK-58323-aliasaware-strict-ordering-partitioning
Closed

[SPARK-58323][SQL] Materialize AliasAware output ordering and partitioning to avoid StackOverflowError#57502
peter-toth wants to merge 2 commits into
apache:masterfrom
peter-toth:SPARK-58323-aliasaware-strict-ordering-partitioning

Conversation

@peter-toth

@peter-toth peter-toth commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

What changes were proposed in this pull request?

AliasAwareQueryOutputOrdering.outputOrdering and PartitioningPreservingUnaryExecNode.outputPartitioning (and BroadcastHashJoinExec's output-partitioning expansion) build their results with multiTransform, which returns a LazyList, and store the bounded result unforced. This forces them into strict collections with .toList, right after the existing .take(...) so the lazy short-circuit during candidate generation is preserved.

Note: for BroadcastHashJoinExec, using a strict List also makes the existing case p :: Nil => p unwrap in outputPartitioning reachable — so a single-element expansion now returns the HashPartitioning directly instead of a one-element PartitioningCollection (that branch was previously dead because the expansion was an unforced LazyList, which a List-cons pattern never matches). This is the cleaner, intended form; the corresponding BroadcastJoinSuite assertion is updated accordingly.

Why are the changes needed?

Each plan node re-wraps the child ordering/partitioning's unforced LazyList, so across a deep projection chain the deferred nesting grows unbounded and overflows the stack when the ordering/partitioning is later serialized or deeply traversed. Concretely, task serialization of a SortedMergeCoalescedRDD (SPARK-55715) that captures the child outputOrdering was observed to fail on the driver with a StackOverflowError at DAGScheduler.submitMissingTasks on a large bucketed MERGE. The lazy LazyList in SortOrder.sameOrderExpressions (and in the projected PartitioningCollection) is the root cause; this has existed since the multiTransform path was introduced in SPARK-42049.

Does this PR introduce any user-facing change?

No.

How was this patch tested?

New test in ProjectedOrderingAndPartitioningSuite asserting sameOrderExpressions and the projected PartitioningCollection.partitionings are strict (not LazyList); it fails before this change (they were LazyList) and passes after. BroadcastJoinSuite / BroadcastJoinSuiteAE updated for the single-element output-partitioning form and pass. Existing ProjectedOrderingAndPartitioningSuite / PlannerSuite / EnsureRequirementsSuite pass.

Was this patch authored or co-authored using generative AI tooling?

Generated-by: Claude Opus 4.8

…oning to avoid StackOverflowError

### What changes were proposed in this pull request?

`AliasAwareQueryOutputOrdering.outputOrdering` and `PartitioningPreservingUnaryExecNode.outputPartitioning` (and `BroadcastHashJoinExec`'s output-partitioning expansion) build their results with `multiTransform`, which returns a `LazyList`, and store the bounded result unforced. Force them into strict collections with `.toList` (after the existing `.take(...)`, so the short-circuit is preserved).

### Why are the changes needed?

Each plan node re-wraps the child ordering/partitioning's unforced `LazyList`, so across a deep projection chain the deferred nesting grows unbounded and overflows the stack when the ordering/partitioning is later serialized or deeply traversed -- e.g. task serialization of a `SortedMergeCoalescedRDD` (SPARK-55715) that captures the child ordering, observed as a driver `StackOverflowError` at `DAGScheduler.submitMissingTasks`.

### Does this PR introduce any user-facing change?

No.

### How was this patch tested?

New test in `ProjectedOrderingAndPartitioningSuite` (fails before this change: `sameOrderExpressions` was a `LazyList`); existing suite passes.

### Was this patch authored or co-authored using generative AI tooling?

Generated-by: Claude Opus 4.8

@dongjoon-hyun dongjoon-hyun 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.

+1, LGTM (Pending CIs).

@dongjoon-hyun

Copy link
Copy Markdown
Member

Since SPARK-42049 is Apache Spark 3.4.0, do we need to backport this until branch-4.0, @peter-toth ?

@peter-toth

peter-toth commented Jul 24, 2026

Copy link
Copy Markdown
Contributor Author

Thanks @dongjoon-hyun for the quick review!

The only place where this issue surfaced is SortedMergeCoalescedRDD (a 4.2.0 feature), but I think backporting this can't hurt so I would do that, yes.

…ut partitioning

The `.toList` on the BroadcastHashJoinExec expansion (added for strictness) also makes the existing `case p :: Nil => p` branch reachable: a single-element expansion now returns the `HashPartitioning` directly instead of a one-element `PartitioningCollection` (previously unreachable because the expansion was an unforced `LazyList`, which the `List`-cons pattern never matched). This is the cleaner, intended form. Update the `BroadcastJoinSuite` assertion to that contract and clarify the code comment.
peter-toth added a commit that referenced this pull request Jul 25, 2026
…oning to avoid StackOverflowError

### What changes were proposed in this pull request?

`AliasAwareQueryOutputOrdering.outputOrdering` and `PartitioningPreservingUnaryExecNode.outputPartitioning` (and `BroadcastHashJoinExec`'s output-partitioning expansion) build their results with `multiTransform`, which returns a `LazyList`, and store the bounded result unforced. This forces them into strict collections with `.toList`, right after the existing `.take(...)` so the lazy short-circuit during candidate generation is preserved.

Note: for `BroadcastHashJoinExec`, using a strict `List` also makes the existing `case p :: Nil => p` unwrap in `outputPartitioning` reachable — so a single-element expansion now returns the `HashPartitioning` directly instead of a one-element `PartitioningCollection` (that branch was previously dead because the expansion was an unforced `LazyList`, which a `List`-cons pattern never matches). This is the cleaner, intended form; the corresponding `BroadcastJoinSuite` assertion is updated accordingly.

### Why are the changes needed?

Each plan node re-wraps the child ordering/partitioning's unforced `LazyList`, so across a deep projection chain the deferred nesting grows unbounded and overflows the stack when the ordering/partitioning is later serialized or deeply traversed. Concretely, task serialization of a `SortedMergeCoalescedRDD` (SPARK-55715) that captures the child `outputOrdering` was observed to fail on the driver with a `StackOverflowError` at `DAGScheduler.submitMissingTasks` on a large bucketed `MERGE`. The lazy `LazyList` in `SortOrder.sameOrderExpressions` (and in the projected `PartitioningCollection`) is the root cause; this has existed since the `multiTransform` path was introduced in SPARK-42049.

### Does this PR introduce _any_ user-facing change?

No.

### How was this patch tested?

New test in `ProjectedOrderingAndPartitioningSuite` asserting `sameOrderExpressions` and the projected `PartitioningCollection.partitionings` are strict (not `LazyList`); it fails before this change (they were `LazyList`) and passes after. `BroadcastJoinSuite` / `BroadcastJoinSuiteAE` updated for the single-element output-partitioning form and pass. Existing `ProjectedOrderingAndPartitioningSuite` / `PlannerSuite` / `EnsureRequirementsSuite` pass.

### Was this patch authored or co-authored using generative AI tooling?

Generated-by: Claude Opus 4.8

Closes #57502 from peter-toth/SPARK-58323-aliasaware-strict-ordering-partitioning.

Authored-by: Peter Toth <peter.toth@gmail.com>
Signed-off-by: Peter Toth <peter.toth@gmail.com>
(cherry picked from commit faa1d1e)
Signed-off-by: Peter Toth <peter.toth@gmail.com>
peter-toth added a commit that referenced this pull request Jul 25, 2026
…oning to avoid StackOverflowError

`AliasAwareQueryOutputOrdering.outputOrdering` and `PartitioningPreservingUnaryExecNode.outputPartitioning` (and `BroadcastHashJoinExec`'s output-partitioning expansion) build their results with `multiTransform`, which returns a `LazyList`, and store the bounded result unforced. This forces them into strict collections with `.toList`, right after the existing `.take(...)` so the lazy short-circuit during candidate generation is preserved.

Note: for `BroadcastHashJoinExec`, using a strict `List` also makes the existing `case p :: Nil => p` unwrap in `outputPartitioning` reachable — so a single-element expansion now returns the `HashPartitioning` directly instead of a one-element `PartitioningCollection` (that branch was previously dead because the expansion was an unforced `LazyList`, which a `List`-cons pattern never matches). This is the cleaner, intended form; the corresponding `BroadcastJoinSuite` assertion is updated accordingly.

Each plan node re-wraps the child ordering/partitioning's unforced `LazyList`, so across a deep projection chain the deferred nesting grows unbounded and overflows the stack when the ordering/partitioning is later serialized or deeply traversed. Concretely, task serialization of a `SortedMergeCoalescedRDD` (SPARK-55715) that captures the child `outputOrdering` was observed to fail on the driver with a `StackOverflowError` at `DAGScheduler.submitMissingTasks` on a large bucketed `MERGE`. The lazy `LazyList` in `SortOrder.sameOrderExpressions` (and in the projected `PartitioningCollection`) is the root cause; this has existed since the `multiTransform` path was introduced in SPARK-42049.

No.

New test in `ProjectedOrderingAndPartitioningSuite` asserting `sameOrderExpressions` and the projected `PartitioningCollection.partitionings` are strict (not `LazyList`); it fails before this change (they were `LazyList`) and passes after. `BroadcastJoinSuite` / `BroadcastJoinSuiteAE` updated for the single-element output-partitioning form and pass. Existing `ProjectedOrderingAndPartitioningSuite` / `PlannerSuite` / `EnsureRequirementsSuite` pass.

Generated-by: Claude Opus 4.8

Closes #57502 from peter-toth/SPARK-58323-aliasaware-strict-ordering-partitioning.

Authored-by: Peter Toth <peter.toth@gmail.com>
Signed-off-by: Peter Toth <peter.toth@gmail.com>
(cherry picked from commit faa1d1e)
Signed-off-by: Peter Toth <peter.toth@gmail.com>
@peter-toth

Copy link
Copy Markdown
Contributor Author

Merge Summary:

Posted by merge_spark_pr.py

@peter-toth

Copy link
Copy Markdown
Contributor Author

Thanks for the review @dongjoon-hyun and @uros-b!

I will open separate backport PR to older branches.

peter-toth added a commit that referenced this pull request Jul 26, 2026
…rtitioning to avoid StackOverflowError

### What changes were proposed in this pull request?

Backport of SPARK-58323 (#57502) to branch-4.1.

`AliasAwareQueryOutputOrdering.outputOrdering` and `BroadcastHashJoinExec`'s output-partitioning expansion build their results with `multiTransform`, which returns a `LazyList`, and store the bounded result unforced. This forces them into strict collections with `.toList`, right after the existing `.take(...)` so the lazy short-circuit during candidate generation is preserved.

Tailored for branch-4.1 (differs from the master PR):
- The `PartitioningPreservingUnaryExecNode.outputPartitioning` change is omitted: on branch-4.1 that method already materializes a strict collection (`...iterator.flatMap(...).take(...).toSeq` over an `Iterator`), so there is no unforced `LazyList` to fix.
- The `BroadcastJoinSuite` single-element assertion change is omitted: on branch-4.1 `expandOutputPartitioning` always returns a `PartitioningCollection` (no `case p :: Nil` unwrap), so the single-element output shape is unchanged.

### Why are the changes needed?

Each plan node re-wraps the child ordering/partitioning's unforced `LazyList`, so across a deep projection chain the deferred nesting grows unbounded and overflows the stack when the ordering/partitioning is later serialized or deeply traversed. On master this was observed as a driver `StackOverflowError` at `DAGScheduler.submitMissingTasks` during task serialization of a `SortedMergeCoalescedRDD` (SPARK-55715) on a large bucketed `MERGE`. That RDD and code path do not exist on branch-4.1, so this backport is defensive hardening of the same latent lazy-collection-in-output-ordering/partitioning hazard, which has existed since the `multiTransform` path was introduced in SPARK-42049.

### Does this PR introduce _any_ user-facing change?

No.

### How was this patch tested?

New test in `ProjectedOrderingAndPartitioningSuite` asserting `sameOrderExpressions` is strict (not a `LazyList`); it fails before this change and passes after. Existing `ProjectedOrderingAndPartitioningSuite` / `BroadcastJoinSuite` / `BroadcastJoinSuiteAE` pass.

### Was this patch authored or co-authored using generative AI tooling?

Generated-by: Claude Opus 4.8

Closes #57531 from peter-toth/SPARK-58323-aliasaware-strict-ordering-partitioning-4.1.

Authored-by: Peter Toth <peter.toth@gmail.com>
Signed-off-by: Peter Toth <peter.toth@gmail.com>
peter-toth added a commit that referenced this pull request Jul 26, 2026
…rtitioning to avoid StackOverflowError

### What changes were proposed in this pull request?

Backport of SPARK-58323 (#57502) to branch-4.0.

`AliasAwareQueryOutputOrdering.outputOrdering` and `BroadcastHashJoinExec`'s output-partitioning expansion build their results with `multiTransform`, which returns a `LazyList`, and store the bounded result unforced. This forces them into strict collections with `.toList`, right after the existing `.take(...)` so the lazy short-circuit during candidate generation is preserved.

Tailored for branch-4.0 (differs from the master PR):
- The `PartitioningPreservingUnaryExecNode.outputPartitioning` change is omitted: on branch-4.0 that method already materializes a strict collection (`...iterator.flatMap(...).take(...).toSeq` over an `Iterator`), so there is no unforced `LazyList` to fix.
- The `BroadcastJoinSuite` single-element assertion change is omitted: on branch-4.0 `expandOutputPartitioning` always returns a `PartitioningCollection` (no `case p :: Nil` unwrap), so the single-element output shape is unchanged.

### Why are the changes needed?

Each plan node re-wraps the child ordering/partitioning's unforced `LazyList`, so across a deep projection chain the deferred nesting grows unbounded and overflows the stack when the ordering/partitioning is later serialized or deeply traversed. On master this was observed as a driver `StackOverflowError` at `DAGScheduler.submitMissingTasks` during task serialization of a `SortedMergeCoalescedRDD` (SPARK-55715) on a large bucketed `MERGE`. That RDD and code path do not exist on branch-4.0, so this backport is defensive hardening of the same latent lazy-collection-in-output-ordering/partitioning hazard, which has existed since the `multiTransform` path was introduced in SPARK-42049.

### Does this PR introduce _any_ user-facing change?

No.

### How was this patch tested?

New test in `ProjectedOrderingAndPartitioningSuite` asserting `sameOrderExpressions` is strict (not a `LazyList`); it fails before this change and passes after. Existing `ProjectedOrderingAndPartitioningSuite` / `BroadcastJoinSuite` / `BroadcastJoinSuiteAE` pass.

### Was this patch authored or co-authored using generative AI tooling?

Generated-by: Claude Opus 4.8

Closes #57532 from peter-toth/SPARK-58323-aliasaware-strict-ordering-partitioning-4.0.

Authored-by: Peter Toth <peter.toth@gmail.com>
Signed-off-by: Peter Toth <peter.toth@gmail.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.

3 participants