Repository navigation
Conversation
Spark puts no sort below a window when its input reports the order the window needs, a relation sorted before it was cached for instance. When those rows enter the native plan from the JVM, the ScanExec they arrive through declares no order, and the native window failed the task with "Expects PARTITION BY expression to be ordered". Send the window's required ordering in a new input_sort_orders field on the Window proto when the child reports an order that satisfies it and mayReportFalseOrder trusts that order. The planner declares it on the window's input with with_declared_order, as the hash join does for its probe input. A key that Comet cannot express, or that holds a collated string, sends the window back to Spark. Closes apache#7223.
- Send no input order when a native sort below the window declares its own. - Build the order only once the window's own expressions have serialized, so a window that falls back for another reason does not walk the plan or record an extra reason. - Map a collated key straight to None instead of signalling it with an empty list. - Drop the empty-list branch in WindowBuilder, as with_declared_order already leaves the plan unchanged for no orders.
68f167c to
8bc8908
Compare
andygrove
left a comment
There was a problem hiding this comment.
Thanks @comphead. This follows the streamed_sort_orders approach from #6835 and the root cause in the description matches what I see in DataFusion 55.2.0. Both BoundedWindowAggExec and WindowAggExec call get_partition_by_sort_exprs, so declaring the order on their shared input is the right place. Declaring it only above a ScanExec, ShuffleScanExec or single-file-group scan, and only when the input declares nothing, looks right to me.
I compared against branch-1.2. A window over a child whose reported order satisfies it used to fail natively with Expects PARTITION BY expression to be ordered. Now it runs, or falls back to Spark when a key can't be expressed or is a collated string. I found no change that makes Comet diverge from Spark where the release did not. When the reported order may be false, nothing is declared and the native window still fails. I think that is the safer outcome, since Spark dropped its sort on the same false order.
I have a few things I'd like addressed before merge.
-
Could you add a test with an unbounded frame, for example
sum(v) OVER (PARTITION BY k ORDER BY v ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)over the same cache? Both new tests land onBoundedWindowAggExec(row_numberin Rust,dense_rankin Scala), so theWindowAggExecbranch inWindowBuilderisn't exercised. The description says the declaration covers both. -
Would it be worth adding a nullable partition key and a descending order key to the cache test data? That would show that the direction and null ordering taken from
requiredOrderingmatch what the cache reports. If they ever disagreed, the window would split partitions silently instead of failing. -
Could you add a Spark 4.x test for the new collated string key fallback? It should check that the window stays in Spark and the result is correct. Is the case where the child is a Spark
SortExecthat Comet could not convert covered anywhere? The serde now sends the order there too, since the child is not aCometSortExec. -
The
WindowBuilder::buildsnippet indocs/source/contributor-guide/debugging.md(around line 270) still passesArc::clone(&child.native_plan)to both window operators. Could it be updated toinput? -
This touches the serde and the planner on a default path, and the PR tier only ran Spark 4.1. Could we get a verdict from the Spark SQL suites before this is queued? Applying
run-all-spark-profilesandrun-spark-4.1-tests, or runningdev/local-ci.sh spark sql_core-1, would do it.
…llback From review: - The cache test also runs a frame up to UNBOUNDED FOLLOWING, which runs in WindowAggExec, over a nullable partition key and a descending order key, so the declared order has to match the cache's. - The planner test covers both window operators through the partition order lookup they share, since WindowAggExec fails only when its stream starts, not in partition_by_sort_keys. - A Spark 4.x test checks that a window over a cache sorted by a collated key stays in Spark for the new reason and returns Spark's rows. - The debugging guide's window snippet builds the operators over `input`.
|
Thanks @andygrove, addressed in 94aaa19.
|
Which issue does this PR close?
Closes #7223.
Rationale for this change
Spark puts no sort below a window when its input reports the order the window needs, a relation sorted before it was cached for instance. When those rows enter the native plan from the JVM, the
ScanExecthey arrive through declares no order to DataFusion. The native window needs its input to declare the partition keys' order.BoundedWindowAggExec, which runs inInputOrderMode::Sorted, fails the task withExpects PARTITION BY expression to be ordered.WindowAggExecfails when its stream starts, withAll partition by columns should have an ordering. The rows are sorted. Only the declaration is missing.DataFusion has no change pending here.
get_partition_by_sort_exprsraises the error on DataFusionmainas it does in 55.2.0, and it is right to: it trusts the order its input declares, and Comet's scan declares none.#6835 fixed the same gap for outer hash joins with
streamed_sort_orders. This PR does the same for the window, which is the first option in the issue.What changes are included in this PR?
Windowgetsrepeated Expr input_sort_orders = 5.CometWindowExecsends the window's required ordering (the partition keys ascending, then the order keys) when the child reports an order that satisfies it, so Spark dropped the sort, andmayReportFalseOrdertrusts that order. ACometSortExecchild declares its own order, so nothing is sent for it. A newmayReportFalseInputOrderin theCometHashJointrait forwards to it. If the order is trusted but a key cannot be expressed or holds a collated string, the window falls back to Spark with a reason instead of failing natively.WindowBuilderdeclares the order on the window's input withwith_declared_order, nowpub(super). It is the same walk the hash join uses, so the order is declared only above aScanExec,ShuffleScanExecor a scan of one file group, and an input that already declares an order is left as it is. Both window operators find the partition keys' order throughget_ordered_partition_by_indiceson that shared input, so the declaration covers both.When the order the input reports may be false, nothing is declared and the native window fails as it does on
main. This PR does not change that case.How are these changes tested?
planner.rs:window_declares_input_order_on_a_scancoversrow_number(BoundedWindowAggExec) andpercent_rank(WindowAggExec). It checks that the window's input is aSortedInputExecover the scan whose order covers the partition key, and that without the orders it is the bare scan, whose order does not. Leaving an input that already declares an order alone is the same early return inwith_declared_orderthathash_join_keeps_probe_input_that_declares_an_ordercovers. The window expression moved into a helper thatwindow_cast_projection_reports_window_timenow uses as well.CometInMemoryCacheSuite:a native window over a sorted cache declares the cache's orderruns the repro from the issue with the native cache scan on and off. The partition key is nullable and the order key descending, so the declared order has to match the cache's direction and null placement. It runsdense_rank, which lands onBoundedWindowAggExec, and asumwith a frame up toUNBOUNDED FOLLOWING, which lands onWindowAggExec. Each checks the rows, that the serde sent both orders, and that no sort is planned.CometInMemoryCacheSuite:a window over a cache sorted by a collated string key stays in Spark(Spark 4.x) checks that the window falls back with the new reason and returns Spark's rows.SortExecdirectly under a native window does not come up. Comet converts only leaf operators into a native operator's JVM input, and withspark.comet.exec.sort.enabled=falsethe window stays in Spark.The window snippet in the debugging guide now builds both operators over
input.Run locally with JDK 17, rebased on
7d251b3619: the native tests above pass, the cache test passes on-Pspark-3.5and-Pspark-4.1, and the collated test passes on-Pspark-4.1. With only the native change inwindow.rsreverted, the cache test fails withCometNativeException: Expects PARTITION BY expression to be ordered. The Spark SQL suites run through therun-all-spark-profilesandrun-spark-4.1-testslabels.