Repository navigation
Conversation
…a SQL execution On an executor, SQLConf.get reads the task's local properties, which Spark fills with the session's configs only for jobs that run inside a SQL execution. A job launched from a query's RDD (Dataset.rdd with AQE off, QueryExecution.toRdd) runs its map stages outside one, and Spark's SQLExecutionRDD re-applies the session's configs only in the final stage. Comet reads its settings on the executor when it builds a native plan, so those map stages ran with every spark.comet.* setting at its default, including the memory pool and the configs passed to native. Capture the session's configs on the driver where Comet builds the RDDs and native shuffle that run native plans, and make them what SQLConf.get returns on the task thread while the plan is built, as SQLExecutionRDD does. Tasks inside a SQL execution are unchanged. Closes apache#7301.
… Spark passed win Capture only spark.comet.* keys, and apply them only when no conf is set on the thread and the task got none of the session's Comet configs from Spark, so SQLExecution.withSQLConfPropagated still takes effect. Carry the native shuffle's one captured conf from its input RDD to the writer, apply it inside the serialized-plan getCometIterator, and drop the test workarounds this change makes redundant.
andygrove
left a comment
There was a problem hiding this comment.
Thanks @comphead. Capturing the session's configs on the driver and applying them the way SQLExecutionRDD does is a clean fix, and leaving tasks inside a SQL execution on the existing path keeps the change small. The repro in #7301 is very clear.
I compared against branch-1.1. The same bug is there, so this is a fix and not a behavior regression. I did not find a correctness or performance concern in the diff itself. I have two things I would like addressed before merge.
The new test only exercises the writer. input_batches == 100 shows that the iterator built in CometNativeShuffleWriter.write saw batchSize=10. Nothing covers CometNativeShuffleInputRDD.compute, CometExecRDD.compute, the limit closure in CometExecUtils, or the top-K and final closures in CometTakeOrderedAndProjectExec. If any of those wrappers were reverted, I think the suite would stay green. Could you add a case for each, following the same pattern? A toRdd over a LIMIT and over an ORDER BY ... LIMIT would cover the closures. A map stage whose child is a CometExecRDD would cover that one.
Some executor-side reads still happen outside these wrappers. The description mentions the JVM columnar writers. CometShuffledRowRDD.compute, computeAsShuffleBlockIterator and CometBlockStoreShuffleReader also read CometConf values such as COMET_SHUFFLE_READ_BUFFER_SIZE and COMET_TRACING_ENABLED. When one of them is the RDD of an intermediate reduce stage in a toRdd job, would it still see defaults? Inside CometExecRDD it is covered because the inputs resolve within withConf. Could you confirm which of these can run unwrapped? Then either wrap them here, or open an issue and link it from the description, since a "not covered here" note without an issue tends not to get done.
One smaller question. The sqlConf: CapturedSQLConf = CapturedSQLConf.capture() defaults on NativeShuffleSpec and CometNativeShuffleInputRDD mean the Arrow-stream path picks up the capture silently. That is correct today because both are built on the driver. Would you make the parameter required, so each site states that it captures on the driver? A default that reads SQLConf.get would capture the executor's defaults if someone later built one of these on an executor, and the bug would come back unnoticed.
… native plan site A Comet shuffle read with no native plan above it in its stage, such as one feeding a Spark operator in an intermediate stage, still read Comet defaults. Capture the configs in CometShuffledBatchRDD and apply them in compute and computeAsShuffleBlockIterator, which makes the wrap in CometNativeShuffleInputRDD.compute redundant, so drop it. Make that RDD's sqlConf required so each call site captures on the driver explicitly. Replace the single test with one per site: the native shuffle writer, a native plan in a map stage, the native limit, the native top-K, each checking the memory pool every native plan resolves, and a shuffle read under spark.comet.shuffle.readBufferSize=0.
|
Thanks @andygrove. All three are addressed in 04cdf52. Tests per site. The single writer test is replaced by one test per site, each running the query from
Each one checks the line Shuffle reads outside the wrappers. Confirmed, they could run unwrapped. When a Comet shuffle read has no native plan above it in its stage, it read the defaults. An example is an intermediate stage where Spark's aggregate or project consumes a Defaults. The I haven't run the new tests locally. They will run in this PR's CI. |
|
|
||
| /** Runs `df` from its RDD and checks that every native plan got the session's memory pool. */ | ||
| private def checkNativePlansUseSessionConfs(df: DataFrame): Unit = { | ||
| val appender = new LogAppender("native plan memory pools") |
There was a problem hiding this comment.
Set the appender threshold to DEBUG; INFO drops these messages.
There was a problem hiding this comment.
Good catch, thanks. LogAppender keeps INFO and above by default, so the check saw no lines. That is very likely why the [shuffle] job failed. Fixed in bcf4862 with appender.setThreshold(Level.DEBUG). Locally the five tests pass, and all five fail with CapturedSQLConf.withConf turned into a no-op.
| try { | ||
| writeInternal(inputs) | ||
| val input = nativeShuffleInput(inputs) | ||
| input.sqlConf.withConf(writeInternal(input)) |
There was a problem hiding this comment.
Native Celeborn reads maxFrameBytes before this wrapper. Apply captured configs during destination construction too.
There was a problem hiding this comment.
Right, getWriter read spark.comet.shuffle.rss.maxFrameBytes from the session before the writer applied anything. Fixed in bcf4862: the captured configs now live on NativeShuffleSpec, which getWriter reaches through the dependency, and the frame-size read runs inside withConf. The writer takes them from the same spec. The other two limits in CelebornShufflePusherFactory.create (frame and in-flight bytes) come from the executor's SparkConf, not the session, so they are unchanged. I did not add a Celeborn test for this read.
…e DEBUG logs in tests The native Celeborn writer read spark.comet.shuffle.rss.maxFrameBytes in getWriter, before the writer applies the captured configs. Carry the configs on NativeShuffleSpec, which getWriter can reach through the dependency, and apply them to that read as well as in the writer, in place of CometNativeShuffleInputRDD and its iterator. Spark's LogAppender keeps INFO and above by default, so set its threshold to DEBUG for the memory pool line the tests read.
andygrove
left a comment
There was a problem hiding this comment.
Thanks @comphead. The three asks from the earlier round are addressed. There is a test per site, the shuffle reads apply the captured configs, and sqlConf is required on the spec and the input RDD. The toRdd repro and the per-site checks of the logged memory pool make the fix easy to trust. I compared against branch-1.2 and the only change is that map stages of toRdd and .rdd jobs now see the session's spark.comet.* values, which is the intended fix.
I added the run-spark-4.1-tests label, since Spark's own suites use .rdd and toRdd a lot and this changes which SQLConf the executor-side code sees.
Nit, optional: CapturedSQLConf.withConf decides it is inside a SQL execution by checking that some captured key is a task local property. Spark only propagates the keys that are currently set. If a capture holds spark.comet.x and the session later unsets it, would a rerun map stage inside a SQL execution see no Comet key in its local properties and apply the stale capture? shuffleDependency is a lazy val on the cached executedPlan, so the capture can outlive the setting. It needs a map stage recompute, so it is narrow. Would also skipping when SQLExecution.EXECUTION_ID_KEY is set make that case exact? A small unit test for the skip conditions would pin it down.
Which issue does this PR close?
Closes #7301.
Rationale for this change
A job launched from a query's RDD (
Dataset.rddwith AQE off,QueryExecution.toRddwith AQE on or off) runs its map stages outside a SQL execution. On an executor,SQLConf.getthen reads task local properties that Spark has not filled, andSQLExecutionRDD(SPARK-28939) re-applies the session's configs only in the final stage. Comet reads its settings on the executor when it builds a native plan and when it reads a shuffle, so those map stages ran with everyspark.comet.*setting at its default. That includes the memory pool, the batch size and the configs passed to native. There was no warning.What changes are included in this PR?
CapturedSQLConfcaptures the session'sspark.comet.*configs on the driver.withConfmakes them whatSQLConf.getreturns on the task thread, usingSQLConf.withExistingConfasSQLExecutionRDDdoes. It does nothing when a conf is already set on the thread, or when Spark passed the task the session's configs as local properties (inside a SQL execution, or underSQLExecution.withSQLConfPropagated).CometExecRDD.compute, the native shuffle writer and the Celeborn writer's frame size (the configs ride onNativeShuffleSpec), the serialized-planCometExec.getCometIteratorused by the native limit and top-K, andCometShuffledBatchRDD.compute/computeAsShuffleBlockIterator.withSQLConfPropagatedworkarounds inCometJoinSuiteandCometTaskMetricsSuiteare no longer needed and are removed.Not covered here: the JVM columnar shuffle writers, tracked in #7303.
CometColumnarShuffleSuitekeeps its workaround until then. Native writes run inside a SQL execution, so they are not affected.How are these changes tested?
New
CometNativeShuffleSuitetests run a query from its RDD with AQE off, one per site:CometExecIteratorlogs when it creates a plan, that every native plan resolved the session's memory poolspark.comet.shuffle.readBufferSize=0, which fails only if the read sees the session's valueThe same query shapes in
spark-shell(Spark 4.1.1): before this change, map-stage plans resolvefair_unifiedand the intermediate shuffle read succeeds. After it, every plan resolves the session'sgreedy_unifiedand the read fails ascollect()does. The probe from #7301 resolves the submitted pool and fraction in the map stage in all five scenarios.