[#2771] feat(spark): Support Spark 4.2 - #2772
Conversation
Add a mutually-exclusive `spark4.2` Maven profile that builds the existing
client-spark/spark4 module against Apache Spark 4.2.0, mirroring spark4.1.
The three cross-major APIs called from the spark4 module (MapStatus.apply,
the ExternalSorter ctor, WebUIPage.render) are byte-identical between 4.1.1
and 4.2.0 (verified with javap), so the src/main/java-spark4_1 Spark4Compat
shim and the scala-jakarta servlet variant are reused as-is — no new source.
Dependency pins for the profile:
- spark.version 4.2.0, scala.version 2.13.18
- netty.version 4.2.13.Final (what Spark 4.2.0 ships; still Netty 4.2)
- jackson.version 2.21.2 (jackson-module-scala 2.21.2 requires databind in
[2.21.0, 2.22.0), else RDDOperationScope's initializer throws)
- lz4 at.yawk.lz4:1.11.0 (Spark 4.2 calls LZ4BlockInputStream.newBuilder,
added in the 1.11 line; lz4-java relocated org.lz4 -> at.yawk.lz4 after
1.8.1). Introduce a ${lz4.groupId} property, flipped to at.yawk.lz4 in the
spark4.2 profile; both coordinates are version-managed in root DM.
CI: add a spark4.2 / java 17 matrix entry (parallel.yml) and a -Pspark4.2
step (sequential.yml).
…rofile The prior commit added at.yawk.lz4:lz4-java:1.11.0 to the root dependencyManagement with a hardcoded version. Because org.lz4:lz4-java:1.8.1 is a relocation stub that Maven rewrites to at.yawk.lz4:1.8.1, that unconditional 1.11.0 entry version-managed every transitive lz4 consumer (server, client, coordinator, cli, ...) up to 1.11.0 in the default build and all non-spark4.2 profiles -- an out-of-scope, undocumented change (the comments claimed the opposite), overriding the 1.8.1 pin from apache#2693. Route the at.yawk.lz4 version through a new ${lz4.version} property: 1.8.1 by default (restores the pre-PR state for the default build and spark2/3/4/4.1), 1.11.0 only in the spark4.2 profile where Spark needs LZ4BlockInputStream.newBuilder(). Also fix the misleading relocation comments and document that the spark4-shaded org.lz4 exclusion must not be parameterized (Maven matches exclusions on the as-declared coordinate; flipping it would leak at.yawk.lz4 into the shaded jar under -Pspark4.2). Verified: default build client/coordinator/server-common resolve at.yawk.lz4:1.8.1 again; -Pspark4.2 spark4 client + integration tests stay on 1.11.0 and pass; -Pspark4.1 unaffected; spark4.2 shaded jar still has no net.jpountz classes.
…s work on Spark 4.2 ShufflePage's collapsible sections use onClick="collapseTable(name, table)", relying on a global collapseTable() that Spark 3.x/4.0/4.1 ship in webui.js. Spark 4.2 removed that function (its own pages moved to Bootstrap 5 data-bs-toggle collapse), so under -Pspark4.2 clicking a section header threw "collapseTable is not defined" and no section (Build Info, Shuffle Server, Assignment, ...) could be expanded. Inline a fallback that defines a compatible collapseTable only when the host Spark didn't provide one: 3.x/4.0/4.1 keep Spark's implementation, 4.2 gets a working equivalent. It reuses the .collapsed / arrow-open|closed CSS classes that every Spark line still ships, so no styling change is needed. Verified end-to-end on Spark 4.2.0 against a local Uniffle cluster: the tab renders, and clicking Build Info / Shuffle Server / Assignment expands each table (Version 0.11.0-SNAPSHOT, server id, per-shuffle assignment shown) with no page JS errors.
End-to-end UI verification on a real Spark 4.2.0 clusterBeyond the automated build/tests, I verified the Uniffle Spark UI tab end-to-end against a real Spark 4.2.0 distribution with a local Uniffle cluster (coordinator + shuffle server). A shuffle job was run through This also surfaced and fixed a Spark 4.2-specific UI regression: Uniffle tab — sections expanded (Build Info / Shuffle Server / Assignment): The footer shows Uniffle tab — default (collapsed) view: |
| <!-- use the lz4 from spark env --> | ||
| <exclusions> | ||
| <!-- | ||
| Must stay org.lz4, do NOT switch to ${lz4.groupId}: Maven matches |
There was a problem hiding this comment.
Why don't we modify the lz4 group in the common module?
There was a problem hiding this comment.
I tried exactly that and it breaks the shaded client, which is why the exclusion has to keep the literal org.lz4.
If I switch common/pom.xml (and client-spark/common/pom.xml) to ${lz4.groupId}, then under -Pspark4.2 the transitive lz4 enters the graph as at.yawk.lz4:lz4-java, but spark4-shaded's exclusion matches on the as-declared coordinate, so <groupId>org.lz4</groupId> no longer matches and lz4 leaks into the shaded jar:
unshaded count: 94
check failed. # dev/scripts/checkshade.sh, 110 net/jpountz/** classes in the jar
Parameterizing the exclusion too (<groupId>${lz4.groupId}</groupId>) doesn't save it either — still 94 unshaded and the checkshade antrun step fails the build. With the current code (literal org.lz4 in both places) the same clean build gives net/jpountz count = 0 and check success.
There's also no benefit to changing it: the only 1.11-only API is LZ4BlockInputStream.newBuilder(), and that's called by Spark's own LZ4CompressionCodec, never by Uniffle. Uniffle's Lz4Codec only uses LZ4Factory.safeInstance() / fastCompressor / fastDecompressor / maxCompressedLength, all of which exist in 1.8.1 and 1.11.0 alike (the net.jpountz package name is unchanged by the groupId relocation). And on a real cluster the shaded client excludes lz4 and defers to Spark's env lz4 anyway.
So the split is deliberate: only the modules that must follow Spark's lz4 on the test classpath (integration-test/spark-common, integration-test/spark4) use ${lz4.groupId}; common / client-spark/common stay on the pre-relocation coordinate so the shade exclusion keeps working. That's what the comment on this line is there to prevent someone from "fixing" later. Happy to reword the comment if it reads more like a puzzle than a warning.
There was a problem hiding this comment.
I tried that approach before as well, and it didn't seem to cause any CI failures (https://github.com/apache/uniffle/pull/2768/checks). However, as you explained, there's also no benefit to changing it.
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## master #2772 +/- ##
============================================
+ Coverage 51.03% 51.07% +0.03%
- Complexity 3362 3366 +4
============================================
Files 533 533
Lines 25998 25998
Branches 2379 2379
============================================
+ Hits 13268 13278 +10
+ Misses 11868 11852 -16
- Partials 862 868 +6 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
|
GHA passed, I will merge this one |



What changes were proposed in this pull request?
Add a mutually-exclusive
spark4.2Maven profile that builds the existingclient-spark/spark4module against Apache Spark 4.2.0, mirroring thespark4.1profile added in #2751.pom.xml: newspark4.2profile — a structural clone ofspark4.1, differing only inspark.version=4.2.0,scala.version=2.13.18,netty.version=4.2.13.Final,jackson.version=2.21.2, and the lz4 coordinate (lz4.groupId+lz4.version, see below).client-spark/spark4/pom.xml:spark4.2profile reuses thesrc/main/java-spark4_1compat source root (the 4.1Spark4Compatshim).client-spark/extension/pom.xml:spark4.2profile reuses thescala-jakartaservlet source root.client-spark/extension—ShufflePage.scala: the Uniffle UI tab's collapsible sections useonClick="collapseTable(...)", relying on a globalcollapseTable()that Spark 3.x/4.0/4.1 ship inwebui.js. Spark 4.2 removed that function (its own pages moved to Bootstrap 5data-bs-togglecollapse), so under-Pspark4.2the sections could not be expanded. Inline a fallback that defines a compatiblecollapseTableonly when the host Spark didn't provide one — 3.x/4.0/4.1 keep Spark's implementation, 4.2 gets a working equivalent, reusing the.collapsed/arrow-open|closedCSS every Spark line still ships.${lz4.groupId}(org.lz4by default,at.yawk.lz4underspark4.2) and${lz4.version}(1.8.1by default,1.11.0underspark4.2); the two integration-test modules declare${lz4.groupId}:lz4-java, and theat.yawk.lz4root-dependencyManagemententry is pinned to${lz4.version}. The default build and every non-spark4.2profile stay onorg.lz4:1.8.1— the bump is scoped tospark4.2only.parallel.ymlgains aspark4.2 / java 17matrix entry;sequential.ymlgains a-Pspark4.2step gated onjava-version == 17.No new Maven module.
client-spark/spark4-shadedneeds no relocation change (its bouncycastle/jctools relocations are already global); a comment was added there noting its lz4 exclusion must stayorg.lz4— Maven matches exclusions on the as-declared coordinate, so parameterizing it would leakat.yawk.lz4into the shaded jar under-Pspark4.2.Why are the changes needed?
Fix: #2771
Users should be able to build and run Uniffle's Spark client against Spark 4.2.0 with full parity to the existing 4.1 support.
The three cross-major APIs called from
client-spark/spark4are byte-identical between 4.1.1 and 4.2.0 (verified withjavap), so no shim change is required:MapStatus.apply(BlockManagerId, long[], long, long)ExternalSorter(…, RowBasedChecksum[])ctorWebUIPage.render(jakarta…HttpServletRequest)Dependency notes:
jackson-module-scala_2.132.21.2, which validates the classpathjackson-databindon registration and throws inRDDOperationScope's static initializer if it is outside[2.21.0, 2.22.0). Same class of failure the 4.1 work hit.NioIoHandler/refCnt handling still applies — no code change).LZ4BlockInputStream.newBuilder()(added in the 1.11 line); without it the integration tests fail at runtime withNoSuchMethodError. Becauseorg.lz4:1.8.1is a relocation stub that Maven rewrites toat.yawk.lz4:1.8.1, a globally-managedat.yawk.lz4:1.11.0would silently bump lz4 across the whole reactor (server/client/coordinator/…), which is out of this PR's scope and would override theorg.lz4:1.8.1pin from chore: Update lz4 to address CVE-2025-12183 #2693 (CVE-2025-12183). The${lz4.groupId}/${lz4.version}properties confine the bump to thespark4.2profile; every other build stays on 1.8.1.Does this PR introduce any user-facing change?
No behavioral change to existing profiles. Adds a new opt-in
-Pspark4.2build profile, and fixes the Uniffle Spark UI tab so its collapsible sections expand under Spark 4.2. Existing-Pspark4(4.0.2) and-Pspark4.1(4.1.1) are unchanged.How was this patch tested?
Verified locally on JDK 17 (Zulu 17.0.18):
mvn clean install -Pspark4.2 -DskipTests— compiles + shades against 4.2.0 (spark-core/sql/catalyst 4.2.0, scala 2.13.18, netty 4.2.13.Final, jackson 2.21.2, lz4at.yawk.lz4:1.11.0resolved).mvn clean install -Pspark4.1 -DskipTests+ integration tests — 4.1.1 build andAQERepartitionTest/MapSideCombineTeststill green (lz4 staysorg.lz4:1.8.1); confirms no regression. Also confirmed the default build (no profile) and other spark profiles keep lz4 at 1.8.1.AQERepartitionTest,MapSideCombineTestpass under-Pspark4.2.TransportFrameDecoderTest(2 tests) passes on Netty 4.2.13 under-Pspark4.2.RepartitionWithLocalFileRssTest(NOOP/ZSTD/LZ4 codecs) passes under-Pspark4.2— exercises the lz4 runtime path.dev/scripts/checkshade.shon the spark4.2 shaded jar — "check success", 0 unshaded classes; bouncycastle/jctools relocated, nonet.jpountz(lz4) leak.RssShuffleManager, and the Uniffle Spark UI tab renders and its collapsible sections (Build Info / Shuffle Server / Assignment / …) expand with no page JS errors.CI runs the build + unit + integration tests via the new
spark4.2matrix entry /-Pspark4.2step.