Repository navigation
feat: run broadcast joins natively when an unsupported scan is on the build side - #6786
Conversation
|
This seems like a great idea. Also, |
andygrove
left a comment
There was a problem hiding this comment.
Thanks for this. Getting the join over the large probe side to run natively when a small lookup table is in a format Comet can't scan is a nice win. The ORC, Iceberg and text probes I ran all matched Spark. I did find three cases where the change makes an existing query slower than it is today, so I'm requesting changes for those. Details are inline. The new tests pass for me on 3.4, 3.5 and 4.2 as well.
Since this changes the planner, I've added run-spark-4.1-tests and run-all-spark-profiles. The local sql_core-1 run skips org.apache.spark.sql.execution.datasources.* and org.apache.spark.sql.connector.*, which run in sql_core-4. Those are the suites that cover ORC, text and V2 scans, and Hive ORC tables are covered in sql_hive. The profiles label runs the new tests on 3.4, where the DPP path is different.
docs/source/user-guide/latest/datasources.md still says these scans only convert with spark.comet.sparkToColumnar.enabled and the operator list. Could you add a paragraph there about the new default and how to turn it off?
The earlier [scans] failure was the listener race in the Iceberg write report test, which #6809 just fixed on main. The re-run passed.
sunchao
left a comment
There was a problem hiding this comment.
Summary
- Prior state and problem: An unsupported build-side scan prevented native broadcast joins even when the probe used a native scan. This PR enables that execution path automatically.
- Design approach: Tag eligible build-side scans before conversion, reuse
CometSparkToColumnarExec, and guard AQE DPP broadcasts against non-Arrow inputs. - Correctness: Reviewed the conversion, schema, input-file metadata, join eligibility and DPP paths against Spark sources. Existing join restrictions remain enforced. No additional introduced P1/P2 correctness issue was found beyond the previously reported blockers.
- Compatibility analysis: Compared relevant Spark sources for 3.4.3, 3.5.9, 4.0.4, 4.1.3 and 4.2.0. Spark's DPP reuse depends on matching broadcast plans. The new scan tagging conflicts with the existing Spark 3.4 fallback, as already reported. Compared the touched paths with
branch-1.1ate9efd9f764ee0a59b7898ff028d6985d4a7a28e1. Automatic conversion is intentional. Lost pruning and broadcast reuse are unintended regressions. - Key design decisions: The feature defaults to enabled independently of general Spark-to-Arrow conversion. Broadcast-exchange conversion must also be enabled. CSV, JSON and Parquet conversion switches retain precedence, and tagging stops at nested joins.
- Implementation sketch:
CometExecRuletags build-side file/V2 scans when the probe contains only recognized native scans. The existing bridge converts their output.CometPlanAdaptiveDynamicPruningFilterschecks the DPP build separately before choosing its exchange type. Nine tests cover positive paths, configuration gates, aggregation, V2 scans, build orientation and DPP execution. - Performance: Conversion copies scan output before filters and aggregates, so broadcast output size does not bound conversion work. The existing review substantiates lost pruning, retained conversions after join fallback, and duplicate DPP broadcasts. No benchmark establishes a net speedup for those cases, and I make no additional performance claim.
- Design: Reusing the existing bridge keeps the execution mechanism simple. However, native scan eligibility does not guarantee native join eligibility. The existing request to restore the build subtree when the join declines addresses that mismatch.
- Abstraction & complexity: The helpers are narrowly scoped and introduce no new execution framework. The new
CometNativeExectest conflates class membership with Arrow-compatible output and excludes valid columnar operators such asCometUnionExec. This is covered by an existing blocker. - Behavioral changes worth calling out: Eligible unsupported file and V2 scans can now feed native broadcast joins by default. DPP can execute a separate Spark broadcast for an unbridged dimension. The new AQE guard also affects existing native queries when the feature switch is disabled.
- Suggested improvements: Resolve the existing requests to honor the Spark 3.4 broadcast skip tag, undo conversions when a join declines, and recognize compatible Comet columnar DPP builds. Regression assertions should verify pruning and exchange reuse. No additional improvement meeting the P1/P2 reporting bar was identified.
Reviewed the full four-file PR diff for 8932aad407544a51fbfc38167c3bb46f29a67946 against supplied base e8c7ee739d3530b309d38388ba1c41decdb8abea, using merge base f9dd86d482d136426990186c2f1d42eef6a1ca59. GitHub's changed-file list matches this scope. The PR remains non-draft. Read the review, conversation comment, eight inline comments and eight unresolved threads before forming conclusions.
Routed skills: review-comet-pr and review-comet-ffi-pr. Also checked contributor guidance on FFI ownership, memory accounting, timezones, configuration, development and CI.
Existing blockers remain substantiated by the reported reproductions and source tracing: Spark 3.4 DPP loss, retained conversion and lost reuse after join fallback, and AQE DPP rejecting valid columnar builds. These are not duplicated as new findings.
Exact-head CI: Latest check runs associated with the reviewed SHA show 60 successes and 53 skips, with none failed or running. Comet suites passed across Spark 3.4–4.2, and all eight Spark 4.1 SQL/Hive shards passed. Retrieved execution logs confirm all nine new tests passed on 4.1. On 3.4, eight passed and the explicitly unsupported AQE DPP test was canceled. These jobs tested merge commits containing this head. The default job merged the supplied base, while the later profile run merged newer main.
Validation limits: No local JVM/native tests or benchmarks were run. This checkout has no compiled artifacts or Spark dependency cache. Runtime evidence comes from CI and the existing review's reproductions, corroborated by source inspection. macOS, dedicated Iceberg suites and other versions' full Spark SQL suites were skipped. git diff --check passed.
No additional introduced P1/P2 issues found within this review beyond the existing unresolved concerns.
parthchandra
left a comment
There was a problem hiding this comment.
Thank you for the review @andygrove. Addressed your comments and also added a paragraph to datasources.md about the new default and how to turn it off
09fb41f to
3738210
Compare
andygrove
left a comment
There was a problem hiding this comment.
Thanks for the quick turnaround on these. I re-ran my probes against 3738210b84. The 3.4 fix works: with AQE the Iceberg fact reads 400 rows instead of 1000, and the broadcast is reused. The union dimension gets its ReusedExchange back under AQE, and turning off spark.comet.exec.broadcastHashJoin.enabled no longer leaves a bridge behind. The ORC and Iceberg probes still match Spark on 3.4 and 4.1.
The revert for joins that stay on Spark doesn't take effect yet, though. A FULL OUTER nested loop join still keeps the bridge under a Spark broadcast, and with DPP the dimension is still read twice. That's the slowdown from my first review, so I'm keeping the request for changes. Details are in the thread.
None of the three fixes has a test yet, so I've asked for one in each thread. CI is green, but none of the suites reaches the revert path.
…apache#6008) Round-2 review fixes for PR apache#6786: - Replace the never-firing revertBridgedBroadcasts (the bridge is a CometScanWrapper leaf, so .exists never saw it, and the rebuild from originalPlan mishandled CometSinkPlaceHolder, unions and native scans) with clearWastedBuildSideBridges: after the transform, clear BROADCAST_BUILD_SIDE_TAG from scans still under a plain Spark BroadcastExchangeExec and transform once more. A declined broadcast join then rebuilds its build side exactly as it would without the feature, and the AQE stage pass no longer re-bridges the scan. - Fix stale docstrings on broadcastBuildSideBridgeEnabled and isNativeBuildSide. - Add FULL OUTER BNLJ revert tests (filter/aggregate/union build sides), a Spark 3.4 DPP test, and a UNION ALL AQE DPP reuse test to CometExecSuite. - Add CometBroadcastBuildSideSuite (ORC and Iceberg build-side probes) and register it in the PR build workflows.
3738210 to
59463e1
Compare
…apache#6008) Round-2 review fixes for PR apache#6786: - Replace the never-firing revertBridgedBroadcasts (the bridge is a CometScanWrapper leaf, so .exists never saw it, and the rebuild from originalPlan mishandled CometSinkPlaceHolder, unions and native scans) with clearWastedBuildSideBridges: after the transform, clear BROADCAST_BUILD_SIDE_TAG from scans still under a plain Spark BroadcastExchangeExec and transform once more. A declined broadcast join then rebuilds its build side exactly as it would without the feature, and the AQE stage pass no longer re-bridges the scan. - Fix stale docstrings on broadcastBuildSideBridgeEnabled and isNativeBuildSide. - Add FULL OUTER BNLJ revert tests (filter/aggregate/union build sides), a Spark 3.4 DPP test, and a UNION ALL AQE DPP reuse test to CometExecSuite. - Add CometBroadcastBuildSideSuite (ORC and Iceberg build-side probes) and register it in the PR build workflows.
59463e1 to
6ba6a5b
Compare
…apache#6008) Round-2 review fixes for PR apache#6786: - Replace the never-firing revertBridgedBroadcasts (the bridge is a CometScanWrapper leaf, so .exists never saw it, and the rebuild from originalPlan mishandled CometSinkPlaceHolder, unions and native scans) with clearWastedBuildSideBridges: after the transform, clear BROADCAST_BUILD_SIDE_TAG from scans still under a plain Spark BroadcastExchangeExec and transform once more. A declined broadcast join then rebuilds its build side exactly as it would without the feature, and the AQE stage pass no longer re-bridges the scan. - Fix stale docstrings on broadcastBuildSideBridgeEnabled and isNativeBuildSide. - Add FULL OUTER BNLJ revert tests (filter/aggregate/union build sides), a Spark 3.4 DPP test, and a UNION ALL AQE DPP reuse test to CometExecSuite. - Add CometBroadcastBuildSideSuite (ORC and Iceberg build-side probes) and register it in the PR build workflows.
6ba6a5b to
657044b
Compare
andygrove
left a comment
There was a problem hiding this comment.
Thanks for reworking this. I re-ran my probes against 657044b7a2, on 4.1 and 3.4. Every join that stays on Spark now gets the same plan as with the feature off, with AQE on and off. That covers the FULL OUTER and build-left LEFT OUTER nested loop joins, aggregate and union build sides, and a probe side with an operator that stays on Spark. DPP over a join that declines scans the dimension once and reuses the broadcast again, and on 3.4 the native Iceberg fact still reads 400 rows. The new tests catch the regressions too. Turning off the clear fails the three no-bridge tests, the old CometNativeExec check fails the union test, and removing the skip check and the clear together fails the Iceberg DPP test on 3.4.
I've dismissed my earlier request for changes, since the bridge it was about is fixed. I left three more comments below that I'd like to see addressed before approving.
Dismissing this since the bridge left under a Spark broadcast is fixed in 657044b7a2. My remaining comments are in the new review.
Round-3 review on PR apache#6786: - clearScanTags now stops at a CometBroadcastExchangeExec, so a nested native broadcast join inside a Spark-staying join's build keeps its bridge, and it only counts a clear (and asks for the second transform) when the tagged scan sits under a CometSparkToColumnarExec. A tagged but unbridged CSV/JSON/Parquet scan (per-format convert off) no longer forces a wasted second transform. - Recomment the 3.4 Parquet DPP test: on 3.4 the V1 fact with a DPP filter stays on Spark (CometScanRule.scala:199), so nothing is tagged - it covers the V1 fallback, while the Iceberg test covers the SKIP_COMET_BROADCAST_TAG path with a native fact.
andygrove
left a comment
There was a problem hiding this comment.
Thanks for the quick fixes. I re-ran my probes against 35a9aff95f on 3.4 and 4.1, and all three asks check out.
The CSV dimension no longer triggers the second transform. On the six-join query the count went from 94 to zero, and planning with the feature on now takes the same time as with it off, with AQE on and off.
The nested case keeps its bridge now. In the FULL OUTER nested loop join over a broadcast join of a Parquet table with a text table, the inner join runs as a CometBroadcastHashJoinExec, the outer join stays on Spark, and the answer matches Spark with AQE on and off. Every other shape I probed gives the same plan as last round, so the joins that stay on Spark still match the feature-off plan.
The renamed 3.4 test reads right to me now. I left two optional nits inline.
| var cleared = false | ||
| def clearScanTags(node: SparkPlan, underBridge: Boolean): Unit = node match { | ||
| // A nested broadcast join that went native keeps its bridge. | ||
| case _: CometBroadcastExchangeExec => |
There was a problem hiding this comment.
Nit, optional: nothing in the PR pins this case. With it removed, every #6008 test still passes on 3.4, and the two that only run on 3.5+ have no nested join. My nested probe does catch it, because the inner join goes back to Spark. Could we add that shape to the Spark-staying tests? A FULL OUTER nested loop join whose build side is a broadcast join of a Parquet table with a text table would do. It could assert that the inner join is a CometBroadcastHashJoinExec with a CometSparkToColumnarExec under its CometBroadcastExchangeExec, and that the outer join stays a Spark BroadcastNestedLoopJoinExec.
|
|
||
| test("Spark 3.4 AQE DPP with a V1 Parquet fact and a text dimension (#6008)") { | ||
| // On Spark 3.4, CometScanRule keeps a V1 scan that carries an AQE DPP filter on Spark | ||
| // (CometScanRule.scala:199), so this Parquet fact is not native, hasOnlyNativeScans is false, |
There was a problem hiding this comment.
Nit, optional: the CometScanRule.scala:199 reference is my fault, since I quoted the line number. It will point at the wrong line after the next edit to that file. Could the comment name the check instead, for example the "AQE Dynamic Partition Pruning requires Spark 3.5+" fallback in CometScanRule.transformV1Scan?
…6008) Optional nits from the PR apache#6786 approval: - Add a Spark-staying test where a FULL OUTER nested loop join's build is a broadcast hash join of a Parquet table with a text table, asserting the inner join is a native CometBroadcastHashJoinExec that keeps its text bridge while the outer join stays a Spark BroadcastNestedLoopJoinExec. This pins clearWastedBuildSideBridges stopping at CometBroadcastExchangeExec. - Replace the brittle CometScanRule.scala:199 line reference in the 3.4 test with the name of the check (the Spark 3.5+ AQE DPP fallback in CometScanRule.transformV1Scan).
|
@andygrove, thank you for the approval. Addressed your latest comments as well. Will wait for ci and any further comments. |
…n configs Converting a Text scan to Arrow replaces bytes that are not valid UTF-8 with U+FFFD, so bridging a Text build side by default (apache#6786) could return different results from 1.1.0 (apache#7118). The other formats that reach the bridge either define strings as UTF-8 (ORC, Avro, Iceberg) or are decoded by Spark, so spark.comet.convert.broadcastBuildSide.enabled goes back to on by default, and Text gets its own configs: - spark.comet.convert.broadcastBuildSide.text.enabled (default false) bridges a Text build side. Without it, the scan records a fallback reason that names the config. - spark.comet.convert.text.enabled (default false) converts every Text scan, like the CSV, JSON and Parquet configs. The sparkToColumnar operator list still converts Text as before. The compatibility guide now names Text rather than CSV and JSON, no longer says that Spark requires Parquet strings to be valid UTF-8, adds Comet columnar shuffle as a path, and gives the configs that keep min/max over CAST(binary AS STRING) in Spark.
…n configs (apache#7123) * fix: default broadcast build side conversion to off and document invalid UTF-8 cases Converting a Text scan on a broadcast build side to Arrow decodes invalid UTF-8 to U+FFFD, so 1.2.0 could return different results from 1.1.0. Default spark.comet.convert.broadcastBuildSide.enabled to false and name the Text-source and CAST(binary AS STRING) cases in the compatibility guide. * test: move the apache#6008 build-side tests into CometBroadcastBuildSideSuite CometBroadcastBuildSideSuite turns the build-side bridge on for every test, so the tests no longer need the bridgeTest helper, and a test added later cannot miss the setting. The two tests that failed because they were not wrapped in bridgeTest now run with the bridge on, and the five that asserted nothing was bridged now exercise the bridge again. The test bodies are unchanged. * fix: keep Text build sides on Spark by default and add Text conversion configs Converting a Text scan to Arrow replaces bytes that are not valid UTF-8 with U+FFFD, so bridging a Text build side by default (apache#6786) could return different results from 1.1.0 (apache#7118). The other formats that reach the bridge either define strings as UTF-8 (ORC, Avro, Iceberg) or are decoded by Spark, so spark.comet.convert.broadcastBuildSide.enabled goes back to on by default, and Text gets its own configs: - spark.comet.convert.broadcastBuildSide.text.enabled (default false) bridges a Text build side. Without it, the scan records a fallback reason that names the config. - spark.comet.convert.text.enabled (default false) converts every Text scan, like the CSV, JSON and Parquet configs. The sparkToColumnar operator list still converts Text as before. The compatibility guide now names Text rather than CSV and JSON, no longer says that Spark requires Parquet strings to be valid UTF-8, adds Comet columnar shuffle as a path, and gives the configs that keep min/max over CAST(binary AS STRING) in Spark. * test: drop the QueryStageExec import that the test move left unused * fix: convert a Text build side only with spark.comet.convert.text.enabled Drop spark.comet.convert.broadcastBuildSide.text.enabled. A Text scan, on a build side or not, is now converted only by spark.comet.convert.text.enabled, like CSV, JSON and Parquet, and the build-side fallback reason names that config. Text goes through isConversionEnabled, so naming FileSourceScan or BatchScan in spark.comet.sparkToColumnar.supportedOperatorList still converts it but is deprecated. The apache#6008 build-side tests read ORC instead of Text, since a global Text switch would convert their probes and no-bridge assertions too. * docs: describe Text conversion through its own config only Update the Text, build-side and compatibility sections for the removal of spark.comet.convert.broadcastBuildSide.text.enabled, and note in the migration guide that converting Text through sparkToColumnar is deprecated. * fix: drop the unused SparkConf import and narrow the min/max workaround The suite no longer overrides sparkConf, so its SparkConf import failed Scalafix and the strict-warnings build. Reformat a CometExecRule comment that Spotless rejected. The three-config min/max workaround only keeps the decode out of the query when the cast is the aggregate's own argument. A cast in a projection below the aggregate runs natively and decodes first, so the guide now says so and keeps disabling Comet as the general answer.
…n configs (#7123) (#7205) * fix: default broadcast build side conversion to off and document invalid UTF-8 cases Converting a Text scan on a broadcast build side to Arrow decodes invalid UTF-8 to U+FFFD, so 1.2.0 could return different results from 1.1.0. Default spark.comet.convert.broadcastBuildSide.enabled to false and name the Text-source and CAST(binary AS STRING) cases in the compatibility guide. * test: move the #6008 build-side tests into CometBroadcastBuildSideSuite CometBroadcastBuildSideSuite turns the build-side bridge on for every test, so the tests no longer need the bridgeTest helper, and a test added later cannot miss the setting. The two tests that failed because they were not wrapped in bridgeTest now run with the bridge on, and the five that asserted nothing was bridged now exercise the bridge again. The test bodies are unchanged. * fix: keep Text build sides on Spark by default and add Text conversion configs Converting a Text scan to Arrow replaces bytes that are not valid UTF-8 with U+FFFD, so bridging a Text build side by default (#6786) could return different results from 1.1.0 (#7118). The other formats that reach the bridge either define strings as UTF-8 (ORC, Avro, Iceberg) or are decoded by Spark, so spark.comet.convert.broadcastBuildSide.enabled goes back to on by default, and Text gets its own configs: - spark.comet.convert.broadcastBuildSide.text.enabled (default false) bridges a Text build side. Without it, the scan records a fallback reason that names the config. - spark.comet.convert.text.enabled (default false) converts every Text scan, like the CSV, JSON and Parquet configs. The sparkToColumnar operator list still converts Text as before. The compatibility guide now names Text rather than CSV and JSON, no longer says that Spark requires Parquet strings to be valid UTF-8, adds Comet columnar shuffle as a path, and gives the configs that keep min/max over CAST(binary AS STRING) in Spark. * test: drop the QueryStageExec import that the test move left unused * fix: convert a Text build side only with spark.comet.convert.text.enabled Drop spark.comet.convert.broadcastBuildSide.text.enabled. A Text scan, on a build side or not, is now converted only by spark.comet.convert.text.enabled, like CSV, JSON and Parquet, and the build-side fallback reason names that config. Text goes through isConversionEnabled, so naming FileSourceScan or BatchScan in spark.comet.sparkToColumnar.supportedOperatorList still converts it but is deprecated. The #6008 build-side tests read ORC instead of Text, since a global Text switch would convert their probes and no-bridge assertions too. * docs: describe Text conversion through its own config only Update the Text, build-side and compatibility sections for the removal of spark.comet.convert.broadcastBuildSide.text.enabled, and note in the migration guide that converting Text through sparkToColumnar is deprecated. * fix: drop the unused SparkConf import and narrow the min/max workaround The suite no longer overrides sparkConf, so its SparkConf import failed Scalafix and the strict-warnings build. Reformat a CometExecRule comment that Spotless rejected. The three-config min/max workaround only keeps the decode out of the query when the cast is the aggregate's own argument. A cast in a projection below the aggregate runs natively and decodes first, so the guide now says so and keeps disabling Comet as the general answer. (cherry picked from commit 2b1c6a3)
…dit mode build() now reports a failed Sphinx run instead of comparing empty warning lists, builds the working tree in audit mode, and uses a named virtual environment, as does the H1 check. Audit mode gets a config-key command, the stop rule no longer treats an empty search as an error, the generated-content check says what to compare, and the apache#6786 case reads the guide from main. Generated-by: OpenCode
* docs: add a documentation style guide Add docs/source/contributor-guide/style_guide.md, which writes down the vocabulary from apache#4419 (pipeline membership, implementation language and data format), the rule for "native", how to name operators in prose, and the page conventions that the docs-review skill in apache#4420 cites. Each rule has a stable ID so that reviews can cite it. The guide follows the code where apache#4419 and the code differ: "Hybrid" keeps the meaning it already has in expressions.md, expressions are described separately from plan nodes, and "Arrow-native" is a claim about data format only. The Generated Content section names the generated text exactly, by the markers GenerateDocs fills in, including the config tables built from QueryPlanSerde and contrib config classes, and the cast tables built from CometCast. P18 covers attribution of third-party content, which apache#4420 lists among its checks. The guide's scope covers .doc strings in contrib config classes, and its vocabulary and accuracy rules also apply to doc comments, AGENTS.md and agent skills. Link the page from the contributor guide toctree and from AGENTS.md. Part of apache#4419. Generated-by: OpenCode * docs: add a docs-review skill Add .ai/skills/review-comet-docs/SKILL.md, a sibling of review-comet-pr for documentation. It has three modes: PR mode reviews the docs a pull request changes, branch mode reviews a branch before it becomes a PR, and audit mode reviews an existing file or directory, including files not yet committed. The skill reads the documentation style guide on every run and cites its rule IDs rather than restating the rules. It finds the Apache remote and the PR's real base, reads everything from the reviewed revision, checks whether main has moved since, and checks only the added lines in PR and branch mode. Mechanical checks come first: vague "native" (including in Markdown italics), config keys (including keys built from a name at run time), operator names, links, anchors, images, external links, structure, redirects for moved pages, and version claims. Then it runs judgment checks, the most important being whether each claim, and each condition in it, still matches the code. Besides docs pages, it checks changed .doc strings, doc comments, AGENTS.md and agent skills. Findings are must-fix, should-fix, or nit, with nits dropped when the skill runs as part of a review-comet-pr review. It never edits files or posts comments. A short table of fixed cases says how to check the skill after changing it. In review-comet-pr, route docs changes to the new skill, mention documentation in its description, and remove "docs only" from the list of PRs it covers on its own. Fix two things in it that no longer match: `gh pr checks --failed` no longer exists, and the opt-in key is spark.comet.expression.<Name>.allowIncompatible. In review-comet-pr and review-comet-expression-pr, point at the style guide for exactly which docs text is generated. Closes apache#4420. Generated-by: OpenCode * docs: make review-comet-docs runs reproducible and check links with Sphinx - Pin the reviewed commit (HEAD_SHA) after checking it against headRefOid, and read everything at it, including the style guide. - List changed files with git diff instead of gh pr view --json files, which stops at 100 files. - Fail on a missing merge base (shallow clones) instead of diffing against an empty revision. - Check drift against the PR's own target branch, not always main. - Name the state file per run and quote its values, so concurrent reviews and paths with spaces work. - Use git grep -E with perl instead of git grep -P, so Git builds without PCRE work, and set core.quotePath=false for non-ASCII names. - Replace the hand-written link, anchor, toctree, and heading checks with a Sphinx build of the base and the head, compared by warning. It resolves the generated per-version pages the way docs/build.sh does, catches links that a renamed heading breaks on unchanged pages, handles reStructuredText, and resolves toctree entries properly. H1s are counted with markdown-it, which handles nested code fences. - Route AGENTS.md and agent skills to the skill from review-comet-pr. Part of apache#4420. Generated-by: OpenCode * docs: retry the merge base after deepening, sanitize the state file name, keep the N5 test case stable The merge-base step now tries again after deepening and stops with an error if there is still no base. Branch names with a slash no longer break the state file path. The N5 case moves to the scratch page, so it holds before and after apache#7151 rewrites understanding-comet-plans.md. Generated-by: OpenCode * docs: make the docs build and heading check fail loudly and run in audit mode build() now reports a failed Sphinx run instead of comparing empty warning lists, builds the working tree in audit mode, and uses a named virtual environment, as does the H1 check. Audit mode gets a config-key command, the stop rule no longer treats an empty search as an error, the generated-content check says what to compare, and the apache#6786 case reads the guide from main. Generated-by: OpenCode * docs: fix what seven agents hit when running the docs review skill Dry runs on Claude, GPT, Gemini and Grok models in zsh on macOS found these gaps. The venv now picks Python 3.10 or later and cleans up on failure, since the macOS system Python cannot install the docs requirements. Each call reloads the state with a given line, and a failed docs build no longer reads as a reason to stop. The guide falls back to the target when the head predates it. Audit mode reads the working tree for the H1 check and code reads, and skips the rename check. Code files are filtered to doc comments, SVG text is reviewed, protobuf message names are not operators, and the build output no longer carries the macOS /private prefix. Generated-by: OpenCode * docs: make the native rules testable N1 now covers every form of the banned phrases, such as ran natively and Comet-native. N2 allows the native shuffle path without pairing it with JVM shuffle. N3 replaces and similar with a test, names user-guide/, and says pages outside the contributor guide allow only the N2 compounds. Agents running the guide could not apply the old wording consistently. Generated-by: OpenCode (cherry picked from commit 80d4af0) * docs: allow Arrow-native in N2 and close the native rules T7 lists Arrow-native, which N3's limit to N2 compounds outside the contributor guide would otherwise forbid. N3 also says any other use of native is a finding, so a phrase outside the lists has an outcome. Generated-by: OpenCode (cherry picked from commit ad78e9b) * docs: save the file list, read the guide from the working tree as a last resort The changed files that step 3 selects are now written to a file saved in the state, so later calls pass them to Git instead of rebuilding the list by hand. When neither the head nor the target has the guide, as for a PR made before it existed, the skill reads the working tree's copy and says so, instead of stopping. Audit mode leaves the revision out of the code searches, because an empty quoted revision makes Git fail. Generated-by: OpenCode * docs: count audit files for the cap, skip test sources, name the guide fallback Audit mode has no ADDED list, so the 10-file cap now counts the files step 1 lists. Test sources no longer enter the file list, since their string literals passed the doc-comment filter. The stop rule names the step 2 guide lookup as a fallback, and the key notes cover the EXPRNAME placeholder pages use. Generated-by: OpenCode * docs: limit the N2 scan compounds to Rust-implemented scans A dry run read native cache scan as allowed by N2, but the in-memory cache scan runs on the JVM. N2 now says the scan and writer compounds apply only to Rust-implemented ones. Generated-by: OpenCode (cherry picked from commit e8b8000) * docs: point N1 at T1 for not-a-fallback and at N5 for Spark A dry run rewrote "falls back to its native execution" and "executes natively (not falling back to Spark)" wrongly, because N1 offered only T1 or T3. N1 now says not falling back is T1, since codegen dispatch is not a fallback, and that Spark's own behavior is N5. Generated-by: OpenCode (cherry picked from commit 147f3f4) * docs: save the audit file list as PATHS Audit mode used an undefined <files> placeholder. It now saves the step 1 list as PATHS, as the other modes do, and says the mechanical checks cover every file while only the judgment checks are capped. Generated-by: OpenCode * docs: apply the style guide to new and changed text Existing pages move over as they are edited, so a review does not ask a change to restyle text it leaves alone, unless the change makes that text wrong (P16). Requested in review. Generated-by: OpenCode (cherry picked from commit fdff0b1) * docs: keep paths with spaces whole, build each revision with its own requirements The file list was passed as $(cat "$PATHS"), which splits a name such as "My Page.md" into two pathspecs, so the page was silently skipped. Commands now pass it through tr and xargs -0 -r. The docs build reused one environment installed from the checkout's requirements; a new venv function installs each revision's own docs/requirements.txt into an environment named after its hash, so a change that updates a Sphinx extension is built with it. Requested in review. Generated-by: OpenCode * docs: list audit files unquoted, note generated expression anchors Audit mode listed files with Git's default path quoting, so a page such as résumé.md became an escaped string and was skipped. The docs build copies only the expression templates, so a link to a heading that GenerateDocs adds to a per-version page warns although the published page has it; the skill now says to check the serde's notes instead. Requested in review. Generated-by: OpenCode * docs: print audit search results with unquoted file names The audit searches now pass core.quotePath=false too, so a finding on a page such as résumé.md names the file as written. Generated-by: OpenCode * docs: match the category page and Spark version for generated anchors A heading GenerateDocs adds appears only on the category page whose serde map holds the expression, at the Spark versions that register it. The manual check now confirms both, so a link to the same heading on another category page is reported as broken. Requested in review. Generated-by: OpenCode
Auto-insert CometSparkToColumnarExec at an unsupported FileSourceScan/BatchScan on a broadcast join's build side when the probe side is natively scannable, so the BroadcastExchange and the join run natively over the large probe. Gated by spark.comet.sparkToColumnar.broadcastBuildSide.enabled (default true) together with the Comet broadcast-exchange config. Guard the AQE DPP rule (convertSAB) against wrapping a non-native DPP subquery build in a CometBroadcastExchangeExec.
Which issue does this PR close?
Part of #6008.
Rationale for this change
An unsupported leaf scan (for example a Text file source,
[COMET: Unsupported file format Text])on the build side of a broadcast join cascades: the build branch (scan → transforms →
BroadcastExchange) stays on Spark, so the exchange never becomes aCometBroadcastExchangeExec,so the
BroadcastHashJoincan't go native — even when the large probe side is a fully nativescan+filter. A tiny lookup table read from an unsupported format disqualifies native execution of
the entire join over the big stream.
Comet already has a row→Arrow bridge (
CometSparkToColumnarExec, delivered as aCometScanWrapperthat is a
CometNativeExec), but it is off by default and itssupportedOperatorListexcludes filescans, so this path was never hit. This PR auto-inserts that bridge at an unsupported build-side
scan — but only when doing so actually lets the join run natively — so the bounded, small build side
is adapted to Arrow and the join over the large probe input is accelerated.
What changes are included in this PR?
New config
spark.comet.sparkToColumnar.broadcastBuildSide.enabled(defaulttrue,CometConf.scala). Independent of the generalspark.comet.sparkToColumnar.enabledopt-in.Auto-bridge on broadcast build sides (
CometExecRule.scala): a pre-pass(
tagBroadcastBuildSideLeaves) tags the build-sideFileSourceScanExec/BatchScanExecof aBroadcastHashJoinExec/BroadcastNestedLoopJoinExec, andshouldApplySparkToColumnarthenbridges a tagged scan — only in the unsupported-format arms, so the per-format
spark.comet.convert.{csv,json,parquet}.enabledopt-outs still take precedence.The bridge is applied only when:
spark.comet.exec.broadcastExchange.enabledare both on(otherwise the broadcast can't go native, so the bridge would be pointless), and
hasOnlyNativeScans) — i.e. the join canactually become a fully native
CometBroadcastHashJoinExec. Bridging a build side under a jointhat stays on Spark is wasteful and would change the build broadcast's subtree, breaking Spark's
dynamic-partition-pruning (DPP) broadcast reuse.
The tag descent walks the dimension's own filters/projects/aggregations but stops at a nested
join, so a large streamed input of a nested join inside the build side is not bridged.
DPP guard (
CometPlanAdaptiveDynamicPruningFilters.convertSAB): when the matched broadcastjoin is Comet, only build a
CometBroadcastExchangeExecfor the reused DPP subquery if thatsubquery's own build is native (
isNativeBuildSide); otherwise fall back to a SparkBroadcastExchangeExec. This prevents an AQE+DPP crash(
Comet execution only takes Arrow Arrays, but got ... OffHeapColumnVector) when the bridgeddimension's separate DPP build copy is row-based. Mirrors the existing non-AQE guard in
CometExecRule.rewriteInSubqueryPlan.How are these changes tested?
CometExecSuite(#6008), covering: a Text build side going native (AQE onand off); the broadcast-exchange-disabled gate; an unsupported scan below a build-side aggregate
(and that the shuffle query stage is not bridged); the per-format opt-out still winning; a DSv2
(
BatchScanExec) build side;BuildLeftandBroadcastNestedLoopJoinvariants; a non-nativeprobe leaving the build side un-bridged; and an AQE+DPP regression test (text dimension + a
partitioned Parquet fact) that pins the DPP guard. All assert result-equivalence to Spark via
checkSparkAnswer/checkSparkAnswerAndOperatorplus the expected plan shape.CometExecSuitepasses onspark-4.1; the project builds onspark-4.0andspark-4.1.dev/local-ci.sh spark sql_core-1is green, including theDynamicPartitionPruningV1/V2 suites that guard broadcast/DPP reuse (an earlier eager version ofthis change regressed those; the probe-native gate + DPP guard fix it).