Skip to content

Commit 86dfa9f

Browse files
committed
[FnApi Java] Add support for separate named data streams to provide bundle isolation.
This is advertised to the runner via a new NAMED_DATA_STREAMS protocol capability. The runner is then free to assign bundles to named data streams as it chooses to isolate bundle processing from each other. Instead of single data stream from the sdk, the sdk will create a data stream for each name. The benefit of doing so is that the multiplexing currently performed on data stream messages being received allows a slow bundle to fill up buffers and block the shared stream. With separate named streams, bundles on other data streams have separate grpc flow control from the blocked stream and are not affected.
1 parent a88686c commit 86dfa9f

15 files changed

Lines changed: 307 additions & 252 deletions

File tree

model/fn-execution/src/main/proto/org/apache/beam/model/fn_execution/v1/beam_fn_api.proto

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,9 @@ message RemoteGrpcPort {
120120
service BeamFnControl {
121121
// Instructions sent by the runner to the SDK requesting different types
122122
// of work.
123+
//
124+
// Header metadata has the specified keys pairs:
125+
// - "worker_id": the id of the sdk
123126
rpc Control(
124127
// A stream of responses to instructions the SDK was asked to be
125128
// performed.
@@ -130,6 +133,9 @@ service BeamFnControl {
130133

131134
// Used to get the full process bundle descriptors for bundles one
132135
// is asked to process.
136+
//
137+
// Header metadata has the specified keys pairs:
138+
// - "worker_id": the id of the sdk
133139
rpc GetProcessBundleDescriptor(GetProcessBundleDescriptorRequest) returns (
134140
ProcessBundleDescriptor) {}
135141
}
@@ -416,14 +422,21 @@ message ProcessBundleRequest {
416422
// at https://s.apache.org/beam-fn-api-control-data-embedding.
417423
Elements elements = 3;
418424

419-
// indicates that the runner has no stare for the keys in this bundle
425+
// Indicates that the runner has no state for the keys in this bundle
420426
// so SDk can safely begin stateful processing with a locally-generated
421-
// initial empty state
427+
// initial empty state.
422428
bool has_no_state = 4;
423429

424-
// indicates that the runner will never process another bundle for the keys
430+
// Indicates that the runner will never process another bundle for the keys
425431
// in this bundle so state need not be included in the bundle commit.
426432
bool only_bundle_for_keys = 5;
433+
434+
// (Optional) If non-empty, the ID of the data stream to use for this bundle.
435+
// See comments at BeamFnData.Data for more details.
436+
//
437+
// The runner should only populate this field if the sdk advertises the
438+
// beam:protocol:named_data_streams:v1 capability.
439+
string data_stream_id = 6;
427440
}
428441

429442
message ProcessBundleResponse {
@@ -835,6 +848,11 @@ message Elements {
835848
// Stable
836849
service BeamFnData {
837850
// Used to send data between harnesses.
851+
//
852+
// Header metadata has the specified keys pairs:
853+
// - "worker_id": value is the id of the sdk
854+
// - "data_stream_id": value is the id of the data stream, distinguishing it from other data streams from the same
855+
// sdk. This field should only be populated if requested in a received ProcessBundleRequest from the runner.
838856
rpc Data(
839857
// A stream of data representing input.
840858
stream Elements)
@@ -900,6 +918,9 @@ message StateResponse {
900918

901919
service BeamFnState {
902920
// Used to get/append/clear state stored by the runner on behalf of the SDK.
921+
//
922+
// Header metadata has the specified keys pairs:
923+
// - "worker_id": the id of the sdk
903924
rpc State(
904925
// A stream of state instructions requested of the runner.
905926
stream StateRequest)
@@ -1295,6 +1316,11 @@ message LogControl {}
12951316
service BeamFnLogging {
12961317
// Allows for the SDK to emit log entries which the runner can
12971318
// associate with the active job.
1319+
//
1320+
// Used to get/append/clear state stored by the runner on behalf of the SDK.
1321+
//
1322+
// Header metadata has the specified keys pairs:
1323+
// - "worker_id": the id of the sdk
12981324
rpc Logging(
12991325
// A stream of log entries batched into lists emitted by the SDK harness.
13001326
stream LogEntry.List)
@@ -1356,6 +1382,8 @@ message WorkerStatusResponse {
13561382

13571383
// API for SDKs to report debug-related statuses to runner during pipeline execution.
13581384
service BeamFnWorkerStatus {
1385+
// Header metadata has the specified keys pairs:
1386+
// - "worker_id": the id of the sdk
13591387
rpc WorkerStatus (stream WorkerStatusResponse)
13601388
returns (stream WorkerStatusRequest) {}
13611389
}

model/pipeline/src/main/proto/org/apache/beam/model/pipeline/v1/beam_runner_api.proto

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1689,6 +1689,10 @@ message StandardProtocols {
16891689
// Indicates whether the SDK supports multimap state.
16901690
MULTIMAP_STATE = 12
16911691
[(beam_urn) = "beam:protocol:multimap_state:v1"];
1692+
1693+
// Indicates whether the SDK supports data stream ids being requested by the runner in
1694+
// ProcessBundleRequests.
1695+
NAMED_DATA_STREAMS = 13 [(beam_urn) = "beam:protocol:named_data_streams:v1"];
16921696
}
16931697
}
16941698

sdks/java/core/src/main/java/org/apache/beam/sdk/fn/data/BeamFnDataGrpcMultiplexer.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ public class BeamFnDataGrpcMultiplexer implements AutoCloseable {
6363
private final Cache</*instructionId=*/ String, /*unused=*/ Boolean> poisonedInstructionIds;
6464

6565
private static class PoisonedException extends RuntimeException {
66-
public PoisonedException() {
66+
private PoisonedException() {
6767
super("Instruction poisoned");
6868
}
6969
};

sdks/java/core/src/main/java/org/apache/beam/sdk/fn/data/BeamFnDataOutboundAggregator.java

Lines changed: 37 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@
1717
*/
1818
package org.apache.beam.sdk.fn.data;
1919

20+
import static org.apache.beam.vendor.guava.v32_1_2_jre.com.google.common.base.Preconditions.checkNotNull;
21+
2022
import java.io.IOException;
2123
import java.util.Collections;
2224
import java.util.HashMap;
@@ -28,7 +30,6 @@
2830
import java.util.concurrent.Executors;
2931
import java.util.concurrent.ScheduledFuture;
3032
import java.util.concurrent.TimeUnit;
31-
import java.util.function.Supplier;
3233
import javax.annotation.Nullable;
3334
import javax.annotation.concurrent.NotThreadSafe;
3435
import org.apache.beam.model.fnexecution.v1.BeamFnApi.Elements;
@@ -56,7 +57,7 @@
5657
@SuppressWarnings({
5758
"nullness" // TODO(https://github.com/apache/beam/issues/20497)
5859
})
59-
// The calling thread that invokes sendBufferedDataAndFinishOutboundStreams synchronizes on
60+
// The calling thread that invokes sendOrCollectBufferedDataAndFinishOutboundStreams synchronizes on
6061
// flushLock effectively making the periodic flushing no longer read or mutate hasFlushedForBundle
6162
// and allowing the calling thread to read and mutate hasFlushedForBundle safely without needing to
6263
// create another memory barrier. Also note that flush is always invoked when synchronizing on
@@ -72,33 +73,40 @@ public class BeamFnDataOutboundAggregator {
7273
private static final Logger LOG = LoggerFactory.getLogger(BeamFnDataOutboundAggregator.class);
7374
private final int sizeLimit;
7475
private final long timeLimit;
75-
private final Supplier<String> processBundleRequestIdSupplier;
76+
private String instructionId;
7677
@VisibleForTesting final Map<String, Receiver<?>> outputDataReceivers;
7778
@VisibleForTesting final Map<TimerEndpoint, Receiver<?>> outputTimersReceivers;
78-
private final StreamObserver<Elements> outboundObserver;
79+
@Nullable private StreamObserver<Elements> outboundObserver;
7980
@Nullable @VisibleForTesting ScheduledFuture<?> flushFuture;
8081
private long bytesWrittenSinceFlush;
8182
private final Object flushLock;
8283
private final boolean collectElementsIfNoFlushes;
8384
private boolean hasFlushedForBundle;
8485

85-
public BeamFnDataOutboundAggregator(
86-
PipelineOptions options,
87-
Supplier<String> processBundleRequestIdSupplier,
88-
StreamObserver<Elements> outboundObserver,
89-
boolean collectElementsIfNoFlushes) {
86+
public BeamFnDataOutboundAggregator(PipelineOptions options, boolean collectElementsIfNoFlushes) {
9087
this.sizeLimit = getSizeLimit(options);
9188
this.timeLimit = getTimeLimit(options);
9289
this.collectElementsIfNoFlushes = collectElementsIfNoFlushes;
9390
this.outputDataReceivers = new HashMap<>();
9491
this.outputTimersReceivers = new HashMap<>();
95-
this.outboundObserver = outboundObserver;
96-
this.processBundleRequestIdSupplier = processBundleRequestIdSupplier;
9792
this.bytesWrittenSinceFlush = 0L;
9893
this.flushLock = new Object();
9994
this.hasFlushedForBundle = false;
10095
}
10196

97+
public void prepareForInstruction(
98+
String instructionId, StreamObserver<Elements> outboundObserver) {
99+
if (timeLimit > 0) {
100+
synchronized (flushLock) {
101+
this.instructionId = instructionId;
102+
this.outboundObserver = outboundObserver;
103+
}
104+
} else {
105+
this.instructionId = instructionId;
106+
this.outboundObserver = outboundObserver;
107+
}
108+
}
109+
102110
/** Starts the flushing daemon thread if data_buffer_time_limit_ms is set. */
103111
public void start() {
104112
if (timeLimit > 0 && this.flushFuture == null) {
@@ -166,7 +174,7 @@ private void flushInternal() {
166174
}
167175
Elements.Builder elements = convertBufferForTransmission();
168176
if (elements.getDataCount() > 0 || elements.getTimersCount() > 0) {
169-
outboundObserver.onNext(elements.build());
177+
checkNotNull(outboundObserver).onNext(elements.build());
170178
}
171179
hasFlushedForBundle = true;
172180
}
@@ -177,6 +185,7 @@ private void flushInternal() {
177185
* collectElementsIfNoFlushes=true, and there was no previous flush in this bundle, otherwise
178186
* returns null.
179187
*/
188+
@Nullable
180189
public Elements sendOrCollectBufferedDataAndFinishOutboundStreams() {
181190
if (outputTimersReceivers.isEmpty() && outputDataReceivers.isEmpty()) {
182191
return null;
@@ -189,16 +198,17 @@ public Elements sendOrCollectBufferedDataAndFinishOutboundStreams() {
189198
} else {
190199
bufferedElements = convertBufferForTransmission();
191200
}
201+
checkNotNull(instructionId);
192202
LOG.debug(
193203
"Closing streams for instruction {} and outbound data {} and timers {}.",
194-
processBundleRequestIdSupplier.get(),
204+
instructionId,
195205
outputDataReceivers,
196206
outputTimersReceivers);
197207
for (Map.Entry<String, Receiver<?>> entry : outputDataReceivers.entrySet()) {
198208
String pTransformId = entry.getKey();
199209
bufferedElements
200210
.addDataBuilder()
201-
.setInstructionId(processBundleRequestIdSupplier.get())
211+
.setInstructionId(instructionId)
202212
.setTransformId(pTransformId)
203213
.setIsLast(true);
204214
entry.getValue().resetStats();
@@ -207,34 +217,37 @@ public Elements sendOrCollectBufferedDataAndFinishOutboundStreams() {
207217
TimerEndpoint timerKey = entry.getKey();
208218
bufferedElements
209219
.addTimersBuilder()
210-
.setInstructionId(processBundleRequestIdSupplier.get())
220+
.setInstructionId(instructionId)
211221
.setTransformId(timerKey.pTransformId)
212222
.setTimerFamilyId(timerKey.timerFamilyId)
213223
.setIsLast(true);
214224
entry.getValue().resetStats();
215225
}
226+
// This is the end of the bundle so we reset state to prepare for future bundles.
227+
instructionId = null;
216228
if (collectElementsIfNoFlushes && !hasFlushedForBundle) {
229+
outboundObserver = null;
217230
return bufferedElements.build();
218231
}
219-
outboundObserver.onNext(bufferedElements.build());
220-
// This is now at the end of a bundle, so we reset hasFlushedForBundle to prepare for new
221-
// bundles.
232+
checkNotNull(outboundObserver).onNext(bufferedElements.build());
233+
outboundObserver = null;
222234
hasFlushedForBundle = false;
223235
return null;
224236
}
225237

226238
// Send the elements to the StreamObserver associated with this aggregator.
227239
public void sendElements(Elements elements) {
228-
outboundObserver.onNext(elements);
240+
checkNotNull(outboundObserver).onNext(elements);
229241
}
230242

231243
public void discard() {
232244
if (flushFuture != null) {
233-
flushFuture.cancel(true);
245+
flushFuture.cancel(false);
234246
}
235247
}
236248

237249
private Elements.Builder convertBufferForTransmission() {
250+
checkNotNull(instructionId);
238251
Elements.Builder bufferedElements = Elements.newBuilder();
239252
for (Map.Entry<String, Receiver<?>> entry : outputDataReceivers.entrySet()) {
240253
if (!entry.getValue().hasBufferedOutput()) {
@@ -243,7 +256,7 @@ private Elements.Builder convertBufferForTransmission() {
243256
ByteString bytes = entry.getValue().toByteStringAndResetBuffer();
244257
bufferedElements
245258
.addDataBuilder()
246-
.setInstructionId(processBundleRequestIdSupplier.get())
259+
.setInstructionId(instructionId)
247260
.setTransformId(entry.getKey())
248261
.setData(bytes);
249262
}
@@ -254,7 +267,7 @@ private Elements.Builder convertBufferForTransmission() {
254267
ByteString bytes = entry.getValue().toByteStringAndResetBuffer();
255268
bufferedElements
256269
.addTimersBuilder()
257-
.setInstructionId(processBundleRequestIdSupplier.get())
270+
.setInstructionId(instructionId)
258271
.setTransformId(entry.getKey().pTransformId)
259272
.setTimerFamilyId(entry.getKey().timerFamilyId)
260273
.setTimers(bytes);
@@ -353,10 +366,12 @@ public void accept(T input) throws Exception {
353366
}
354367
}
355368

369+
@VisibleForTesting
356370
public long getByteCount() {
357371
return perBundleByteCount;
358372
}
359373

374+
@VisibleForTesting
360375
public long getElementCount() {
361376
return perBundleElementCount;
362377
}

sdks/java/core/src/main/java/org/apache/beam/sdk/util/construction/Environments.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -522,6 +522,7 @@ public static Set<String> getJavaCapabilities() {
522522
capabilities.add(BeamUrns.getUrn(StandardProtocols.Enum.SDK_CONSUMING_RECEIVED_DATA));
523523
capabilities.add(BeamUrns.getUrn(StandardProtocols.Enum.ORDERED_LIST_STATE));
524524
capabilities.add(BeamUrns.getUrn(StandardProtocols.Enum.MULTIMAP_STATE));
525+
capabilities.add(BeamUrns.getUrn(StandardProtocols.Enum.NAMED_DATA_STREAMS));
525526
return capabilities.build();
526527
}
527528

0 commit comments

Comments
 (0)