[POC][SPARK-51705][CONNECT][PYTHON] Support SparkSession.broadcast() (broadcast variables over Spark Connect)#57385
Open
Tagar wants to merge 1 commit into
Open
Conversation
Tagar
marked this pull request as ready for review
July 20, 2026 23:27
Tagar
force-pushed
the
broadcast-connect-python-v1
branch
3 times, most recently
from
July 21, 2026 01:18
e6bc6df to
75dd5dc
Compare
…) over Spark Connect Candidate-A pickle lane, Python-only v1: client cloudpickles the value and uploads it via the existing cache/<sha256> artifact channel; new CreateBroadcastCommand/UnpersistBroadcastCommand on ExecutePlan materialize a server-side Broadcast[PythonBroadcast] on the live driver SparkContext in SessionHolder; a new PythonUDF.broadcast_ids field (6) is resolved by SparkConnectPlanner into SimplePythonFunction.broadcastVars. Executor/worker path reused unchanged. broadcast_id == driver-side Broadcast.id so the worker's _broadcastRegistry/_from_id resolves it. DRAFT / DO-NOT-MERGE: proto _pb2.py stubs still need regen via dev/connect-gen-protos.sh (buf); BROADCAST_VALUE_TOO_LARGE quota not yet enforced; Scala/ScalarScalaUDF deferred (see companion spike branch). Co-authored-by: Isaac
Tagar
force-pushed
the
broadcast-connect-python-v1
branch
from
July 21, 2026 01:54
75dd5dc to
bc20d9f
Compare
Tagar
marked this pull request as draft
July 21, 2026 13:52
Tagar
marked this pull request as ready for review
July 21, 2026 14:01
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
[POC / DO-NOT-MERGE] — a design-preview PoC to anchor discussion on SPARK-51705. Posted in the spirit of the RDD-on-Connect PoC (#55888); not proposed for merge in current form. Feedback on the wire-protocol shape is explicitly solicited before this is polished into a mergeable series.
What changes were proposed in this pull request?
Adds a broadcast-variable API to Spark Connect (Python-only in this PoC), closing the gap where a Connect client cannot create a broadcast variable and reference it from a Python UDF. Today
spark.sparkContextraisesJVM_ATTRIBUTE_NOT_SUPPORTEDon a Connect client, and a UDF that captures a broadcast fails at the worker withBROADCAST_VARIABLE_NOT_LOADED.New public API — portable, byte-for-byte, with classic PySpark:
Design ("server-mediated pickle lane", reusing the classic
Broadcast[T]/TorrentBroadcastmachinery):Key properties:
SimplePythonFunction.broadcastVarsat the threeSparkConnectPlannersites that currently hard-code an empty list.broadcast_id== the driver-sideBroadcast.id, which is exactly what the worker keys on in_broadcastRegistry, so the classic_from_idunpickle contract resolves unchanged and UDF source is portable between classic and Connect.cache/<sha256>artifact channel (content-addressing, dedup, per-sessionCacheIdisolation, ref-counted GC, transparent at-rest encryption) — no new artifact namespace; the typed handle comes fromCreateBroadcastResult.Why are the changes needed?
Broadcast variables are a core PySpark primitive with no Spark Connect equivalent. Because Connect is the forward direction of the platform (and any classic-less deployment runs on it), the current guidance — "use a non-Connect cluster, or pass the value as a UDF parameter" — is a feature-parity gap and a portability cliff for commonly-taught code.
sc.broadcastre-serialized as a per-task parameter defeats the purpose of broadcasting. This implements the ask in SPARK-51705.Does this PR introduce any user-facing change?
Yes — a new
SparkSession.broadcast(value)method. In classic mode it is a thin alias forspark.sparkContext.broadcast(value)(no behavior change); in Connect mode it returns aConnectBroadcastproxy whose.valueand closure-capture semantics match the classicBroadcast. Additive proto fields only; gated behavior, no change to existing APIs.Proto surface (additive)
expressions.proto:PythonUDF.broadcast_ids(field 6).commands.proto:CreateBroadcastCommand/UnpersistBroadcastCommandon theCommandoneof (fields 20/21).base.proto:CreateBroadcastResultonExecutePlanResponse(field 24)._pb2.py/_pb2.pyiregenerated with the repo's pinned toolchain (protobuf==6.33.5,mypy-protobuf==3.3.0,bufremote plugins).Carrier note (re-verified against current master, 2026-07-20)
broadcast_idsrides the legacyPythonUDFmessage, resolved intoSimplePythonFunction.broadcastVars. The Language-agnostic / Unified UDF Protocol (SPARK-55278) is not wired into Spark Connect, adds no proto, and has no broadcast handling, so the legacy carrier is correct today. If Connect UDFs later migrate onto that path,broadcast_idswould move with them — tracked, not a blocker.How was this patch tested?
PoC. Locally: proto regenerated with the pinned toolchain and runtime-verified (field round-trip under protobuf 6.33.5); Python
ruff check+ruff format --checkclean. A newpython/pyspark/sql/tests/connect/test_connect_broadcast.pycovers the E2E dict-in-UDF, portability,unpersist/destroy, and the negativeBROADCAST_NOT_FOUNDcase; full E2E + Scala compile run under CI. Definition-of-done includes removingbroadcastfromtest_connect_compatibility.py::expected_missing_connect_methods.Scope / follow-ups
transformPythonTableFunction(UDTF) andtransformPythonDataSourcekeep empty broadcasts for now.ScalarScalaUDF) is a separate follow-up. A companion spike explored it: unlike Python (id injected intobroadcastVarswithout touching the closure), a Scala UDF is a Java-serialized closure carrying theBroadcast[T]object graph, so the id must be resolved during closure deserialization (clientwriteReplaceid-token → serverreadResolvefrom the registry). Related to closure-deserialization on Connect (SPARK-46032).AddArtifacts+ExecutePlancommands instead. Wire-protocol feedback welcome — happy to align.Related work
[POC] RDD in Python Spark Connect(HyukjinKwon), which enumeratedbroadcastamong the not-yet-supportedSparkContextmethods.