Skip to content

Commit 9791bcb

Browse files
committed
Merge branch 'master' into handle-grpc-exception
2 parents c6ee013 + c33bc97 commit 9791bcb

23 files changed

Lines changed: 751 additions & 218 deletions

File tree

.github/workflows/beam_PostCommit_Java_ValidatesRunner_Dataflow_JavaVersions.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ jobs:
6060
matrix:
6161
job_name: [beam_PostCommit_Java_ValidatesRunner_Dataflow_JavaVersions]
6262
job_phrase: [Run Dataflow ValidatesRunner Java]
63-
java_version: ['8', '25']
63+
java_version: ['25']
6464
if: |
6565
github.event_name == 'workflow_dispatch' ||
6666
github.event_name == 'pull_request_target' ||

.github/workflows/load-tests-pipeline-options/beam_Inference_Python_Benchmarks_Dataflow_Pytorch_Sentiment_Batch_DistilBert_Base_Uncased.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
--machine_type=n1-standard-2
1919
--num_workers=20
2020
--max_num_workers=250
21+
--timeout_ms=600000
2122
--disk_size_gb=50
2223
--autoscaling_algorithm=THROUGHPUT_BASED
2324
--staging_location=gs://temp-storage-for-perf-tests/loadtests
@@ -31,5 +32,7 @@
3132
--device=CPU
3233
--input_file=gs://apache-beam-ml/testing/inputs/sentences_50k.txt
3334
--runner=DataflowRunner
35+
--sdk_location=container
36+
--sdk_container_image=us.gcr.io/apache-beam-testing/python-postcommit-it/tensor_rt:latest
3437
--model_path=distilbert-base-uncased-finetuned-sst-2-english
3538
--model_state_dict_path=gs://apache-beam-ml/models/huggingface.sentiment.distilbert-base-uncased.pth

.github/workflows/load-tests-pipeline-options/beam_Inference_Python_Benchmarks_Dataflow_Pytorch_Sentiment_Streaming_DistilBert_Base_Uncased.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
--machine_type=n1-standard-2
1919
--num_workers=20
2020
--max_num_workers=250
21+
--timeout_ms=600000
2122
--disk_size_gb=50
2223
--autoscaling_algorithm=THROUGHPUT_BASED
2324
--staging_location=gs://temp-storage-for-perf-tests/loadtests
@@ -31,6 +32,8 @@
3132
--device=CPU
3233
--input_file=gs://apache-beam-ml/testing/inputs/sentences_50k.txt
3334
--runner=DataflowRunner
35+
--sdk_location=container
36+
--sdk_container_image=us.gcr.io/apache-beam-testing/python-postcommit-it/tensor_rt:latest
3437
--dataflow_service_options=worker_accelerator=type:nvidia-tesla-t4;count:1;install-nvidia-driver
3538
--model_path=distilbert-base-uncased-finetuned-sst-2-english
3639
--model_state_dict_path=gs://apache-beam-ml/models/huggingface.sentiment.distilbert-base-uncased.pth

buildSrc/src/main/groovy/org/apache/beam/gradle/BeamModulePlugin.groovy

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -743,7 +743,7 @@ class BeamModulePlugin implements Plugin<Project> {
743743
google_api_common : "com.google.api:api-common", // google_cloud_platform_libraries_bom sets version
744744
google_api_services_bigquery : "com.google.apis:google-api-services-bigquery:v2-rev20251012-2.0.0", // [bomupgrader] sets version
745745
google_api_services_cloudresourcemanager : "com.google.apis:google-api-services-cloudresourcemanager:v1-rev20250606-2.0.0", // [bomupgrader] sets version
746-
google_api_services_dataflow : "com.google.apis:google-api-services-dataflow:v1b3-rev20260405-$google_clients_version",
746+
google_api_services_dataflow : "com.google.apis:google-api-services-dataflow:v1b3-rev20260503-$google_clients_version",
747747
google_api_services_healthcare : "com.google.apis:google-api-services-healthcare:v1-rev20240130-$google_clients_version",
748748
google_api_services_pubsub : "com.google.apis:google-api-services-pubsub:v1-rev20220904-$google_clients_version",
749749
google_api_services_storage : "com.google.apis:google-api-services-storage:v1-rev20260204-2.0.0", // [bomupgrader] sets version

runners/core-java/src/main/java/org/apache/beam/runners/core/ReduceFnRunner.java

Lines changed: 46 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
import org.apache.beam.runners.core.triggers.TriggerStateMachineRunner;
3838
import org.apache.beam.sdk.metrics.Counter;
3939
import org.apache.beam.sdk.metrics.Metrics;
40+
import org.apache.beam.sdk.options.ExperimentalOptions;
4041
import org.apache.beam.sdk.options.PipelineOptions;
4142
import org.apache.beam.sdk.state.CombiningState;
4243
import org.apache.beam.sdk.state.TimeDomain;
@@ -94,6 +95,11 @@
9495
}) // TODO(https://github.com/apache/beam/issues/20497)
9596
public class ReduceFnRunner<K, InputT, OutputT, W extends BoundedWindow> {
9697

98+
// Experiments guarding optimizations in development. No backward compatibility guarantees.
99+
public static final String UNSTABLE_NOT_UPDATE_COMPATIBLE_NEW_WINDOW_OPTIMIZATION =
100+
"unstable_not_update_compatible_new_window_optimization";
101+
public static final String UNSTABLE_DISABLE_WATERMARK_KNOWN_EMPTY_OPTIMIZATION =
102+
"unstable_disable_watermark_known_empty_optimization";
97103
/**
98104
* The {@link ReduceFnRunner} depends on most aspects of the {@link WindowingStrategy}.
99105
*
@@ -218,6 +224,9 @@ public class ReduceFnRunner<K, InputT, OutputT, W extends BoundedWindow> {
218224
*/
219225
private final NonEmptyPanes<K, W> nonEmptyPanes;
220226

227+
private final boolean useNewWindowOptimization;
228+
private final boolean disableWatermarkKnownEmptyOptimization;
229+
221230
public ReduceFnRunner(
222231
K key,
223232
WindowingStrategy<?, W> windowingStrategy,
@@ -244,6 +253,15 @@ public ReduceFnRunner(
244253

245254
this.nonEmptyPanes = NonEmptyPanes.create(this.windowingStrategy, this.reduceFn);
246255

256+
this.useNewWindowOptimization =
257+
windowingStrategy.getWindowFn().isNonMerging()
258+
&& ExperimentalOptions.hasExperiment(
259+
options, UNSTABLE_NOT_UPDATE_COMPATIBLE_NEW_WINDOW_OPTIMIZATION);
260+
261+
this.disableWatermarkKnownEmptyOptimization =
262+
ExperimentalOptions.hasExperiment(
263+
options, UNSTABLE_DISABLE_WATERMARK_KNOWN_EMPTY_OPTIMIZATION);
264+
247265
// Note this may incur I/O to load persisted window set data.
248266
this.activeWindows = createActiveWindowSet();
249267

@@ -263,7 +281,8 @@ public ReduceFnRunner(
263281
new TriggerStateMachineRunner<>(
264282
triggerStateMachine,
265283
new TriggerStateMachineContextFactory<>(
266-
windowingStrategy.getWindowFn(), stateInternals, activeWindows));
284+
windowingStrategy.getWindowFn(), stateInternals, activeWindows),
285+
this.useNewWindowOptimization);
267286
}
268287

269288
private ActiveWindowSet<W> createActiveWindowSet() {
@@ -282,6 +301,16 @@ boolean hasNoActiveWindows() {
282301
return activeWindows.getActiveAndNewWindows().isEmpty();
283302
}
284303

304+
@VisibleForTesting
305+
TriggerStateMachineRunner<W> getTriggerRunner() {
306+
return triggerRunner;
307+
}
308+
309+
@VisibleForTesting
310+
ReduceFnContextFactory<K, InputT, OutputT, W> getContextFactory() {
311+
return contextFactory;
312+
}
313+
285314
private Set<W> windowsThatAreOpen(Collection<W> windows) {
286315
Set<W> result = new HashSet<>();
287316
for (W window : windows) {
@@ -626,6 +655,20 @@ private void processElement(Map<W, W> windowToMergeResult, WindowedValue<InputT>
626655
StateStyle.RENAMED,
627656
value.causedByDrain());
628657

658+
// TODO: Make sure the NewWindowOptimization does not create unbounded trigger state
659+
// in GlobalWindow
660+
if (useNewWindowOptimization && triggerRunner.isNew(directContext.state())) {
661+
// Blindly clear state to ensure Windmill doesn't do unnecessary reads.
662+
// TODO: Instead of the clears here, we could mark these states as empty locally
663+
// in the state cache and/or explicitly tell that the entries are non-existent via api
664+
reduceFn.clearState(renamedContext);
665+
paneInfoTracker.clear(directContext.state());
666+
if (!disableWatermarkKnownEmptyOptimization) {
667+
watermarkHold.setKnownEmpty(renamedContext);
668+
}
669+
nonEmptyPanes.clearPane(renamedContext.state());
670+
}
671+
629672
nonEmptyPanes.recordContent(renamedContext.state());
630673
scheduleGarbageCollectionTimer(directContext);
631674

@@ -761,7 +804,7 @@ public void onTimers(Iterable<TimerData> timers) throws Exception {
761804

762805
// Perform prefetching of state to determine if the trigger should fire.
763806
if (windowActivation.isGarbageCollection) {
764-
triggerRunner.prefetchIsClosed(directContext.state());
807+
triggerRunner.prefetchFinishedSet(directContext.state());
765808
} else {
766809
triggerRunner.prefetchShouldFire(directContext.window(), directContext.state());
767810
}
@@ -941,7 +984,7 @@ private void prefetchEmit(
941984
ReduceFn<K, InputT, OutputT, W>.Context directContext,
942985
ReduceFn<K, InputT, OutputT, W>.Context renamedContext) {
943986
triggerRunner.prefetchShouldFire(directContext.window(), directContext.state());
944-
triggerRunner.prefetchIsClosed(directContext.state());
987+
triggerRunner.prefetchFinishedSet(directContext.state());
945988
prefetchOnTrigger(directContext, renamedContext);
946989
}
947990

runners/core-java/src/main/java/org/apache/beam/runners/core/WatermarkHold.java

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -466,6 +466,23 @@ public void clearHolds(ReduceFn<?, ?, ?, W>.Context context) {
466466
context.state().access(EXTRA_HOLD_TAG).clear();
467467
}
468468

469+
/**
470+
* <b><i>For internal use only; no backwards-compatibility guarantees.</i></b>
471+
*
472+
* <p>Permit marking the watermark holds as empty locally, without necessarily clearing them in
473+
* the backend.
474+
*/
475+
public void setKnownEmpty(ReduceFn<?, ?, ?, W>.Context context) {
476+
WindowTracing.debug(
477+
"WatermarkHold.setKnownEmpty: For key:{}; window:{}; inputWatermark:{}; outputWatermark:{}",
478+
context.key(),
479+
context.window(),
480+
timerInternals.currentInputWatermarkTime(),
481+
timerInternals.currentOutputWatermarkTime());
482+
context.state().access(elementHoldTag).setKnownEmpty();
483+
context.state().access(EXTRA_HOLD_TAG).setKnownEmpty();
484+
}
485+
469486
/** Return the current data hold, or null if none. Does not clear. For debugging only. */
470487
public @Nullable Instant getDataCurrent(ReduceFn<?, ?, ?, W>.Context context) {
471488
return context.state().access(elementHoldTag).read();

runners/core-java/src/main/java/org/apache/beam/runners/core/triggers/FinishedTriggersBitSet.java

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
package org.apache.beam.runners.core.triggers;
1919

2020
import java.util.BitSet;
21+
import org.checkerframework.checker.nullness.qual.Nullable;
2122

2223
/** A {@link FinishedTriggers} implementation based on an underlying {@link BitSet}. */
2324
public class FinishedTriggersBitSet implements FinishedTriggers {
@@ -60,4 +61,17 @@ public void clearRecursively(ExecutableTriggerStateMachine trigger) {
6061
public FinishedTriggersBitSet copy() {
6162
return new FinishedTriggersBitSet((BitSet) bitSet.clone());
6263
}
64+
65+
@Override
66+
public boolean equals(@Nullable Object obj) {
67+
if (!(obj instanceof FinishedTriggersBitSet)) {
68+
return false;
69+
}
70+
return bitSet.equals(((FinishedTriggersBitSet) obj).bitSet);
71+
}
72+
73+
@Override
74+
public int hashCode() {
75+
return bitSet.hashCode();
76+
}
6377
}

runners/core-java/src/main/java/org/apache/beam/runners/core/triggers/TriggerStateMachineRunner.java

Lines changed: 34 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -63,13 +63,16 @@ public class TriggerStateMachineRunner<W extends BoundedWindow> {
6363

6464
private final ExecutableTriggerStateMachine rootTrigger;
6565
private final TriggerStateMachineContextFactory<W> contextFactory;
66+
private final boolean useNewWindowOptimization;
6667

6768
public TriggerStateMachineRunner(
6869
ExecutableTriggerStateMachine rootTrigger,
69-
TriggerStateMachineContextFactory<W> contextFactory) {
70+
TriggerStateMachineContextFactory<W> contextFactory,
71+
boolean useNewWindowOptimization) {
7072
checkState(rootTrigger.getTriggerIndex() == 0);
7173
this.rootTrigger = rootTrigger;
7274
this.contextFactory = contextFactory;
75+
this.useNewWindowOptimization = useNewWindowOptimization;
7376
}
7477

7578
private FinishedTriggersBitSet readFinishedBits(ValueState<BitSet> state) {
@@ -81,9 +84,11 @@ private FinishedTriggersBitSet readFinishedBits(ValueState<BitSet> state) {
8184
}
8285

8386
@Nullable BitSet bitSet = state.read();
84-
return bitSet == null
85-
? FinishedTriggersBitSet.emptyWithCapacity(rootTrigger.getFirstIndexAfterSubtree())
86-
: FinishedTriggersBitSet.fromBitSet(bitSet);
87+
if (bitSet == null) {
88+
return FinishedTriggersBitSet.emptyWithCapacity(rootTrigger.getFirstIndexAfterSubtree());
89+
}
90+
91+
return FinishedTriggersBitSet.fromBitSet(bitSet);
8792
}
8893

8994
private void clearFinishedBits(ValueState<BitSet> state) {
@@ -99,19 +104,29 @@ public boolean isClosed(StateAccessor<?> state) {
99104
return readFinishedBits(state.access(FINISHED_BITS_TAG)).isFinished(rootTrigger);
100105
}
101106

102-
public void prefetchIsClosed(StateAccessor<?> state) {
107+
/** Return true if the window is new (no trigger state has ever been persisted). */
108+
public boolean isNew(StateAccessor<?> state) {
109+
return isFinishedSetNeeded() && state.access(FINISHED_BITS_TAG).read() == null;
110+
}
111+
112+
@VisibleForTesting
113+
public BitSet getFinishedBits(StateAccessor<?> state) {
114+
return readFinishedBits(state.access(FINISHED_BITS_TAG)).getBitSet();
115+
}
116+
117+
public void prefetchFinishedSet(StateAccessor<?> state) {
103118
if (isFinishedSetNeeded()) {
104119
state.access(FINISHED_BITS_TAG).readLater();
105120
}
106121
}
107122

108123
public void prefetchForValue(W window, StateAccessor<?> state) {
109-
prefetchIsClosed(state);
124+
prefetchFinishedSet(state);
110125
rootTrigger.invokePrefetchOnElement(contextFactory.createPrefetchContext(window, rootTrigger));
111126
}
112127

113128
public void prefetchShouldFire(W window, StateAccessor<?> state) {
114-
prefetchIsClosed(state);
129+
prefetchFinishedSet(state);
115130
rootTrigger.invokePrefetchShouldFire(contextFactory.createPrefetchContext(window, rootTrigger));
116131
}
117132

@@ -180,13 +195,23 @@ public void onFire(W window, Timers timers, StateAccessor<?> state) throws Excep
180195
persistFinishedSet(state, finishedSet);
181196
}
182197

183-
private void persistFinishedSet(
184-
StateAccessor<?> state, FinishedTriggersBitSet modifiedFinishedSet) {
198+
@VisibleForTesting
199+
void persistFinishedSet(StateAccessor<?> state, FinishedTriggersBitSet modifiedFinishedSet) {
185200
if (!isFinishedSetNeeded()) {
186201
return;
187202
}
188203

189204
ValueState<BitSet> finishedSetState = state.access(FINISHED_BITS_TAG);
205+
206+
if (useNewWindowOptimization) {
207+
if (finishedSetState.read() == null
208+
|| !readFinishedBits(finishedSetState).equals(modifiedFinishedSet)) {
209+
// Write a value even if the bitset was empty
210+
finishedSetState.write(modifiedFinishedSet.getBitSet());
211+
}
212+
return;
213+
}
214+
190215
if (!readFinishedBits(finishedSetState).equals(modifiedFinishedSet)) {
191216
if (modifiedFinishedSet.getBitSet().isEmpty()) {
192217
finishedSetState.clear();

runners/core-java/src/test/java/org/apache/beam/runners/core/ReduceFnRunnerTest.java

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,15 +40,19 @@
4040

4141
import java.util.ArrayList;
4242
import java.util.Arrays;
43+
import java.util.BitSet;
44+
import java.util.Collections;
4345
import java.util.List;
4446
import java.util.Random;
4547
import java.util.concurrent.ThreadLocalRandom;
48+
import org.apache.beam.runners.core.ReduceFnContextFactory.StateStyle;
4649
import org.apache.beam.runners.core.metrics.MetricsContainerImpl;
4750
import org.apache.beam.runners.core.triggers.DefaultTriggerStateMachine;
4851
import org.apache.beam.runners.core.triggers.TriggerStateMachine;
4952
import org.apache.beam.sdk.coders.VarIntCoder;
5053
import org.apache.beam.sdk.metrics.MetricName;
5154
import org.apache.beam.sdk.metrics.MetricsEnvironment;
55+
import org.apache.beam.sdk.options.ExperimentalOptions;
5256
import org.apache.beam.sdk.options.PipelineOptions;
5357
import org.apache.beam.sdk.options.PipelineOptionsFactory;
5458
import org.apache.beam.sdk.state.TimeDomain;
@@ -2343,4 +2347,58 @@ public interface TestOptions extends PipelineOptions {
23432347

23442348
void setValue(int value);
23452349
}
2350+
2351+
@Test
2352+
public void testNewWindowOptimization() throws Exception {
2353+
WindowingStrategy<?, IntervalWindow> strategy =
2354+
WindowingStrategy.of(FixedWindows.of(Duration.millis(10)))
2355+
.withTrigger(AfterPane.elementCountAtLeast(2))
2356+
.withMode(AccumulationMode.ACCUMULATING_FIRED_PANES);
2357+
2358+
PipelineOptions options = PipelineOptionsFactory.create();
2359+
options
2360+
.as(ExperimentalOptions.class)
2361+
.setExperiments(
2362+
Collections.singletonList("unstable_not_update_compatible_new_window_optimization"));
2363+
ReduceFnTester<Integer, Iterable<Integer>, IntervalWindow> tester =
2364+
ReduceFnTester.nonCombining(strategy, options);
2365+
2366+
IntervalWindow window = new IntervalWindow(new Instant(0), new Instant(10));
2367+
2368+
assertTrue(
2369+
"Window should be new",
2370+
tester
2371+
.createRunner()
2372+
.getTriggerRunner()
2373+
.isNew(
2374+
tester.createRunner().getContextFactory().base(window, StateStyle.DIRECT).state()));
2375+
2376+
// 1. First element for a new window.
2377+
tester.injectElements(TimestampedValue.of(1, new Instant(1)));
2378+
2379+
BitSet bitSet =
2380+
tester
2381+
.createRunner()
2382+
.getTriggerRunner()
2383+
.getFinishedBits(
2384+
tester.createRunner().getContextFactory().base(window, StateStyle.DIRECT).state());
2385+
assertTrue("Bitset should be empty", bitSet.isEmpty());
2386+
assertFalse("Trigger should not be finished", bitSet.get(0));
2387+
2388+
assertFalse(
2389+
"Window should no longer be new",
2390+
tester
2391+
.createRunner()
2392+
.getTriggerRunner()
2393+
.isNew(
2394+
tester.createRunner().getContextFactory().base(window, StateStyle.DIRECT).state()));
2395+
2396+
// 2. Second element for the same window.
2397+
tester.injectElements(TimestampedValue.of(2, new Instant(2)));
2398+
2399+
// Extract output.
2400+
List<WindowedValue<Iterable<Integer>>> output = tester.extractOutput();
2401+
// 2 elements fired at end of window.
2402+
assertThat(output, contains(isSingleWindowedValue(containsInAnyOrder(1, 2), 9, 0, 10)));
2403+
}
23462404
}

0 commit comments

Comments
 (0)