This is an automated email from the ASF dual-hosted git repository.
Abacn pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/beam.git
The following commit(s) were added to refs/heads/master by this push:
new a8ee2b84d37 [Spark 4] Group the streaming stateful ParDo by encoded
key bytes to support null keys (#40516)
a8ee2b84d37 is described below
commit a8ee2b84d37d014c3de8934b2e3ef8702260a2b2
Author: Tobias Kaymak <[email protected]>
AuthorDate: Mon Oct 19 17:37:24 2026 +0200
[Spark 4] Group the streaming stateful ParDo by encoded key bytes to
support null keys (#40516)
* [Spark 4] Group the streaming stateful ParDo by encoded key bytes to
support null keys
A null Beam key became a null Spark grouping key and transformWithState
failed with INTERNAL_ERROR_TWS. Grouping by the coder encoded key bytes is null
safe and matches Beam key equality. Part of #36841.
* Retrigger CI, flaky StatefulStreamingParDoEvaluatorTest on Spark 3.4
* Retrigger CI, flaky legacy Spark streaming tests in Spark Versions
* Retrigger CI, KafkaToPubsubE2ETest container startup flake in PreCommit
Java
* Use the structured streaming CoderHelpers in the stateful ParDo
* Merge the structured streaming CoderHelpers into the common one
* Decode with the common CoderHelpers in BeamMicroBatchSourceTest
---
...eam_PostCommit_Java_ValidatesRunner_Spark4.json | 2 +-
...a_ValidatesRunner_SparkStructuredStreaming.json | 2 +-
.../io/streaming/BeamReaderCache.java | 2 +-
.../io/streaming/UnboundedSourceDataset.java | 2 +-
.../batch/GroupByKeyTranslatorBatch.java | 2 +-
.../translation/helpers/EncoderHelpers.java | 1 +
.../streaming/ReadUnboundedTranslator.java | 2 +-
.../StatefulParDoStreamingTranslator.java | 10 ++--
.../streaming/state/BeamStatefulProcessor.java | 13 +++--
.../io/streaming/BeamMicroBatchSourceTest.java | 12 ++---
.../streaming/StatefulParDoStreamingTest.java | 36 +++++++++++++
.../batch/GroupByKeyTranslatorBatch.java | 2 +-
.../batch/functions/SideInputValues.java | 2 +-
.../translation/helpers/CoderHelpers.java | 59 ----------------------
.../translation/helpers/EncoderHelpers.java | 1 +
15 files changed, 64 insertions(+), 84 deletions(-)
diff --git
a/.github/trigger_files/beam_PostCommit_Java_ValidatesRunner_Spark4.json
b/.github/trigger_files/beam_PostCommit_Java_ValidatesRunner_Spark4.json
index c537844dc84..e0266d62f2e 100644
--- a/.github/trigger_files/beam_PostCommit_Java_ValidatesRunner_Spark4.json
+++ b/.github/trigger_files/beam_PostCommit_Java_ValidatesRunner_Spark4.json
@@ -1,4 +1,4 @@
{
"comment": "Modify this file in a trivial way to cause this test suite to
run",
- "modification": 3
+ "modification": 4
}
diff --git
a/.github/trigger_files/beam_PostCommit_Java_ValidatesRunner_SparkStructuredStreaming.json
b/.github/trigger_files/beam_PostCommit_Java_ValidatesRunner_SparkStructuredStreaming.json
index bd93fd4e988..c0d5a77072b 100644
---
a/.github/trigger_files/beam_PostCommit_Java_ValidatesRunner_SparkStructuredStreaming.json
+++
b/.github/trigger_files/beam_PostCommit_Java_ValidatesRunner_SparkStructuredStreaming.json
@@ -12,5 +12,5 @@
"https://github.com/apache/beam/pull/40103": "noting that PR #40103 should
run this test",
"https://github.com/apache/beam/issues/40427": "Spark ValidatesRunner tests
run serially",
"https://github.com/apache/beam/pull/40281": "noting that PR #40281 should
run this test",
- "https://github.com/apache/beam/issues/36841": "Spark 4 streaming watermark
lag"
+ "https://github.com/apache/beam/issues/36841": "Spark 4 streaming null keys"
}
diff --git
a/runners/spark/4/src/main/java/org/apache/beam/runners/spark/structuredstreaming/io/streaming/BeamReaderCache.java
b/runners/spark/4/src/main/java/org/apache/beam/runners/spark/structuredstreaming/io/streaming/BeamReaderCache.java
index 1eb31e766e7..e961ff2f380 100644
---
a/runners/spark/4/src/main/java/org/apache/beam/runners/spark/structuredstreaming/io/streaming/BeamReaderCache.java
+++
b/runners/spark/4/src/main/java/org/apache/beam/runners/spark/structuredstreaming/io/streaming/BeamReaderCache.java
@@ -27,7 +27,7 @@ import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.LongSupplier;
-import
org.apache.beam.runners.spark.structuredstreaming.translation.helpers.CoderHelpers;
+import org.apache.beam.runners.spark.coders.CoderHelpers;
import org.apache.beam.sdk.io.UnboundedSource;
import org.apache.beam.sdk.io.UnboundedSource.CheckpointMark;
import org.apache.beam.sdk.io.UnboundedSource.UnboundedReader;
diff --git
a/runners/spark/4/src/main/java/org/apache/beam/runners/spark/structuredstreaming/io/streaming/UnboundedSourceDataset.java
b/runners/spark/4/src/main/java/org/apache/beam/runners/spark/structuredstreaming/io/streaming/UnboundedSourceDataset.java
index 0368f95ba6d..33106824c10 100644
---
a/runners/spark/4/src/main/java/org/apache/beam/runners/spark/structuredstreaming/io/streaming/UnboundedSourceDataset.java
+++
b/runners/spark/4/src/main/java/org/apache/beam/runners/spark/structuredstreaming/io/streaming/UnboundedSourceDataset.java
@@ -29,9 +29,9 @@ import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicLong;
import org.apache.beam.runners.core.construction.SerializablePipelineOptions;
+import org.apache.beam.runners.spark.coders.CoderHelpers;
import
org.apache.beam.runners.spark.structuredstreaming.SparkStructuredStreamingPipelineOptions;
import
org.apache.beam.runners.spark.structuredstreaming.io.streaming.BeamReaderCache.CachedReader;
-import
org.apache.beam.runners.spark.structuredstreaming.translation.helpers.CoderHelpers;
import org.apache.beam.sdk.coders.Coder;
import org.apache.beam.sdk.io.UnboundedSource;
import org.apache.beam.sdk.io.UnboundedSource.CheckpointMark;
diff --git
a/runners/spark/4/src/main/java/org/apache/beam/runners/spark/structuredstreaming/translation/batch/GroupByKeyTranslatorBatch.java
b/runners/spark/4/src/main/java/org/apache/beam/runners/spark/structuredstreaming/translation/batch/GroupByKeyTranslatorBatch.java
index 7caf06cb38f..2b930c579d0 100644
---
a/runners/spark/4/src/main/java/org/apache/beam/runners/spark/structuredstreaming/translation/batch/GroupByKeyTranslatorBatch.java
+++
b/runners/spark/4/src/main/java/org/apache/beam/runners/spark/structuredstreaming/translation/batch/GroupByKeyTranslatorBatch.java
@@ -18,13 +18,13 @@
package org.apache.beam.runners.spark.structuredstreaming.translation.batch;
import static
org.apache.beam.repackaged.core.org.apache.commons.lang3.ArrayUtils.EMPTY_BYTE_ARRAY;
+import static org.apache.beam.runners.spark.coders.CoderHelpers.toByteArray;
import static
org.apache.beam.runners.spark.structuredstreaming.translation.batch.GroupByKeyHelpers.eligibleForGlobalGroupBy;
import static
org.apache.beam.runners.spark.structuredstreaming.translation.batch.GroupByKeyHelpers.eligibleForGroupByWindow;
import static
org.apache.beam.runners.spark.structuredstreaming.translation.batch.GroupByKeyHelpers.explodeWindowedKey;
import static
org.apache.beam.runners.spark.structuredstreaming.translation.batch.GroupByKeyHelpers.valueKey;
import static
org.apache.beam.runners.spark.structuredstreaming.translation.batch.GroupByKeyHelpers.valueValue;
import static
org.apache.beam.runners.spark.structuredstreaming.translation.batch.GroupByKeyHelpers.windowedKV;
-import static
org.apache.beam.runners.spark.structuredstreaming.translation.helpers.CoderHelpers.toByteArray;
import static
org.apache.beam.runners.spark.structuredstreaming.translation.helpers.EncoderHelpers.collectionEncoder;
import static
org.apache.beam.runners.spark.structuredstreaming.translation.helpers.EncoderHelpers.encoderOf;
import static
org.apache.beam.runners.spark.structuredstreaming.translation.helpers.EncoderHelpers.kvEncoder;
diff --git
a/runners/spark/4/src/main/java/org/apache/beam/runners/spark/structuredstreaming/translation/helpers/EncoderHelpers.java
b/runners/spark/4/src/main/java/org/apache/beam/runners/spark/structuredstreaming/translation/helpers/EncoderHelpers.java
index 173b4653a19..4cd92da0a15 100644
---
a/runners/spark/4/src/main/java/org/apache/beam/runners/spark/structuredstreaming/translation/helpers/EncoderHelpers.java
+++
b/runners/spark/4/src/main/java/org/apache/beam/runners/spark/structuredstreaming/translation/helpers/EncoderHelpers.java
@@ -36,6 +36,7 @@ import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.concurrent.ConcurrentHashMap;
+import org.apache.beam.runners.spark.coders.CoderHelpers;
import org.apache.beam.sdk.coders.Coder;
import org.apache.beam.sdk.transforms.windowing.BoundedWindow;
import org.apache.beam.sdk.transforms.windowing.GlobalWindow;
diff --git
a/runners/spark/4/src/main/java/org/apache/beam/runners/spark/structuredstreaming/translation/streaming/ReadUnboundedTranslator.java
b/runners/spark/4/src/main/java/org/apache/beam/runners/spark/structuredstreaming/translation/streaming/ReadUnboundedTranslator.java
index fb1691108b7..eb1421b4554 100644
---
a/runners/spark/4/src/main/java/org/apache/beam/runners/spark/structuredstreaming/translation/streaming/ReadUnboundedTranslator.java
+++
b/runners/spark/4/src/main/java/org/apache/beam/runners/spark/structuredstreaming/translation/streaming/ReadUnboundedTranslator.java
@@ -19,10 +19,10 @@ package
org.apache.beam.runners.spark.structuredstreaming.translation.streaming;
import static org.apache.spark.sql.functions.col;
+import org.apache.beam.runners.spark.coders.CoderHelpers;
import
org.apache.beam.runners.spark.structuredstreaming.SparkStructuredStreamingPipelineOptions;
import
org.apache.beam.runners.spark.structuredstreaming.io.streaming.UnboundedSourceDataset;
import
org.apache.beam.runners.spark.structuredstreaming.translation.TransformTranslator;
-import
org.apache.beam.runners.spark.structuredstreaming.translation.helpers.CoderHelpers;
import org.apache.beam.sdk.coders.Coder;
import org.apache.beam.sdk.io.UnboundedSource;
import org.apache.beam.sdk.transforms.windowing.GlobalWindow;
diff --git
a/runners/spark/4/src/main/java/org/apache/beam/runners/spark/structuredstreaming/translation/streaming/StatefulParDoStreamingTranslator.java
b/runners/spark/4/src/main/java/org/apache/beam/runners/spark/structuredstreaming/translation/streaming/StatefulParDoStreamingTranslator.java
index 1709ec57003..199570ae64a 100644
---
a/runners/spark/4/src/main/java/org/apache/beam/runners/spark/structuredstreaming/translation/streaming/StatefulParDoStreamingTranslator.java
+++
b/runners/spark/4/src/main/java/org/apache/beam/runners/spark/structuredstreaming/translation/streaming/StatefulParDoStreamingTranslator.java
@@ -17,6 +17,7 @@
*/
package
org.apache.beam.runners.spark.structuredstreaming.translation.streaming;
+import org.apache.beam.runners.spark.coders.CoderHelpers;
import
org.apache.beam.runners.spark.structuredstreaming.metrics.MetricsAccumulator;
import
org.apache.beam.runners.spark.structuredstreaming.translation.TransformTranslator;
import
org.apache.beam.runners.spark.structuredstreaming.translation.batch.DoFnRunnerFactory;
@@ -35,7 +36,7 @@ import org.apache.beam.sdk.values.WindowedValue;
import org.apache.beam.sdk.values.WindowingStrategy;
import org.apache.spark.api.java.function.MapFunction;
import org.apache.spark.sql.Dataset;
-import org.apache.spark.sql.Encoder;
+import org.apache.spark.sql.Encoders;
import org.apache.spark.sql.streaming.OutputMode;
import org.apache.spark.sql.streaming.TimeMode;
@@ -57,7 +58,7 @@ public class StatefulParDoStreamingTranslator<K, V, OutputT>
DoFn<KV<K, V>, OutputT> doFn = transform.getFn();
KvCoder<K, V> inputCoder = (KvCoder<K, V>) input.getCoder();
- Encoder<K> keyEnc = cxt.keyEncoderOf(inputCoder);
+ Coder<K> keyCoder = inputCoder.getKeyCoder();
TupleTag<OutputT> mainOutputTag = transform.getMainOutputTag();
PCollection<OutputT> output = cxt.getOutput(mainOutputTag);
Coder<OutputT> outputCoder = output.getCoder();
@@ -71,10 +72,11 @@ public class StatefulParDoStreamingTranslator<K, V, OutputT>
new BeamStatefulProcessor<>(
doFn, runnerFactory, inputCoder, windowing,
cxt.getOptionsSupplier(), metrics);
- MapFunction<WindowedValue<KV<K, V>>, K> keyFn = v -> v.getValue().getKey();
+ MapFunction<WindowedValue<KV<K, V>>, byte[]> keyFn =
+ v -> CoderHelpers.toByteArray(v.getValue().getKey(), keyCoder);
Dataset<WindowedValue<OutputT>> result =
cxt.getDataset(input)
- .groupByKey(keyFn, keyEnc)
+ .groupByKey(keyFn, Encoders.BINARY())
.transformWithState(
processor,
TimeMode.EventTime(),
diff --git
a/runners/spark/4/src/main/java/org/apache/beam/runners/spark/structuredstreaming/translation/streaming/state/BeamStatefulProcessor.java
b/runners/spark/4/src/main/java/org/apache/beam/runners/spark/structuredstreaming/translation/streaming/state/BeamStatefulProcessor.java
index e359ed6bd58..2649f883af3 100644
---
a/runners/spark/4/src/main/java/org/apache/beam/runners/spark/structuredstreaming/translation/streaming/state/BeamStatefulProcessor.java
+++
b/runners/spark/4/src/main/java/org/apache/beam/runners/spark/structuredstreaming/translation/streaming/state/BeamStatefulProcessor.java
@@ -41,6 +41,7 @@ import
org.apache.beam.runners.spark.structuredstreaming.translation.batch.DoFnR
import
org.apache.beam.runners.spark.structuredstreaming.translation.batch.StatefulTaskRunner;
import
org.apache.beam.runners.spark.structuredstreaming.translation.utils.ScalaInterop;
import org.apache.beam.sdk.coders.Coder;
+import org.apache.beam.sdk.coders.KvCoder;
import org.apache.beam.sdk.coders.ListCoder;
import org.apache.beam.sdk.options.PipelineOptions;
import org.apache.beam.sdk.state.TimeDomain;
@@ -68,7 +69,7 @@ import scala.collection.Iterator;
/** Runs a stateful {@link DoFn} within Spark 4 Structured Streaming {@code
transformWithState}. */
public class BeamStatefulProcessor<K, V, OutputT>
- extends StatefulProcessor<K, WindowedValue<KV<K, V>>,
WindowedValue<OutputT>> {
+ extends StatefulProcessor<byte[], WindowedValue<KV<K, V>>,
WindowedValue<OutputT>> {
private static final String BEAM_STATE = "beamState";
private static final String BEAM_TIMERS = "beamTimers";
@@ -76,7 +77,7 @@ public class BeamStatefulProcessor<K, V, OutputT>
private final DoFn<KV<K, V>, OutputT> doFn;
private final DoFnRunnerFactory<KV<K, V>, OutputT> runnerFactory;
- private final Coder<KV<K, V>> inputCoder;
+ private final KvCoder<K, V> inputCoder;
private final WindowingStrategy<?, ?> windowingStrategy;
private final Supplier<PipelineOptions> optionsSupplier;
private final MetricsAccumulator metrics;
@@ -93,7 +94,7 @@ public class BeamStatefulProcessor<K, V, OutputT>
public BeamStatefulProcessor(
DoFn<KV<K, V>, OutputT> doFn,
DoFnRunnerFactory<KV<K, V>, OutputT> runnerFactory,
- Coder<KV<K, V>> inputCoder,
+ KvCoder<K, V> inputCoder,
WindowingStrategy<?, ?> windowingStrategy,
Supplier<PipelineOptions> optionsSupplier,
MetricsAccumulator metrics) {
@@ -138,7 +139,8 @@ public class BeamStatefulProcessor<K, V, OutputT>
@Override
public Iterator<WindowedValue<OutputT>> handleInputRows(
- K key, Iterator<WindowedValue<KV<K, V>>> rows, TimerValues timerValues) {
+ byte[] keyBytes, Iterator<WindowedValue<KV<K, V>>> rows, TimerValues
timerValues) {
+ K key = CoderHelpers.fromByteArray(keyBytes, inputCoder.getKeyCoder());
List<WindowedValue<OutputT>> outputs = new ArrayList<>();
SparkTimerInternals timerInternals = timerInternals(timerValues);
@@ -159,7 +161,8 @@ public class BeamStatefulProcessor<K, V, OutputT>
@Override
public Iterator<WindowedValue<OutputT>> handleExpiredTimer(
- K key, TimerValues timerValues, ExpiredTimerInfo expiredTimerInfo) {
+ byte[] keyBytes, TimerValues timerValues, ExpiredTimerInfo
expiredTimerInfo) {
+ K key = CoderHelpers.fromByteArray(keyBytes, inputCoder.getKeyCoder());
List<WindowedValue<OutputT>> outputs = new ArrayList<>();
SparkTimerInternals timerInternals = timerInternals(timerValues);
timerInternals.advanceWatermark();
diff --git
a/runners/spark/4/src/test/java/org/apache/beam/runners/spark/structuredstreaming/io/streaming/BeamMicroBatchSourceTest.java
b/runners/spark/4/src/test/java/org/apache/beam/runners/spark/structuredstreaming/io/streaming/BeamMicroBatchSourceTest.java
index 3b2e36bf5ef..559deb80803 100644
---
a/runners/spark/4/src/test/java/org/apache/beam/runners/spark/structuredstreaming/io/streaming/BeamMicroBatchSourceTest.java
+++
b/runners/spark/4/src/test/java/org/apache/beam/runners/spark/structuredstreaming/io/streaming/BeamMicroBatchSourceTest.java
@@ -46,6 +46,7 @@ import java.util.function.BooleanSupplier;
import javax.annotation.Nullable;
import org.apache.beam.runners.core.construction.SerializablePipelineOptions;
import org.apache.beam.runners.spark.StreamingTest;
+import org.apache.beam.runners.spark.coders.CoderHelpers;
import org.apache.beam.runners.spark.structuredstreaming.SparkSessionRule;
import
org.apache.beam.runners.spark.structuredstreaming.SparkStructuredStreamingPipelineOptions;
import
org.apache.beam.runners.spark.structuredstreaming.io.streaming.UnboundedSourceDataset.BeamInputPartition;
@@ -60,7 +61,6 @@ import org.apache.beam.sdk.io.CountingSource;
import org.apache.beam.sdk.io.UnboundedSource;
import org.apache.beam.sdk.options.PipelineOptionsFactory;
import org.apache.beam.sdk.transforms.windowing.GlobalWindow;
-import org.apache.beam.sdk.util.CoderUtils;
import org.apache.beam.sdk.values.WindowedValue;
import org.apache.beam.sdk.values.WindowedValues;
import org.apache.hadoop.conf.Configuration;
@@ -209,7 +209,7 @@ public class BeamMicroBatchSourceTest implements
Serializable {
List<String> values = new ArrayList<>();
for (List<Row> batch : batches(tag)) {
for (Row row : batch) {
- WindowedValue<String> value = CoderUtils.decodeFromByteArray(CODER,
row.getAs(COL_PAYLOAD));
+ WindowedValue<String> value =
CoderHelpers.fromByteArray(row.getAs(COL_PAYLOAD), CODER);
values.add(value.getValue());
assertEquals(
value.getTimestamp().getMillis(),
row.<Timestamp>getAs(COL_EVENT_TS).getTime());
@@ -593,11 +593,7 @@ public class BeamMicroBatchSourceTest implements
Serializable {
}
private static String decode(byte[] payload) {
- try {
- return CoderUtils.decodeFromByteArray(CODER, payload).getValue();
- } catch (IOException e) {
- throw new IllegalStateException(e);
- }
+ return CoderHelpers.fromByteArray(payload, CODER).getValue();
}
private static List<Integer> nonEmptySizes(List<List<Row>> batches) {
@@ -748,7 +744,7 @@ public class BeamMicroBatchSourceTest implements
Serializable {
new BeamSourceCheckpoint(sourceDir(checkpointDir).getAbsolutePath(),
new Configuration());
byte[] coded = checkpoint.readMark(shard, epoch);
assertNotNull("no mark at committed epoch " + epoch + " for shard " +
shard, coded);
- return CoderUtils.decodeFromByteArray(TestUnboundedSource.MARK_CODER,
coded).next;
+ return CoderHelpers.fromByteArray(coded,
TestUnboundedSource.MARK_CODER).next;
}
//
---------------------------------------------------------------------------------------------
diff --git
a/runners/spark/4/src/test/java/org/apache/beam/runners/spark/structuredstreaming/translation/streaming/StatefulParDoStreamingTest.java
b/runners/spark/4/src/test/java/org/apache/beam/runners/spark/structuredstreaming/translation/streaming/StatefulParDoStreamingTest.java
index 2ff0bfc2bf0..2d3c127d72b 100644
---
a/runners/spark/4/src/test/java/org/apache/beam/runners/spark/structuredstreaming/translation/streaming/StatefulParDoStreamingTest.java
+++
b/runners/spark/4/src/test/java/org/apache/beam/runners/spark/structuredstreaming/translation/streaming/StatefulParDoStreamingTest.java
@@ -33,6 +33,7 @@ import org.apache.beam.sdk.Pipeline;
import org.apache.beam.sdk.PipelineResult;
import org.apache.beam.sdk.coders.KvCoder;
import org.apache.beam.sdk.coders.StringUtf8Coder;
+import org.apache.beam.sdk.coders.VoidCoder;
import org.apache.beam.sdk.io.Read;
import org.apache.beam.sdk.state.StateSpec;
import org.apache.beam.sdk.state.StateSpecs;
@@ -43,6 +44,7 @@ import org.apache.beam.sdk.state.TimerSpecs;
import org.apache.beam.sdk.state.ValueState;
import org.apache.beam.sdk.transforms.DoFn;
import org.apache.beam.sdk.transforms.ParDo;
+import org.apache.beam.sdk.transforms.WithKeys;
import org.apache.beam.sdk.transforms.windowing.FixedWindows;
import org.apache.beam.sdk.transforms.windowing.Window;
import org.apache.beam.sdk.values.KV;
@@ -69,12 +71,14 @@ public class StatefulParDoStreamingTest implements
Serializable {
private static final String TAG_BATCHES = "stateful-batches";
private static final String TAG_RESTART = "stateful-restart";
private static final String TAG_CHAINED = "stateful-chained";
+ private static final String TAG_NULL_KEY = "stateful-null-key";
@After
public void tearDown() {
TestUnboundedSource.forget(TAG_BATCHES);
TestUnboundedSource.forget(TAG_RESTART);
TestUnboundedSource.forget(TAG_CHAINED);
+ TestUnboundedSource.forget(TAG_NULL_KEY);
}
private static Pipeline pipeline(
@@ -167,6 +171,25 @@ public class StatefulParDoStreamingTest implements
Serializable {
assertTrue(collected.contains("timer-output"));
}
+ @Test
+ public void testNullKey() throws Exception {
+ String collectorId = StreamingTestUtils.newCollectorId(TAG_NULL_KEY);
+ SparkStructuredStreamingPipelineOptions options =
+ StreamingTestUtils.streamingOptions(tempFolder);
+ options.setMaxRecordsPerBatch(1L);
+ Pipeline pipeline = Pipeline.create(options);
+ pipeline
+ .apply("Read", Read.from(new TestUnboundedSource(TAG_NULL_KEY, 1, 5)))
+ .apply("WithKeys", WithKeys.of((Void) null))
+ .setCoder(KvCoder.of(VoidCoder.of(), StringUtf8Coder.of()))
+ .apply("StatefulParDo", ParDo.of(new NullKeyCountFn()))
+ .apply("Collect", ParDo.of(new
StreamingTestUtils.CollectDoFn<>(collectorId)));
+
+ assertEquals(PipelineResult.State.DONE,
StreamingTestUtils.run(pipeline).getState());
+ Set<Integer> collected = StreamingTestUtils.collected(collectorId);
+ assertTrue(collected.contains(5));
+ }
+
private static final class KeySelectorFn extends DoFn<String, KV<String,
String>> {
@ProcessElement
public void process(@Element String element, OutputReceiver<KV<String,
String>> out) {
@@ -301,4 +324,17 @@ public class StatefulParDoStreamingTest implements
Serializable {
out.output(element.getValue());
}
}
+
+ private static final class NullKeyCountFn extends DoFn<KV<Void, String>,
Integer> {
+ @StateId("count")
+ private final StateSpec<ValueState<Integer>> countSpec =
StateSpecs.value();
+
+ @ProcessElement
+ public void process(
+ @StateId("count") ValueState<Integer> countState,
OutputReceiver<Integer> out) {
+ int count = (countState.read() == null ? 0 : countState.read()) + 1;
+ countState.write(count);
+ out.output(count);
+ }
+ }
}
diff --git
a/runners/spark/src/main/java/org/apache/beam/runners/spark/structuredstreaming/translation/batch/GroupByKeyTranslatorBatch.java
b/runners/spark/src/main/java/org/apache/beam/runners/spark/structuredstreaming/translation/batch/GroupByKeyTranslatorBatch.java
index c55781ff84a..c67ea3ad3e5 100644
---
a/runners/spark/src/main/java/org/apache/beam/runners/spark/structuredstreaming/translation/batch/GroupByKeyTranslatorBatch.java
+++
b/runners/spark/src/main/java/org/apache/beam/runners/spark/structuredstreaming/translation/batch/GroupByKeyTranslatorBatch.java
@@ -18,13 +18,13 @@
package org.apache.beam.runners.spark.structuredstreaming.translation.batch;
import static
org.apache.beam.repackaged.core.org.apache.commons.lang3.ArrayUtils.EMPTY_BYTE_ARRAY;
+import static org.apache.beam.runners.spark.coders.CoderHelpers.toByteArray;
import static
org.apache.beam.runners.spark.structuredstreaming.translation.batch.GroupByKeyHelpers.eligibleForGlobalGroupBy;
import static
org.apache.beam.runners.spark.structuredstreaming.translation.batch.GroupByKeyHelpers.eligibleForGroupByWindow;
import static
org.apache.beam.runners.spark.structuredstreaming.translation.batch.GroupByKeyHelpers.explodeWindowedKey;
import static
org.apache.beam.runners.spark.structuredstreaming.translation.batch.GroupByKeyHelpers.valueKey;
import static
org.apache.beam.runners.spark.structuredstreaming.translation.batch.GroupByKeyHelpers.valueValue;
import static
org.apache.beam.runners.spark.structuredstreaming.translation.batch.GroupByKeyHelpers.windowedKV;
-import static
org.apache.beam.runners.spark.structuredstreaming.translation.helpers.CoderHelpers.toByteArray;
import static
org.apache.beam.runners.spark.structuredstreaming.translation.helpers.EncoderHelpers.collectionEncoder;
import static
org.apache.beam.runners.spark.structuredstreaming.translation.helpers.EncoderHelpers.encoderOf;
import static
org.apache.beam.runners.spark.structuredstreaming.translation.helpers.EncoderHelpers.kvEncoder;
diff --git
a/runners/spark/src/main/java/org/apache/beam/runners/spark/structuredstreaming/translation/batch/functions/SideInputValues.java
b/runners/spark/src/main/java/org/apache/beam/runners/spark/structuredstreaming/translation/batch/functions/SideInputValues.java
index 23c8d49c309..0017f95b2a0 100644
---
a/runners/spark/src/main/java/org/apache/beam/runners/spark/structuredstreaming/translation/batch/functions/SideInputValues.java
+++
b/runners/spark/src/main/java/org/apache/beam/runners/spark/structuredstreaming/translation/batch/functions/SideInputValues.java
@@ -34,8 +34,8 @@ import java.util.List;
import java.util.Map;
import java.util.function.Function;
import javax.annotation.Nullable;
+import org.apache.beam.runners.spark.coders.CoderHelpers;
import
org.apache.beam.runners.spark.structuredstreaming.translation.EvaluationContext;
-import
org.apache.beam.runners.spark.structuredstreaming.translation.helpers.CoderHelpers;
import org.apache.beam.sdk.annotations.Internal;
import org.apache.beam.sdk.coders.Coder;
import org.apache.beam.sdk.transforms.windowing.BoundedWindow;
diff --git
a/runners/spark/src/main/java/org/apache/beam/runners/spark/structuredstreaming/translation/helpers/CoderHelpers.java
b/runners/spark/src/main/java/org/apache/beam/runners/spark/structuredstreaming/translation/helpers/CoderHelpers.java
deleted file mode 100644
index f8c63bc34f1..00000000000
---
a/runners/spark/src/main/java/org/apache/beam/runners/spark/structuredstreaming/translation/helpers/CoderHelpers.java
+++ /dev/null
@@ -1,59 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.beam.runners.spark.structuredstreaming.translation.helpers;
-
-import java.io.IOException;
-import org.apache.beam.sdk.coders.Coder;
-import org.apache.beam.sdk.util.CoderUtils;
-
-/** Serialization utility class. */
-public final class CoderHelpers {
- private CoderHelpers() {}
-
- /**
- * Utility method for serializing an object using the specified coder.
- *
- * @param value Value to serialize.
- * @param coder Coder to serialize with.
- * @param <T> type of value that is serialized
- * @return Byte array representing serialized object.
- */
- public static <T> byte[] toByteArray(T value, Coder<T> coder) {
- try {
- return CoderUtils.encodeToByteArray(coder, value);
- } catch (IOException e) {
- throw new IllegalStateException("Error encoding value: " + value, e);
- }
- }
-
- /**
- * Utility method for deserializing a byte array using the specified coder.
- *
- * @param serialized bytearray to be deserialized.
- * @param coder Coder to deserialize with.
- * @param <T> Type of object to be returned.
- * @return Deserialized object.
- */
- public static <T> T fromByteArray(byte[] serialized, Coder<T> coder) {
- try {
- return CoderUtils.decodeFromByteArray(coder, serialized);
- } catch (IOException e) {
- throw new IllegalStateException("Error decoding bytes for coder: " +
coder, e);
- }
- }
-}
diff --git
a/runners/spark/src/main/java/org/apache/beam/runners/spark/structuredstreaming/translation/helpers/EncoderHelpers.java
b/runners/spark/src/main/java/org/apache/beam/runners/spark/structuredstreaming/translation/helpers/EncoderHelpers.java
index 29f01c84c02..08ce47c67bb 100644
---
a/runners/spark/src/main/java/org/apache/beam/runners/spark/structuredstreaming/translation/helpers/EncoderHelpers.java
+++
b/runners/spark/src/main/java/org/apache/beam/runners/spark/structuredstreaming/translation/helpers/EncoderHelpers.java
@@ -36,6 +36,7 @@ import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.concurrent.ConcurrentHashMap;
+import org.apache.beam.runners.spark.coders.CoderHelpers;
import org.apache.beam.sdk.coders.Coder;
import org.apache.beam.sdk.transforms.windowing.BoundedWindow;
import org.apache.beam.sdk.transforms.windowing.GlobalWindow;