[DRAFT][SPARK-51705][CONNECT][PYTHON] Support SparkSession.broadcast() (broadcast variables over Spark Connect)#1
Draft
Tagar wants to merge 1 commit into
Draft
Conversation
Tagar
marked this pull request as ready for review
July 20, 2026 23:00
Tagar
force-pushed
the
broadcast-connect-python-v1
branch
from
July 20, 2026 23:10
62b1847 to
dc11a44
Compare
Tagar
marked this pull request as draft
July 20, 2026 23:10
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
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.
What changes were proposed in this pull request?
DRAFT / DO-NOT-MERGE — design preview to anchor the SPARK-51705 discussion. Not for merge in current form.
This PR adds a broadcast-variable API to Spark Connect (Python-only v1), closing the gap where a Connect/Serverless client cannot create a broadcast variable and reference it from a Python UDF (today this fails at the worker with
BROADCAST_VARIABLE_NOT_LOADED).Design: Candidate A — server-mediated pickle lane reusing classic
Broadcast[T]/TorrentBroadcast.The transport reuses the existing
cache/<sha256>artifact channel (content-addressing, dedup, per-sessionCacheId(sessionUUID,hash)isolation, ref-counted GC, transparent at-rest encryption) rather than adding a newbroadcasts/prefix. The typed handle comes fromCreateBroadcastResult, not the artifact prefix.Why are the changes needed?
JIRA: https://issues.apache.org/jira/browse/SPARK-51705 (
[CONNECT] Support sc.broadcast over Spark Connect, open, unassigned). Martin Grund endorsed "lift broadcast to the SparkSession" on-list. Broadcast is a core PySpark primitive with no Connect equivalent; Databricks Serverless / shared clusters run Connect, so the current guidance is to abandon shared/serverless for single-user clusters — a governance regression this closes.Carrier decision (re-verified against current master, 2026-07-20)
broadcast_idsrides the legacyPythonUDFproto message (field 6), resolved inSparkConnectPlanner.transformPythonFunctionintoSimplePythonFunction.broadcastVars. This was the #1 gate to re-check:udf/worker/module +ExternalUserDefinedFunction/ExternalUDFExecunder a default-OFF experimental SQLConf (spark.sql.execution.udf.unified.execution.enabled, v4.2.0). It is not wired into Spark Connect (0 references undersql/connect), added no.proto, and has no broadcast handling at all. So the legacyPythonUDFcarrier is correct and unobstructed for v1. Do NOT putbroadcast_idson anyudf/worker/proto/*message.PythonWorkerUtils.writeBroadcastsunchanged;core/broadcast.pyunchanged). Note: the Python worker decode was refactored (SPARK-56519 / SPARK-56324) —worker_util.setup_broadcastsnow delegates topyspark.worker_message.BroadcastInfo.from_stream— but this is Python-internal and format-preserving, so the Scala-side Connect changes need no worker edits.How was this patch tested?
(To be added; DRAFT skeleton.)
broadcastout oftest_connect_compatibility.py::expected_missing_connect_methods.spark.broadcast(dict_10k)referenced in a UDF over a >=1M-row DataFrame returns correct results (the exact case that fails today).spark.io.encryption.enabled=true): value re-encrypted at rest on server, worker reads viaEncryptedPythonBroadcastServer.unpersist()/destroy()frees driver+executor blocks and releases theRefCountedCacheId;SessionHolder.close()sweeps.broadcast_id->BROADCAST_NOT_FOUND(loud), not a silent empty list.Caveats / scope
Local verification status (2026-07-20)
protobuf==6.33.5,mypy-protobuf==3.3.0,bufremote pluginsprotocolbuffers/python:v33.5) viadev/connect-gen-protos.sh— the_pb2.py/_pb2.pyidiffs are limited to the new broadcast fields/messages; runtime import + field round-trip verified under protobuf 6.33.5.ruff check(pinnedruff==0.14.8) passes;ruff format --checkclean on all new/modified files.build/sbt), fullmypy(needs numpy/pandas-stubs), and the Connect E2E test suite (needs a running Connect server). These will run under Apache CI.Scope / caveats
transformPythonTableFunction) and DataSource (transformPythonDataSource) paths intentionally keep empty broadcasts; Scala/ScalarScalaUDFdeferred to a separate JIRA.ExternalUserDefinedFunction(opaque payload, no broadcast field) and the SQLConf flips to default-on, broadcasts need re-plumbing on that carrier. Tracked, not a v1 blocker.This pull request and its description were written by Isaac.
Proto diff
Companion: Scala UDF spike (
broadcast-connect-scala-spike)The anchor customer workload consumes its broadcast inside a Scala UDF, which Python-only v1 does not cover. The companion branch
broadcast-connect-scala-spikeis an exploratory spike (not for merge) documenting the path (seeSCALA-SPIKE-FINDINGS.md):Broadcast[T]object graph, so a broadcast id must be resolved during closure deserialization — unlike Python, where the id ridesPythonUDF.broadcast_idsand is injected intoSimplePythonFunction.broadcastVarswithout touching the closure.ConnectBroadcast[T].writeReplaceemits an id-only token; serverreadResolveswaps the id for the registryBroadcast[_]via a thread-local aroundunpackScalaUDF. Stacks on this PR'sSessionHolderregistry.SPARK-46032(closure deserialization on Connect) is a related reliability risk, not a strict blocker.Provenance
Design of record and cited impact analysis were developed in a companion research effort; this PR implements the "Candidate A" pickle lane. Note: the fork's base history is
apache/sparkand contains Apache's own SSL test-fixture PEM keys (core/src/test/resources/key.pem, etc.) — those are upstream fixtures, not introduced by these commits.