Skip to content

Commit e7472da

Browse files
committed
side-input improvements
1 parent 3e5a085 commit e7472da

15 files changed

Lines changed: 1734 additions & 69 deletions

File tree

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

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -870,8 +870,17 @@ public InputT element(DoFn<InputT, OutputT> doFn) {
870870
}
871871

872872
@Override
873-
public Object sideInput(String tagId) {
874-
throw new UnsupportedOperationException("SideInput parameters are not supported.");
873+
public @Nullable Object sideInput(String tagId) {
874+
PCollectionView<?> view =
875+
checkStateNotNull(sideInputMapping.get(tagId), "Side input tag %s not found", tagId);
876+
return sideInput(view);
877+
}
878+
879+
@Override
880+
public <T> T sideInput(PCollectionView<T> view) {
881+
checkNotNull(view, "View passed to sideInput cannot be null");
882+
return SimpleDoFnRunner.this.sideInput(
883+
view, view.getWindowMappingFn().getSideInputWindow(window()));
875884
}
876885

877886
@Override
@@ -1196,8 +1205,17 @@ public InputT element(DoFn<InputT, OutputT> doFn) {
11961205
}
11971206

11981207
@Override
1199-
public Object sideInput(String tagId) {
1200-
throw new UnsupportedOperationException("SideInput parameters are not supported.");
1208+
public @Nullable Object sideInput(String tagId) {
1209+
PCollectionView<?> view =
1210+
checkStateNotNull(sideInputMapping.get(tagId), "Side input tag %s not found", tagId);
1211+
return sideInput(view);
1212+
}
1213+
1214+
@Override
1215+
public <T> T sideInput(PCollectionView<T> view) {
1216+
checkNotNull(view, "View passed to sideInput cannot be null");
1217+
return SimpleDoFnRunner.this.sideInput(
1218+
view, view.getWindowMappingFn().getSideInputWindow(window()));
12011219
}
12021220

12031221
@Override

runners/google-cloud-dataflow-java/worker/src/main/java/org/apache/beam/runners/dataflow/worker/SimpleDoFnRunnerFactory.java

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@
2424
import org.apache.beam.runners.core.SideInputReader;
2525
import org.apache.beam.sdk.coders.Coder;
2626
import org.apache.beam.sdk.options.PipelineOptions;
27-
import org.apache.beam.sdk.options.StreamingOptions;
2827
import org.apache.beam.sdk.transforms.DoFn;
2928
import org.apache.beam.sdk.transforms.DoFnSchemaInformation;
3029
import org.apache.beam.sdk.util.WindowedValueMultiReceiver;
@@ -68,17 +67,6 @@ public DoFnRunner<InputT, OutputT> createRunner(
6867
windowingStrategy,
6968
doFnSchemaInformation,
7069
sideInputMapping);
71-
boolean hasStreamingSideInput =
72-
options.as(StreamingOptions.class).isStreaming() && !sideInputReader.isEmpty();
73-
if (hasStreamingSideInput) {
74-
return new StreamingSideInputDoFnRunner<>(
75-
fnRunner,
76-
new StreamingSideInputFetcher<>(
77-
sideInputViews,
78-
inputCoder,
79-
windowingStrategy,
80-
(StreamingModeExecutionContext.StreamingModeStepContext) userStepContext));
81-
}
8270
return fnRunner;
8371
}
8472
}

runners/google-cloud-dataflow-java/worker/src/main/java/org/apache/beam/runners/dataflow/worker/SimpleParDoFn.java

Lines changed: 69 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,9 @@
2222

2323
import java.io.Closeable;
2424
import java.util.Collection;
25+
import java.util.Collections;
2526
import java.util.HashMap;
27+
import java.util.Iterator;
2628
import java.util.List;
2729
import java.util.Map;
2830
import org.apache.beam.runners.core.DoFnRunner;
@@ -59,6 +61,7 @@
5961
import org.apache.beam.sdk.values.WindowingStrategy;
6062
import org.apache.beam.vendor.guava.v32_1_2_jre.com.google.common.annotations.VisibleForTesting;
6163
import org.apache.beam.vendor.guava.v32_1_2_jre.com.google.common.collect.ImmutableList;
64+
import org.apache.beam.vendor.guava.v32_1_2_jre.com.google.common.collect.Lists;
6265
import org.checkerframework.checker.nullness.qual.Nullable;
6366
import org.joda.time.Duration;
6467
import org.joda.time.Instant;
@@ -76,7 +79,7 @@
7679
"rawtypes", // TODO(https://github.com/apache/beam/issues/20447)
7780
"nullness" // TODO(https://github.com/apache/beam/issues/20497)
7881
})
79-
public class SimpleParDoFn<InputT, OutputT> implements ParDoFn {
82+
public class SimpleParDoFn<InputT, OutputT, W extends BoundedWindow> implements ParDoFn {
8083

8184
// TODO: Remove once Distributions has shipped.
8285
@VisibleForTesting
@@ -112,6 +115,8 @@ public class SimpleParDoFn<InputT, OutputT> implements ParDoFn {
112115
// GroupAlsoByWindowViaWindowSetDoFn
113116
private @Nullable DoFnSignature fnSignature;
114117

118+
private @Nullable StreamingSideInputProcessor<InputT, W> sideInputProcessor;
119+
115120
/** Creates a {@link SimpleParDoFn} using basic information about the step being executed. */
116121
SimpleParDoFn(
117122
PipelineOptions options,
@@ -317,8 +322,32 @@ public <TagT> void output(TupleTag<TagT> tag, WindowedValue<TagT> output) {
317322
outputManager,
318323
doFnSchemaInformation,
319324
sideInputMapping);
325+
if (hasStreamingSideInput) {
326+
sideInputProcessor =
327+
new StreamingSideInputProcessor<>(
328+
new StreamingSideInputFetcher<InputT, W>(
329+
fnInfo.getSideInputViews(),
330+
fnInfo.getInputCoder(),
331+
(WindowingStrategy<?, W>) fnInfo.getWindowingStrategy(),
332+
(StreamingModeExecutionContext.StreamingModeStepContext) userStepContext));
333+
}
320334

321335
fnRunner.startBundle();
336+
if (sideInputProcessor != null) {
337+
boolean hasState = fnSignature != null && !fnSignature.stateDeclarations().isEmpty();
338+
Iterator<WindowedValue<InputT>> unblockedElements = sideInputProcessor.tryUnblockElements();
339+
for (Iterator<WindowedValue<InputT>> it = unblockedElements; it.hasNext(); ) {
340+
WindowedValue<InputT> unblockedElement = it.next();
341+
fnRunner.processElement(unblockedElement);
342+
if (hasState) {
343+
// These elements are now processed. Register cleanup timers for all the unblocked
344+
// windows.
345+
registerStateCleanup(
346+
(WindowingStrategy<?, W>) getDoFnInfo().getWindowingStrategy(),
347+
(Collection<W>) unblockedElement.getWindows());
348+
}
349+
}
350+
}
322351
}
323352

324353
@Override
@@ -334,14 +363,32 @@ public void processElement(Object untypedElem) throws Exception {
334363

335364
WindowedValue<InputT> elem = (WindowedValue<InputT>) untypedElem;
336365

337-
if (fnSignature != null && fnSignature.stateDeclarations().size() > 0) {
366+
boolean hasState = fnSignature != null && !fnSignature.stateDeclarations().isEmpty();
367+
outputsPerElementTracker.onProcessElement();
368+
369+
Collection<W> windowsProcessed;
370+
if (sideInputProcessor != null) {
371+
windowsProcessed = hasState ? Lists.newArrayList() : Collections.emptyList();
372+
for (Iterator<? extends WindowedValue<InputT>> it =
373+
sideInputProcessor.handleProcessElement(elem);
374+
it.hasNext(); ) {
375+
WindowedValue<InputT> toProcess = it.next();
376+
fnRunner.processElement(toProcess);
377+
if (hasState) {
378+
windowsProcessed.addAll((Collection<W>) toProcess.getWindows());
379+
// If the element was blocked, don't register a cleanup timer. The timer will be
380+
// registered
381+
// when the window is unblocked ensuring that it is not processed until the element is.
382+
}
383+
}
384+
} else {
385+
fnRunner.processElement(elem);
386+
windowsProcessed = (Collection<W>) elem.getWindows();
387+
}
388+
if (hasState) {
338389
registerStateCleanup(
339-
(WindowingStrategy<?, BoundedWindow>) getDoFnInfo().getWindowingStrategy(),
340-
(Collection<BoundedWindow>) elem.getWindows());
390+
(WindowingStrategy<?, W>) getDoFnInfo().getWindowingStrategy(), windowsProcessed);
341391
}
342-
343-
outputsPerElementTracker.onProcessElement();
344-
fnRunner.processElement(elem);
345392
outputsPerElementTracker.onProcessElementSuccess();
346393
}
347394

@@ -367,6 +414,9 @@ private void processUserTimer(TimerData timer) throws Exception {
367414
if (fnSignature.timerDeclarations().containsKey(timer.getTimerId())
368415
|| fnSignature.timerFamilyDeclarations().containsKey(timer.getTimerFamilyId())) {
369416
BoundedWindow window = ((WindowNamespace) timer.getNamespace()).getWindow();
417+
if (sideInputProcessor != null) {
418+
sideInputProcessor.handleProcessTimer(timer);
419+
}
370420
fnRunner.onTimer(
371421
timer.getTimerId(),
372422
timer.getTimerFamilyId(),
@@ -380,7 +430,6 @@ private void processUserTimer(TimerData timer) throws Exception {
380430
}
381431

382432
private void processSystemTimer(TimerData timer) throws Exception {
383-
384433
// Timer owned by this class, for cleaning up state in expired windows
385434
if (timer.getTimerId().equals(CLEANUP_TIMER_ID)) {
386435
checkState(
@@ -396,6 +445,13 @@ private void processSystemTimer(TimerData timer) throws Exception {
396445
WindowNamespace.class.getSimpleName(),
397446
timer);
398447

448+
if (sideInputProcessor != null) {
449+
// We must call this to ensure the side-input is cached for onWindowExpiration. Since we
450+
// don't set cleanup
451+
// timers until we actually call processElement, the window must be unblocked here.
452+
sideInputProcessor.handleProcessTimer(timer);
453+
}
454+
399455
BoundedWindow window = ((WindowNamespace) timer.getNamespace()).getWindow();
400456
Instant targetTime = earliestAllowableCleanupTime(window, fnInfo.getWindowingStrategy());
401457

@@ -436,10 +492,14 @@ private void processSystemTimer(TimerData timer) throws Exception {
436492
public void finishBundle() throws Exception {
437493
if (fnRunner != null) {
438494
fnRunner.finishBundle();
495+
if (sideInputProcessor != null) {
496+
sideInputProcessor.handleFinishBundle();
497+
}
439498
doFnInstanceManager.complete(fnInfo);
440499
fnRunner = null;
441500
fnInfo = null;
442501
fnSignature = null;
502+
sideInputProcessor = null;
443503
}
444504
}
445505

@@ -490,7 +550,7 @@ private void processTimers(
490550
}
491551
}
492552

493-
private <W extends BoundedWindow> void registerStateCleanup(
553+
private void registerStateCleanup(
494554
WindowingStrategy<?, W> windowingStrategy, Collection<W> windowsToCleanup) {
495555
Coder<W> windowCoder = windowingStrategy.getWindowFn().windowCoder();
496556

runners/google-cloud-dataflow-java/worker/src/main/java/org/apache/beam/runners/dataflow/worker/StreamingSideInputDoFnRunner.java

Lines changed: 13 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,8 @@
1717
*/
1818
package org.apache.beam.runners.dataflow.worker;
1919

20-
import java.util.Set;
20+
import java.util.Iterator;
2121
import org.apache.beam.runners.core.DoFnRunner;
22-
import org.apache.beam.sdk.state.BagState;
2322
import org.apache.beam.sdk.state.TimeDomain;
2423
import org.apache.beam.sdk.transforms.DoFn;
2524
import org.apache.beam.sdk.transforms.windowing.BoundedWindow;
@@ -37,43 +36,32 @@
3736
public class StreamingSideInputDoFnRunner<InputT, OutputT, W extends BoundedWindow>
3837
implements DoFnRunner<InputT, OutputT> {
3938
private final DoFnRunner<InputT, OutputT> simpleDoFnRunner;
40-
private final StreamingSideInputFetcher<InputT, W> sideInputFetcher;
39+
private final StreamingSideInputProcessor<InputT, W> sideInputProcessor;
4140

4241
public StreamingSideInputDoFnRunner(
4342
DoFnRunner<InputT, OutputT> simpleDoFnRunner,
4443
StreamingSideInputFetcher<InputT, W> sideInputFetcher) {
4544
this.simpleDoFnRunner = simpleDoFnRunner;
46-
this.sideInputFetcher = sideInputFetcher;
45+
this.sideInputProcessor = new StreamingSideInputProcessor<>(sideInputFetcher);
4746
}
4847

4948
@Override
5049
public void startBundle() {
5150
simpleDoFnRunner.startBundle();
52-
sideInputFetcher.prefetchBlockedMap();
53-
54-
// Find the set of ready windows.
55-
Set<W> readyWindows = sideInputFetcher.getReadyWindows();
56-
57-
Iterable<BagState<WindowedValue<InputT>>> elementsBags =
58-
sideInputFetcher.prefetchElements(readyWindows);
59-
60-
// Run the DoFn code now that all side inputs are ready.
61-
for (BagState<WindowedValue<InputT>> elementsBag : elementsBags) {
62-
Iterable<WindowedValue<InputT>> elements = elementsBag.read();
63-
for (WindowedValue<InputT> elem : elements) {
64-
simpleDoFnRunner.processElement(elem);
65-
}
66-
elementsBag.clear();
51+
Iterator<WindowedValue<InputT>> unblocked = sideInputProcessor.tryUnblockElements();
52+
for (Iterator<WindowedValue<InputT>> it = unblocked; it.hasNext(); ) {
53+
WindowedValue<InputT> elem = it.next();
54+
simpleDoFnRunner.processElement(elem);
6755
}
68-
sideInputFetcher.releaseBlockedWindows(readyWindows);
6956
}
7057

7158
@Override
7259
public void processElement(WindowedValue<InputT> compressedElem) {
73-
for (WindowedValue<InputT> elem : compressedElem.explodeWindows()) {
74-
if (!sideInputFetcher.storeIfBlocked(elem)) {
75-
simpleDoFnRunner.processElement(elem);
76-
}
60+
for (Iterator<? extends WindowedValue<InputT>> it =
61+
sideInputProcessor.handleProcessElement(compressedElem);
62+
it.hasNext(); ) {
63+
WindowedValue<InputT> elem = it.next();
64+
simpleDoFnRunner.processElement(elem);
7765
}
7866
}
7967

@@ -94,7 +82,7 @@ public <KeyT> void onTimer(
9482
@Override
9583
public void finishBundle() {
9684
simpleDoFnRunner.finishBundle();
97-
sideInputFetcher.persist();
85+
sideInputProcessor.handleFinishBundle();
9886
}
9987

10088
@Override

runners/google-cloud-dataflow-java/worker/src/main/java/org/apache/beam/runners/dataflow/worker/StreamingSideInputFetcher.java

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import java.io.IOException;
2121
import java.io.InputStream;
2222
import java.io.OutputStream;
23+
import java.util.Collections;
2324
import java.util.HashMap;
2425
import java.util.HashSet;
2526
import java.util.List;
@@ -83,7 +84,6 @@ public StreamingSideInputFetcher(
8384
this.stepContext = stepContext;
8485

8586
this.mainWindowCoder = windowingStrategy.getWindowFn().windowCoder();
86-
8787
this.sideInputViews = new HashMap<>();
8888
for (PCollectionView<?> view : views) {
8989
sideInputViews.put(view.getTagInternal().getId(), view);
@@ -188,11 +188,7 @@ public Iterable<BagState<TimerData>> prefetchTimers(Iterable<W> readyWindows) {
188188
return timers;
189189
}
190190

191-
/** Compute the set of side inputs that are not yet ready for the given main input window. */
192-
public boolean storeIfBlocked(WindowedValue<InputT> elem) {
193-
@SuppressWarnings("unchecked")
194-
W window = (W) Iterables.getOnlyElement(elem.getWindows());
195-
191+
private Set<Windmill.GlobalDataRequest> checkIfBlocked(W window) {
196192
Set<Windmill.GlobalDataRequest> blocked = blockedMap().get(window);
197193
if (blocked == null) {
198194
for (PCollectionView<?> view : sideInputViews.values()) {
@@ -205,7 +201,16 @@ public boolean storeIfBlocked(WindowedValue<InputT> elem) {
205201
}
206202
}
207203
}
208-
if (blocked != null) {
204+
return blocked == null ? Collections.emptySet() : blocked;
205+
}
206+
207+
/** Compute the set of side inputs that are not yet ready for the given main input window. */
208+
public boolean storeIfBlocked(WindowedValue<InputT> elem) {
209+
@SuppressWarnings("unchecked")
210+
W window = (W) Iterables.getOnlyElement(elem.getWindows());
211+
212+
Set<Windmill.GlobalDataRequest> blocked = checkIfBlocked(window);
213+
if (!blocked.isEmpty()) {
209214
elementBag(window).add(elem);
210215
watermarkHold(window).add(elem.getTimestamp());
211216
stepContext.addBlockingSideInputs(blocked);
@@ -223,17 +228,12 @@ public boolean storeIfBlocked(TimerData timer) {
223228
@SuppressWarnings("unchecked")
224229
WindowNamespace<W> windowNamespace = (WindowNamespace<W>) timer.getNamespace();
225230
W window = windowNamespace.getWindow();
226-
227-
boolean blocked = false;
228-
for (PCollectionView<?> view : sideInputViews.values()) {
229-
if (!stepContext.issueSideInputFetch(view, window, SideInputState.UNKNOWN)) {
230-
blocked = true;
231-
}
232-
}
233-
if (blocked) {
231+
Set<Windmill.GlobalDataRequest> blocked = checkIfBlocked(window);
232+
if (!blocked.isEmpty()) {
234233
timerBag(window).add(timer);
234+
return true;
235235
}
236-
return blocked;
236+
return false;
237237
}
238238

239239
public void persist() {

0 commit comments

Comments
 (0)