Skip to content

Conversation

@cloud-fan
Copy link
Contributor

What changes were proposed in this pull request?

In V1Writes, we try to avoid adding Sort if the output ordering always satisfies. However, the code is completely broken with two issues:

  • we put SortOrder as the child of another SortOrder and compare, which always returns false.
  • once we add a project to do empty2null, we change the query output attribute id and the sort order never matches.

It's not a big issue as we still have QO rules to eliminate useless sorts, but #44429 exposes this problem because the way we optimize sort is a bit different. For V1Writes, we should always avoid adding sort even if the number of ordering key is less, to not change the user query.

Why are the changes needed?

fix code mistakes.

Does this PR introduce any user-facing change?

no

How was this patch tested?

updated test

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

no

@github-actions github-actions bot added the SQL label Dec 22, 2023
val outputOrdering = query.outputOrdering
val orderingMatched = isOrderingMatched(requiredOrdering, outputOrdering)
val outputOrdering = empty2NullPlan.outputOrdering
val orderingMatched = isOrderingMatched(requiredOrdering.map(_.child), outputOrdering)
Copy link
Contributor Author

Choose a reason for hiding this comment

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

what def isOrderingMatched does is outputOrder.satisfies(outputOrder.copy(child = requiredOrder)), so it's completely wrong to pass requiredOrdering as a Seq[SortOrder]

Copy link
Contributor

Choose a reason for hiding this comment

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

Good catch!

Copy link
Member

Choose a reason for hiding this comment

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

Yea, so it was never matched before...

@cloud-fan
Copy link
Contributor Author

cloud-fan commented Dec 22, 2023

val outputOrdering = query.outputOrdering
val orderingMatched = isOrderingMatched(requiredOrdering, outputOrdering)
val outputOrdering = empty2NullPlan.outputOrdering
val orderingMatched = isOrderingMatched(requiredOrdering.map(_.child), outputOrdering)
Copy link
Contributor

Choose a reason for hiding this comment

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

Good catch!

}.asInstanceOf[SortOrder])
val outputOrdering = query.outputOrdering
val orderingMatched = isOrderingMatched(requiredOrdering, outputOrdering)
val outputOrdering = empty2NullPlan.outputOrdering
Copy link
Contributor

Choose a reason for hiding this comment

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

I rough remember before we did not support preserve ordering through empty2null so use the query.outputOrdering. I think use empty2NullPlan.outputOrdering is the expected behavior

Copy link
Contributor

Choose a reason for hiding this comment

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

Yea Project is an OrderPreservingUnaryNode so it should be fine.

@cloud-fan
Copy link
Contributor Author

thanks for the review, merging to master!

@cloud-fan cloud-fan closed this in cb2f47b Dec 22, 2023
@EnricoMi
Copy link
Contributor

What do you think about making user-desired order of partitions explicit by opening .write.orderBy to .write.partitionBy? Right now, .write.orderBy is exclusively used by bucketing (.write.bucketBy).

Instead of

df.sortWithinPartitions("id", "time").write.partitionBy("id")

users can explicitly sort the partitions:

df.write.partitionBy("id").sortBy("id", "time")

Then that desire is explicitly available to the writer and does not need to be derived from the plan.

@cloud-fan
Copy link
Contributor Author

Ideally, users shouldn't care about optimal ordering during data writing. The data source should be smart enough to auto-optimize its data layout. This API goes against the eventual goal.

@EnricoMi
Copy link
Contributor

This is not about optimal ordering (I presume you refer to partitions being ordered by partition columns, which is optimal to have only one file writer open at a time), but about additional ordering (to have some additional order that is not required by the writer task). Having sorted partitions is very useful when your downstream systems that consume the written data can expect some order beyond partition keys. So users care about the in-partition order.

I am happy as long as df.repartition("id").sortWithinPartitions("id", "time").write.partitionBy("id") keeps being supported.

@viirya
Copy link
Member

viirya commented Dec 22, 2023

Looks good to me.

@sweetpythoncode
Copy link

@EnricoMi Like your idea, so for now if u want to sort per nested partition output you will need to use

df.repartition("id", "nested_id").sortWithinPartitions("id", "nested_id", "time").write.partitionBy("id", "nested_id")?

@pan3793
Copy link
Member

pan3793 commented Oct 21, 2025

I found this PR when trying to backport SPARK-53738 (#52584) to branch-3.5.

From my understanding, this PR fixes a hidden bug (until exposed by #44429) that has existed since 3.4, if so, I'd like to backport this to branch-3.5, it's the pre-step for backporting SPARK-53738

@cloud-fan @viirya @yaooqinn @allisonwang-db @ulysses-you @peter-toth @EnricoMi WDYT?

@cloud-fan
Copy link
Contributor Author

+1 to backport

pan3793 pushed a commit to pan3793/spark that referenced this pull request Oct 22, 2025
### What changes were proposed in this pull request?

In `V1Writes`, we try to avoid adding Sort if the output ordering always satisfies. However, the code is completely broken with two issues:
- we put `SortOrder` as the child of another `SortOrder` and compare, which always returns false.
- once we add a project to do `empty2null`, we change the query output attribute id and the sort order never matches.

It's not a big issue as we still have QO rules to eliminate useless sorts, but apache#44429 exposes this problem because the way we optimize sort is a bit different. For `V1Writes`, we should always avoid adding sort even if the number of ordering key is less, to not change the user query.

### Why are the changes needed?

fix code mistakes.

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

no

### How was this patch tested?

updated test

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

no

Closes apache#44458 from cloud-fan/sort.

Authored-by: Wenchen Fan <wenchen@databricks.com>
Signed-off-by: Wenchen Fan <wenchen@databricks.com>
@peter-toth
Copy link
Contributor

I found this PR when trying to backport SPARK-35738 (#52584) to branch-3.5.

SPARK-35738 -> SPARK-53738, but I agree, let's backport this and that PR.

@pan3793
Copy link
Member

pan3793 commented Oct 22, 2025

@peter-toth yes, it's a typo, should be SPARK-53738, sorry for the confusion.

peter-toth pushed a commit that referenced this pull request Oct 22, 2025
Backport #44458 to branch-3.5.

Justification: it fixes a hidden bug (until exposed by #44429) that has existed since 3.4.

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

In `V1Writes`, we try to avoid adding Sort if the output ordering always satisfies. However, the code is completely broken with two issues:
- we put `SortOrder` as the child of another `SortOrder` and compare, which always returns false.
- once we add a project to do `empty2null`, we change the query output attribute id and the sort order never matches.

It's not a big issue as we still have QO rules to eliminate useless sorts, but #44429 exposes this problem because the way we optimize sort is a bit different. For `V1Writes`, we should always avoid adding sort even if the number of ordering key is less, to not change the user query.

### Why are the changes needed?

fix code mistakes.

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

no

### How was this patch tested?

updated test

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

no

Closes #52692 from pan3793/SPARK-46485-3.5.

Authored-by: Wenchen Fan <wenchen@databricks.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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

9 participants