Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
5154679
[Dataflow Streaming][Multikey] Support MultiKey commits in windmill c…
arunpandianp Jun 2, 2026
73faa68
[Dataflow Streaming] [Multi Key] StreamingModeExecutionContext refact…
arunpandianp Jun 4, 2026
53bc9a6
trigger postsubmit tests
arunpandianp Jun 4, 2026
7720cf8
fix tests
arunpandianp Jun 4, 2026
72740d2
fix tests
arunpandianp Jun 4, 2026
0f96e72
improve work synchronization
arunpandianp Jun 4, 2026
51b9257
cleanup logic
arunpandianp Jun 5, 2026
9a4e7be
cleanup logic
arunpandianp Jun 5, 2026
3f36afd
address comments
arunpandianp Jun 8, 2026
f3cc628
improve WindowingWindmillReader
arunpandianp Jun 8, 2026
58e0ef9
spotless fix
arunpandianp Jun 8, 2026
3dceab0
[Dataflow Streaming] Fix nullness supression in StreamingModeExecutio…
arunpandianp Jun 8, 2026
e199438
make windmillTagEncoding final
arunpandianp Jun 8, 2026
700dfbc
address comments
arunpandianp Jun 8, 2026
bc5bee2
Move SideInputStateFetcherFactory from start to constructor
arunpandianp Jun 8, 2026
6d7f28e
Merge branch 'contextnullness' into multikey_context_review
arunpandianp Jun 8, 2026
c4a52c1
Merge remote-tracking branch 'beam/master' into multikey_context_review
arunpandianp Jun 8, 2026
1107a60
Merge beam/master into multikey_context_review
arunpandianp Jun 8, 2026
47eb7d6
Address comment
arunpandianp Jun 8, 2026
24505dd
Address comment
arunpandianp Jun 8, 2026
4e0d174
Fix UnderInitialization
arunpandianp Jun 9, 2026
5f9fef5
Merge branch 'multikey_commit' into tmp
arunpandianp Jun 11, 2026
0e8157f
Merge branch 'multikey_context_review' into tmp
arunpandianp Jun 11, 2026
93de23a
Multikey commit failure handling and integration
arunpandianp Jun 11, 2026
114780e
Retry on commit size validation
arunpandianp Jun 11, 2026
9de3d84
Fix tests and add sink byte limit for batching
arunpandianp Jun 11, 2026
0df9f54
spotless
arunpandianp Jun 11, 2026
18ae844
Merge remote-tracking branch 'beam/master' into multikey_commit
arunpandianp Jun 29, 2026
ad177c0
Merge remote-tracking branch 'beam/master' into multikey_failure
arunpandianp Jun 30, 2026
557f95e
Merge branch 'multikey_commit' into multikey_failure
arunpandianp Jun 30, 2026
00e5fbf
fix merge
arunpandianp Jul 1, 2026
9ff2cad
Address comments
arunpandianp Jul 23, 2026
6c5481a
Merge branch 'multikey_commit' into multikey_failure
arunpandianp Jul 23, 2026
5ddc9d4
Merge remote-tracking branch 'beam/master' into multikey_failure
arunpandianp Jul 23, 2026
ab33927
fix merge
arunpandianp Jul 23, 2026
ef77b1f
fix merge
arunpandianp Jul 23, 2026
43c7f1a
Address comments
arunpandianp Jul 23, 2026
0f79170
Merge branch 'multikey_failure' into multikey_commit_validation
arunpandianp Jul 23, 2026
c38a831
fix merge
arunpandianp Jul 24, 2026
ca8f8d4
Merge remote-tracking branch 'beam/master' into multikey_commit_valid…
arunpandianp Jul 29, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@
import org.apache.beam.runners.dataflow.worker.windmill.state.WindmillTagEncodingV1;
import org.apache.beam.runners.dataflow.worker.windmill.state.WindmillTagEncodingV2;
import org.apache.beam.runners.dataflow.worker.windmill.state.WindmillTimerData;
import org.apache.beam.runners.dataflow.worker.windmill.work.processing.StreamingWorkScheduler.MultiKeyCommitValidationException;
import org.apache.beam.runners.dataflow.worker.windmill.work.processing.failures.FailureTracker;
import org.apache.beam.sdk.annotations.Internal;
import org.apache.beam.sdk.coders.Coder;
Expand Down Expand Up @@ -713,6 +714,17 @@ private void validateCommitRequestSize() {
return;
}

if (executedWorks.size() > 1) {
LOG.warn(
"Windmill Commit limit exceeded on a multi key bundle. Retrying without batching. Batch size: {}",
executedWorks.size());
for (Work w : executedWorks) {
w.setDisableMultiKeyBatching(true);
}
throw new MultiKeyCommitValidationException(
"Commit size validation failed for batch. Retrying individually.");
}

KeyCommitTooLargeException e =
KeyCommitTooLargeException.causedBy(
systemName, byteLimit, commitRequest, key, hotKeyLoggingEnabled);
Expand Down Expand Up @@ -769,7 +781,9 @@ public boolean advance() throws CoderException {
throw new WorkItemCancelledException(activeWork.getWorkItem().getShardingKey());
}

if (activeWork.getKeyGroup().equals(Work.KeyGroup.DEFAULT) || shouldStopBatching()) {
if (activeWork.getKeyGroup().equals(Work.KeyGroup.DEFAULT)
|| activeWork.isMultiKeyBatchingDisabled()
|| shouldStopBatching()) {
return false;
}

Expand All @@ -789,7 +803,10 @@ public boolean advance() throws CoderException {
}

private boolean shouldStopBatching() {
// TODO: stop batching if the previous work item requested truncation
// stop batching if the previous work item requested truncation
if (getOutputBuilder().getExceedsMaxWorkItemCommitBytes()) {
return true;
}
if (workItemsPolled >= multiKeyBundleOptions.maxKeyGroupBatchSize()) {
return true;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,23 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.beam.runners.dataflow.worker;
package org.apache.beam.runners.dataflow.worker; /*
* 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.
*/

import org.checkerframework.checker.nullness.qual.Nullable;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,4 +82,8 @@ public String getComputationId() {
public Work.KeyGroup getKeyGroup() {
return work().getKeyGroup();
}

public boolean isMultiKeyBatchingDisabled() {
return work().isMultiKeyBatchingDisabled();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,10 @@ public final class Work implements RefreshableWork {
private final long serializedWorkItemSize;
private volatile TimedState currentState;
private volatile boolean isFailed;
// If true, this work item will not be batched with other work items in a multi-key bundle.
// This is used to isolate work items that failed validation (e.g. commit size limit exceeded)
// so they can be retried individually and potentially truncated.
private volatile boolean disableMultiKeyBatching = false;
private volatile String processingThreadName = "";
private final AtomicReference<@Nullable AtomicBoolean> onFailureListener =
new AtomicReference<>(null);
Expand Down Expand Up @@ -399,6 +403,14 @@ public boolean isFailed() {
return isFailed;
}

public void setDisableMultiKeyBatching(boolean disableMultiKeyBatching) {
this.disableMultiKeyBatching = disableMultiKeyBatching;
}

public boolean isMultiKeyBatchingDisabled() {
return disableMultiKeyBatching;
}

boolean isStuckCommittingAt(Instant stuckCommitDeadline) {
return currentState.state() == Work.State.COMMITTING
&& currentState.startTime().isBefore(stuckCommitDeadline);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import static org.apache.beam.sdk.util.Preconditions.checkArgumentNotNull;
import static org.apache.beam.sdk.util.Preconditions.checkStateNotNull;
import static org.apache.beam.vendor.guava.v32_1_2_jre.com.google.common.base.Preconditions.checkArgument;
import static org.apache.beam.vendor.guava.v32_1_2_jre.com.google.common.base.Preconditions.checkState;

import java.util.AbstractQueue;
import java.util.Collection;
Expand Down Expand Up @@ -67,9 +68,14 @@ static class Node {
@Nullable Node prevKeyGroupNode;
@Nullable Node nextKeyGroupNode;

private static boolean isMultiKeyBatchingDisabled(Runnable task) {
return (task instanceof QueuedWork)
&& ((QueuedWork) task).getWork().isMultiKeyBatchingDisabled();
}

Node(Runnable task) {
this.task = task;
if (task instanceof QueuedWork) {
if (task instanceof QueuedWork && !isMultiKeyBatchingDisabled(task)) {
this.computationId = ((QueuedWork) task).getWork().getComputationId();
this.keyGroup = ((QueuedWork) task).getWork().getKeyGroup();
} else {
Expand Down Expand Up @@ -193,6 +199,10 @@ private void unlinkNode(Node node) {
if (firstNode == keyGroupWorkList.tail) {
return null;
}

// MultiKeyBatchingDisabled items should not be in keyGroupWorkList
checkState(!Node.isMultiKeyBatchingDisabled(firstNode.task));

unlinkNode(firstNode);

return (QueuedWork) firstNode.task;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -417,10 +417,6 @@ private void commitMultiKeyWorkBatch(
}
for (int i = 0; i < workBatch.size(); i++) {
Windmill.WorkItemCommitRequest commit = workItemCommits.get(i);
// TODO: Retry on commit truncations
checkState(
!commit.getExceedsMaxWorkItemCommitBytes(),
"Commit truncation with multikey bundles not implemented");
Work w = workBatch.get(i);
multiKeyBuilder.addRequests(
commit
Expand All @@ -429,6 +425,8 @@ private void commitMultiKeyWorkBatch(
.build());
}

Windmill.MultiKeyWorkItemCommitRequest multiKeyCommitRequest = multiKeyBuilder.build();

// Transition states of all completed works in the batch to COMMIT_QUEUED and submit
for (Work w : workBatch) {
w.setState(Work.State.COMMIT_QUEUED);
Expand All @@ -439,7 +437,7 @@ private void commitMultiKeyWorkBatch(
.workCommitter()
.accept(
Commit.createMultiKey(
multiKeyBuilder.build(), computationState, ImmutableList.copyOf(workBatch)));
multiKeyCommitRequest, computationState, ImmutableList.copyOf(workBatch)));
}

private void commitSingleKeyWork(
Expand Down Expand Up @@ -534,4 +532,10 @@ static ExecuteWorkResult create(

abstract long stateBytesRead();
}

public static class MultiKeyCommitValidationException extends RuntimeException {
public MultiKeyCommitValidationException(String message) {
super(message);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
import org.apache.beam.runners.dataflow.worker.streaming.ExecutableWork;
import org.apache.beam.runners.dataflow.worker.streaming.Work;
import org.apache.beam.runners.dataflow.worker.util.BoundedQueueExecutor;
import org.apache.beam.runners.dataflow.worker.windmill.work.processing.StreamingWorkScheduler.MultiKeyCommitValidationException;
import org.apache.beam.sdk.annotations.Internal;
import org.apache.beam.sdk.util.UserCodeException;
import org.apache.beam.vendor.guava.v32_1_2_jre.com.google.common.annotations.VisibleForTesting;
Expand Down Expand Up @@ -160,6 +161,17 @@
@Nullable final Throwable cause = t.getCause();
Throwable parsedException = (t instanceof UserCodeException && cause != null) ? cause : t;

if (parsedException instanceof MultiKeyCommitValidationException) {
LOG.info(
"Execution of work for computation '{}' on sharding key '{}' failed batch validation. "
+ "Work will be retried locally.",
computationId,
work.getWorkItem().getShardingKey());
return RetryEvaluation.RETRY_LOCALLY;
}
@Nullable final Throwable cause = t.getCause();

Check failure on line 172 in runners/google-cloud-dataflow-java/worker/src/main/java/org/apache/beam/runners/dataflow/worker/windmill/work/processing/failures/WorkFailureProcessor.java

View workflow job for this annotation

GitHub Actions / beam_PreCommit_Java_Examples_Dataflow (Run Java_Examples_Dataflow PreCommit)

variable cause is already defined in method evaluateRetry(String,Work,Throwable)
Throwable parsedException = (t instanceof UserCodeException && cause != null) ? cause : t;

Check failure on line 173 in runners/google-cloud-dataflow-java/worker/src/main/java/org/apache/beam/runners/dataflow/worker/windmill/work/processing/failures/WorkFailureProcessor.java

View workflow job for this annotation

GitHub Actions / beam_PreCommit_Java_Examples_Dataflow (Run Java_Examples_Dataflow PreCommit)

variable parsedException is already defined in method evaluateRetry(String,Work,Throwable)

LastExceptionDataProvider.reportException(parsedException);
LOG.debug("Failed work: {}", work);
Duration elapsedTimeSinceStart = new Duration(work.getStartTime(), clock.get());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,7 @@
import org.apache.beam.runners.dataflow.worker.windmill.client.grpc.stubs.WindmillChannels;
import org.apache.beam.runners.dataflow.worker.windmill.testing.FakeWindmillStubFactory;
import org.apache.beam.runners.dataflow.worker.windmill.testing.FakeWindmillStubFactoryFactory;
import org.apache.beam.runners.dataflow.worker.windmill.work.processing.StreamingWorkScheduler;
import org.apache.beam.runners.dataflow.worker.windmill.work.refresh.HeartbeatSender;
import org.apache.beam.sdk.coders.Coder;
import org.apache.beam.sdk.coders.Coder.Context;
Expand All @@ -156,6 +157,7 @@
import org.apache.beam.sdk.state.StateSpec;
import org.apache.beam.sdk.state.StateSpecs;
import org.apache.beam.sdk.state.ValueState;
import org.apache.beam.sdk.testing.ExpectedLogs;
import org.apache.beam.sdk.transforms.DoFn;
import org.apache.beam.sdk.transforms.DoFnSchemaInformation;
import org.apache.beam.sdk.transforms.windowing.AfterPane;
Expand Down Expand Up @@ -303,6 +305,10 @@ public Long get() {
};

@Rule public transient Timeout globalTimeout = Timeout.seconds(600);

@Rule
public ExpectedLogs expectedWorkSchedulerLogs = ExpectedLogs.none(StreamingWorkScheduler.class);

@Rule public BlockingFn blockingFn = new BlockingFn();
@Rule public TestRule restoreMDC = new RestoreDataflowLoggingMDC();
@Rule public final GrpcCleanupRule grpcCleanup = new GrpcCleanupRule();
Expand Down Expand Up @@ -4853,6 +4859,23 @@ public void processElement(ProcessContext c) {
}
}

static class FixedSizeCommitFn extends DoFn<KV<String, String>, KV<String, String>> {
private final int size;

FixedSizeCommitFn(int size) {
this.size = size;
}

@ProcessElement
public void processElement(ProcessContext c) {
StringBuilder s = new StringBuilder();
for (int i = 0; i < size; ++i) {
s.append("a");
}
c.output(KV.of(c.element().getKey(), s.toString()));
}
}

static class ExceptionCatchingFn extends DoFn<KV<String, String>, KV<String, String>> {

@ProcessElement
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@
import org.apache.beam.runners.dataflow.worker.windmill.state.WindmillStateCache;
import org.apache.beam.runners.dataflow.worker.windmill.state.WindmillTagEncodingV1;
import org.apache.beam.runners.dataflow.worker.windmill.state.WindmillTagEncodingV2;
import org.apache.beam.runners.dataflow.worker.windmill.work.processing.failures.FailureTracker;
import org.apache.beam.runners.dataflow.worker.windmill.work.processing.failures.StreamingEngineFailureTracker;
import org.apache.beam.runners.dataflow.worker.windmill.work.refresh.HeartbeatSender;
import org.apache.beam.sdk.Pipeline;
import org.apache.beam.sdk.coders.Coder;
Expand Down Expand Up @@ -152,7 +152,7 @@ private StreamingModeExecutionContext createExecutionContext(
/*stepName=*/ "stepName",
/*systemName=*/ "systemName",
StreamingCounters.create(),
mock(FailureTracker.class),
StreamingEngineFailureTracker.create(10, 10),
"sourceBytesProcessCounterName",
MultiKeyBundleOptions.fromOptions(options),
SideInputStateFetcherFactory.fromOptions(options));
Expand Down Expand Up @@ -693,6 +693,32 @@ public void testAdvance_defaultKeyGroup() throws Exception {
verifyNoInteractions(mockExecutor);
}

@Test
public void testAdvance_batchingDisabled() throws Exception {
BoundedQueueExecutor mockExecutor = mock(BoundedQueueExecutor.class);
BoundedQueueExecutorWorkHandle mockHandle = mock(BoundedQueueExecutorWorkHandle.class);

Windmill.Uint128Proto keyGroup =
Windmill.Uint128Proto.newBuilder().setHigh(1).setLow(2).build();
Windmill.WorkItem workItem1 =
Windmill.WorkItem.newBuilder()
.setKey(ByteString.copyFromUtf8("key1"))
.setWorkToken(1L)
.setKeyGroup(keyGroup)
.build();
Work work1 =
createMockWork(
workItem1, Watermarks.builder().setInputDataWatermark(Instant.EPOCH).build());

work1.setDisableMultiKeyBatching(true);

executionContext.start(
work1, workExecutor, mockExecutor, mockHandle, null, (oldWork, newWork) -> {});

assertFalse(executionContext.advance());
verifyNoInteractions(mockExecutor);
}

@Test
public void testAdvance_experimentDisabled() throws Exception {
DataflowWorkerHarnessOptions optionsDisabled =
Expand Down Expand Up @@ -831,4 +857,79 @@ public void testInternalsPoisonedAfterFlushState() throws Exception {
assertThat(e.getMessage(), Matchers.containsString("poisoned"));
}
}

@Test
public void testAdvance_stopsBatchingWhenCommitTruncated() throws Exception {
DataflowWorkerHarnessOptions optionsMultiKey =
PipelineOptionsFactory.as(DataflowWorkerHarnessOptions.class);
optionsMultiKey
.as(ExperimentalOptions.class)
.setExperiments(Arrays.asList("unstable_enable_multi_key_bundle"));
StreamingModeExecutionContext context =
createExecutionContext(optionsMultiKey, globalConfigHandle);

BoundedQueueExecutor mockExecutor = mock(BoundedQueueExecutor.class);
BoundedQueueExecutorWorkHandle mockHandle = mock(BoundedQueueExecutorWorkHandle.class);

Windmill.Uint128Proto keyGroup =
Windmill.Uint128Proto.newBuilder().setHigh(1).setLow(2).build();
Work work1 =
createMockWork(
Windmill.WorkItem.newBuilder()
.setKey(ByteString.copyFromUtf8("key1"))
.setWorkToken(1L)
.setKeyGroup(keyGroup)
.build(),
Watermarks.builder().setInputDataWatermark(Instant.EPOCH).build());

context.start(work1, workExecutor, mockExecutor, mockHandle, null, (oldWork, newWork) -> {});

context.getOutputBuilder().setExceedsMaxWorkItemCommitBytes(true);

assertFalse(context.advance());
org.mockito.Mockito.verifyNoInteractions(mockExecutor);
}

@Test
public void testAdvance_stopsWhenQueuedWorkBatchingDisabled() throws Exception {
DataflowWorkerHarnessOptions optionsMultiKey =
PipelineOptionsFactory.as(DataflowWorkerHarnessOptions.class);
optionsMultiKey
.as(ExperimentalOptions.class)
.setExperiments(Arrays.asList("unstable_enable_multi_key_bundle"));
StreamingModeExecutionContext context =
createExecutionContext(optionsMultiKey, globalConfigHandle);

BoundedQueueExecutor mockExecutor = mock(BoundedQueueExecutor.class);
BoundedQueueExecutorWorkHandle mockHandle = mock(BoundedQueueExecutorWorkHandle.class);
Windmill.Uint128Proto keyGroup =
Windmill.Uint128Proto.newBuilder().setHigh(1).setLow(2).build();

Work work1 =
createMockWork(
Windmill.WorkItem.newBuilder()
.setKey(ByteString.copyFromUtf8("key1"))
.setWorkToken(1L)
.setKeyGroup(keyGroup)
.build(),
Watermarks.builder().setInputDataWatermark(Instant.EPOCH).build());
assertFalse(work1.isMultiKeyBatchingDisabled());

org.mockito.Mockito.when(mockExecutor.pollWork(COMPUTATION_ID, work1.getKeyGroup(), mockHandle))
.thenReturn(null);

AtomicBoolean transitionListenerCalled = new AtomicBoolean(false);
context.start(
work1,
workExecutor,
mockExecutor,
mockHandle,
null,
(oldWork, newWork) -> transitionListenerCalled.set(true));

assertFalse(context.advance());
assertFalse(transitionListenerCalled.get());
org.mockito.Mockito.verify(mockExecutor)
.pollWork(COMPUTATION_ID, work1.getKeyGroup(), mockHandle);
}
}
Loading
Loading