Conversation
…e pipelines Behind --useStructuredStreaming, SparkPipelineRunner translates the fused Runner API pipeline into Spark Datasets. Impulse, Flatten, Reshuffle and GroupByKey are Dataset operations, and executable stages run through the existing Fn API bridge inside mapPartitions. Bounded pipelines run as batch Datasets. Unbounded input, user state and timers are rejected at translation. The branch clears the streaming option so the metrics accumulator and anything else reading it agree with how the job runs. The validatesPortableRunnerStructuredStreaming task runs the streaming PortableValidatesRunner suite on this backend and is the exit gate for making it the default for portable streaming pipelines.
d1b25b9 to
58719bd
Compare
|
Assigning reviewers: R: @Abacn for label build. Note: If you would like to opt out of this review, comment Available commands:
The PR bot will only process comments in the main thread (not review comments). |
| boolean isStreaming = pipelineOptions.isStreaming() || hasUnboundedPCollections(pipeline); | ||
| if (isStreaming) { | ||
| if (pipelineOptions.getUseStructuredStreaming()) { | ||
| // The Dataset backend evaluates its own leaves. It never starts a DStream context, and it |
There was a problem hiding this comment.
We shouldn't overwrite pipelineOptions.setStreaming here as it doesn't sounds semantically correct. We should prevent the logic ends up detecting "isStreaming" to avoid expansion involving DStream. The correct flag is "useStructuredStreaming"
"The Dataset backend rejects unbounded input at translation" is current supporting status and isn't a spec
There was a problem hiding this comment.
Fixed in f4a2d39. The runner no longer touches the streaming option. With useStructuredStreaming set it picks the Dataset translator and skips the DStream translator and streaming context. MetricsAccumulator.init got an explicit useCheckpoint argument, so the Dataset path never opens the DStream metrics checkpoint and the DStream path keeps its old condition. I also reworded the comments, option description and README to describe the unbounded rejection as current status.
| SparkPipelineOptions options = options(); | ||
| Pipeline p = Pipeline.create(options); | ||
| PCollection<String> words = | ||
| p.apply("impulse", Impulse.create()) |
There was a problem hiding this comment.
We can have a few full pipeline test but the majority of runner unit tests are targeted, testing component but not a pipeline. The latter is covered by validate runner tests.
There was a problem hiding this comment.
Fixed in f4a2d39. I kept one end to end test through the job invoker, with --streaming and --useStructuredStreaming like the gate task, and moved the rest into component tests. SparkDatasetPortablePipelineTranslatorTest translates single transforms on injected Datasets and runs stages in the embedded harness, SparkDatasetTranslationContextTest checks the persist and evaluation logic, and the rejection cases are asserted on the translator directly.
…onent tests Address review on apache#40129. - SparkPipelineRunner no longer sets the streaming option. The Dataset backend is selected by useStructuredStreaming, which also bypasses the DStream translator and the streaming context. - MetricsAccumulator.init takes an explicit useCheckpoint argument. The portable runner passes the streaming option restricted to the DStream path, so the Dataset backend never opens the DStream metrics checkpoint. - Reword the option description, README, translator javadoc and rejection message as current support status. - Replace the full-pipeline tests with component tests of the translator and its context. One end-to-end run through the job invoker remains.
Proof of concept for the Dataset-based portable Spark 4 backend discussed in the "[DISCUSS] Spark 4 portable runner direction" thread on dev@. Opening this as a draft so the discussion has concrete code to work from. Addresses #36841.
What this adds
An opt-in
--useStructuredStreamingflag on the portable Spark runner. With the flag set,SparkPipelineRunnertranslates the fused Runner API pipeline into Spark Datasets through a newSparkDatasetPortablePipelineTranslator:Impulse,Flatten,ReshuffleandGroupByKeybecome Dataset operations.GroupByKeygroups on the encoded key and runsSparkGroupAlsoByWindowViaOutputBufferFnper key. That is the generalGroupAlsoByWindowthe RDD batch translator falls back to when a windowing strategy is not eligible for theGroupNonMergingWindowsFunctionsfast paths. The Dataset path always takes the general branch, so it gives up the non-merging memory optimization and keeps theReduceFnRunnersemantics.Dataset.mapPartitionsthrough the existing Fn API bridge,SparkExecutableStageFunction, which is unchanged. Outputs are demultiplexed withEncoderHelpers.oneOfEncoder, followingParDoTranslatorBatch. Side inputs are collected during translation and broadcast.The translation context attaches to the
SparkContextthatSparkContextFactoryalready created, soSparkJobInvokerand result handling are unchanged. The runner selects the backend by the flag alone and leaves the streaming option as submitted.MetricsAccumulator.initgained an explicituseCheckpointargument, which the portable runner passes as the streaming option restricted to the DStream path, because that path is the only one using the metrics checkpoint. The translator lives in the sharedrunners/sparkbase and compiles for both the Spark 3 and Spark 4 modules.The flag name matches the terminology in the dev@ thread and is provisional. Today it only selects the Dataset translator. No Structured Streaming query is built yet.
The exit gate
Following Yi's suggestion on the thread,
validatesPortableRunnerStructuredStreamingruns the streamingPortableValidatesRunnersuite with the flag on, keeping exactly the same categories and test filters asvalidatesPortableRunnerStreaming, and aPostCommit Java PVR Spark4 StructuredStreamingworkflow runs it. That suite is the exit gate for flipping the default for portable streaming pipelines.What that gate currently measures is narrower than its name suggests.
--streamingdoes not by itself mark any PCollection unbounded, and the streaming exclusion list already removes the categories that would produce one.ValidatesRunnerextendsNeedsRunner, so tests annotated withNeedsRunnerandUsesUnboundedPCollectionsare outside the suite, and the remainingValidatesRunnertests carryingUsesUnboundedPCollectionsare each excluded byUsesTimersInParDo,UsesTestStream,UsesStrictTimerOrderingorUsesOnWindowExpiration. The first run therefore exercises bounded Dataset execution under the streaming exclusion list. The risk it actually probes is windowing, triggers and GroupByKey behaviour on this backend. This matches the point on the thread that the suite is mostly batch pipelines after the exclusions.The new task is deliberately not part of the aggregate
validatesPortableRunnertask while the backend is a preview.Out of scope, tracked separately
This does not modify the in-process Structured Streaming runner, including the work merged in #39906 and #39939 and the open #39576. It does reuse two of its classes,
EncoderHelpersandEvaluationContext, so the portable path also picks up the Spark 4EncoderHelpersoverride that came with the Spark 4 runner in #38255.Validation
SparkDatasetPortablePipelineTranslatorTestcovers one piece at a time. Flatten with mismatched input coders, GroupByKey with fixed windows and Reshuffle are translated on injected Datasets. Impulse, a multi-output stage and a side input go through the translator on a small pipeline and run in the embedded SDK harness. The unbounded, state and timer rejections are asserted on the translator directly. The gate excludesUsesSideInputs, so the side-input test here is the only side-input coverage.SparkDatasetTranslationContextTestcovers the persist decisions and that only unconsumed Datasets are evaluated.SparkDatasetPortableExecutionTestkeeps one end to end run through the job invoker with--streamingand--useStructuredStreaming, as the gate task sets them, and checks that the streaming option is left untouched. Fifteen tests, green on:runners:spark:4:testunder JDK 17 and on:runners:spark:3:testunder JDK 11.nullnessat class level, matching the surrounding translator code.Notes for review
Three things I would particularly like a second opinion on:
MetricsAccumulator.initoverload. The alternative was to readuseStructuredStreaminginsideMetricsAccumulator, which would tie a shared class to a portable-only flag.SparkSessionis never cleared. That is fine whenPortableBatchMode.stop()stops the context, and it accumulates when the context is provided or reused.Thank you for your contribution! Follow this checklist to help us incorporate your contribution quickly and easily:
addresses #123), if applicable. This will automatically add a link to the pull request in the issue. If you would like the issue to automatically close on merging the pull request, commentfixes #<ISSUE NUMBER>instead.CHANGES.mdwith noteworthy changes. Holding this until the flag is more than a preview.See the Contributor Guide for more tips on how to make review process smoother.
To check the build health, please visit https://github.com/apache/beam/blob/master/.test-infra/BUILD_STATUS.md
GitHub Actions Tests Status (on master branch)
See CI.md for more information about GitHub Actions CI or the workflows README to see a list of phrases to trigger workflows.