Skip to content

Commit 2169a2e

Browse files
committed
[Dataflow Java Streaming] Add a timeout to how long commits will retry to the service. In cases where the service is unavailable this prevents build-up of commits on workers that are no longer relevant. This defaults to 30 minutes but can be disabled via an experiment or by setting the option.
1 parent 447899e commit 2169a2e

5 files changed

Lines changed: 186 additions & 22 deletions

File tree

runners/google-cloud-dataflow-java/src/main/java/org/apache/beam/runners/dataflow/options/DataflowStreamingPipelineOptions.java

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -196,6 +196,13 @@ public interface DataflowStreamingPipelineOptions extends PipelineOptions {
196196

197197
void setStuckCommitDurationMillis(int value);
198198

199+
@Description(
200+
"Retry commits on stream errors until this much time has elapsed since the commit was scheduled. If zero, retry forever.")
201+
@Default.InstanceFactory(CommitWorkStreamRetryTimeoutMillisFactory.class)
202+
long getCommitWorkStreamRetryTimeoutMillis();
203+
204+
void getCommitWorkStreamRetryTimeoutMillis(long value);
205+
199206
@Description(
200207
"Period for sending 'global get config' requests to the service. The duration is "
201208
+ "specified as seconds in 'PTx.yS' format, e.g. 'PT5.125S'."
@@ -343,4 +350,15 @@ public Boolean create(PipelineOptions options) {
343350
return ExperimentalOptions.hasExperiment(options, "enable_windmill_service_direct_path");
344351
}
345352
}
353+
354+
/** defaults to false unless one of the experiment is set. */
355+
class CommitWorkStreamRetryTimeoutMillisFactory implements DefaultValueFactory<Long> {
356+
@Override
357+
public Long create(PipelineOptions options) {
358+
if (ExperimentalOptions.hasExperiment(options, "disable_commit_retry_timeout")) {
359+
return 0L;
360+
}
361+
return Duration.standardMinutes(30).getMillis();
362+
}
363+
}
346364
}

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -756,6 +756,8 @@ public static StreamingDataflowWorker fromOptions(DataflowWorkerHarnessOptions o
756756
new WorkHeartbeatResponseProcessor(computationStateCache::get))
757757
.setHealthCheckIntervalMillis(
758758
options.getWindmillServiceStreamingRpcHealthCheckPeriodMs())
759+
.setCommitWorkStreamRetryTimeout(
760+
java.time.Duration.ofMillis(options.getCommitWorkStreamRetryTimeoutMillis()))
759761
.build();
760762
return ConfigFetcherComputationStateCacheAndWindmillClient.builder()
761763
.setWindmillDispatcherClient(dispatcherClient)

runners/google-cloud-dataflow-java/worker/src/main/java/org/apache/beam/runners/dataflow/worker/windmill/client/grpc/GrpcCommitWorkStream.java

Lines changed: 74 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@
2020
import static org.apache.beam.vendor.guava.v32_1_2_jre.com.google.common.base.Preconditions.checkNotNull;
2121
import static org.apache.beam.vendor.guava.v32_1_2_jre.com.google.common.base.Preconditions.checkState;
2222

23-
import com.google.auto.value.AutoValue;
2423
import java.io.PrintWriter;
2524
import java.time.Duration;
2625
import java.util.HashMap;
@@ -77,6 +76,7 @@ private static class StreamAndRequest {
7776
private final JobHeader jobHeader;
7877
private final int streamingRpcBatchLimit;
7978
private volatile boolean logMissingResponse = true;
79+
private final Duration maxRetryDuration;
8080

8181
private GrpcCommitWorkStream(
8282
String backendWorkerToken,
@@ -90,6 +90,7 @@ private GrpcCommitWorkStream(
9090
AtomicLong idGenerator,
9191
int streamingRpcBatchLimit,
9292
Duration halfClosePhysicalStreamAfter,
93+
Duration maxRetryDuration,
9394
ScheduledExecutorService executor) {
9495
super(
9596
LOG,
@@ -104,6 +105,7 @@ private GrpcCommitWorkStream(
104105
this.idGenerator = idGenerator;
105106
this.jobHeader = jobHeader;
106107
this.streamingRpcBatchLimit = streamingRpcBatchLimit;
108+
this.maxRetryDuration = maxRetryDuration;
107109
}
108110

109111
static GrpcCommitWorkStream create(
@@ -118,6 +120,7 @@ static GrpcCommitWorkStream create(
118120
AtomicLong idGenerator,
119121
int streamingRpcBatchLimit,
120122
Duration halfClosePhysicalStreamAfter,
123+
Duration maxRetryDuration,
121124
ScheduledExecutorService executor) {
122125
return new GrpcCommitWorkStream(
123126
backendWorkerToken,
@@ -130,6 +133,7 @@ static GrpcCommitWorkStream create(
130133
idGenerator,
131134
streamingRpcBatchLimit,
132135
halfClosePhysicalStreamAfter,
136+
maxRetryDuration,
133137
executor);
134138
}
135139

@@ -224,14 +228,50 @@ public void onResponse(StreamingCommitResponse response) {
224228
failureHandler.throwIfNonEmpty();
225229
}
226230

227-
@Override
228231
@SuppressWarnings("ReferenceEquality")
232+
private boolean belongsToThisHandler(StreamAndRequest streamAndRequest) {
233+
return streamAndRequest.handler == this;
234+
}
235+
236+
@Override
229237
public boolean hasPendingRequests() {
230-
return pending.entrySet().stream().anyMatch(e -> e.getValue().handler == this);
238+
return pending.entrySet().stream().anyMatch(e -> belongsToThisHandler(e.getValue()));
231239
}
232240

233241
@Override
242+
@SuppressWarnings("ReferenceEquality")
234243
public void onDone(Status status) {
244+
if (maxRetryDuration.compareTo(Duration.ZERO) > 0) {
245+
// Remove the requests that have exceeded the retry time so they are not retried.
246+
long startTimeRetryThresholdMsec = System.currentTimeMillis() - maxRetryDuration.toMillis();
247+
Iterator<Map.Entry<Long, StreamAndRequest>> iterator = pending.entrySet().iterator();
248+
int keptRequests = 0, removedRequests = 0;
249+
while (iterator.hasNext()) {
250+
StreamAndRequest streamAndRequest = checkNotNull(iterator.next().getValue());
251+
PendingRequest pendingRequest = streamAndRequest.request;
252+
if (!belongsToThisHandler(streamAndRequest)
253+
|| pendingRequest.getStartTimeMillis() > startTimeRetryThresholdMsec) {
254+
++keptRequests;
255+
continue;
256+
}
257+
++removedRequests;
258+
try {
259+
pendingRequest.completeWithStatus(CommitStatus.ABORTED);
260+
} catch (RuntimeException e) {
261+
// Catch possible exceptions to ensure that an exception for one commit does not prevent
262+
// other commits from being processed. Aggregate all the failures to throw after
263+
// processing the response if they exist.
264+
LOG.warn("Exception while aborting commit due to retry timeout.", e);
265+
}
266+
iterator.remove();
267+
}
268+
if (removedRequests > 0) {
269+
LOG.info(
270+
"Aborting {} commits which have exceeded retry deadline, kept {}. Work will be retried as needed by service.",
271+
removedRequests,
272+
keptRequests);
273+
}
274+
}
235275
if (status.isOk() && hasPendingRequests()) {
236276
LOG.warn("Unexpected requests without responses on drained physical stream, retrying.");
237277
}
@@ -270,7 +310,7 @@ private void flushInternal(Map<Long, PendingRequest> requests)
270310

271311
if (requests.size() == 1) {
272312
Map.Entry<Long, PendingRequest> elem = requests.entrySet().iterator().next();
273-
if (elem.getValue().request().getSerializedSize()
313+
if (elem.getValue().getRequest().getSerializedSize()
274314
> AbstractWindmillStream.RPC_STREAM_CHUNK_SIZE) {
275315
issueMultiChunkRequest(elem.getKey(), elem.getValue());
276316
} else {
@@ -286,7 +326,7 @@ private void issueSingleRequest(long id, PendingRequest pendingRequest)
286326
StreamingCommitWorkRequest.Builder requestBuilder = StreamingCommitWorkRequest.newBuilder();
287327
requestBuilder
288328
.addCommitChunkBuilder()
289-
.setComputationId(pendingRequest.computationId())
329+
.setComputationId(pendingRequest.getComputationId())
290330
.setRequestId(id)
291331
.setShardingKey(pendingRequest.shardingKey())
292332
.setSerializedWorkItemCommit(pendingRequest.serializedCommit());
@@ -311,9 +351,9 @@ private void issueBatchedRequest(Map<Long, PendingRequest> requests)
311351
for (Map.Entry<Long, PendingRequest> entry : requests.entrySet()) {
312352
PendingRequest request = entry.getValue();
313353
StreamingCommitRequestChunk.Builder chunkBuilder = requestBuilder.addCommitChunkBuilder();
314-
if (lastComputation == null || !lastComputation.equals(request.computationId())) {
315-
chunkBuilder.setComputationId(request.computationId());
316-
lastComputation = request.computationId();
354+
if (lastComputation == null || !lastComputation.equals(request.getComputationId())) {
355+
chunkBuilder.setComputationId(request.getComputationId());
356+
lastComputation = request.getComputationId();
317357
}
318358
chunkBuilder
319359
.setRequestId(entry.getKey())
@@ -338,7 +378,7 @@ private void issueBatchedRequest(Map<Long, PendingRequest> requests)
338378

339379
private void issueMultiChunkRequest(long id, PendingRequest pendingRequest)
340380
throws WindmillStreamShutdownException {
341-
checkNotNull(pendingRequest.computationId(), "Cannot commit WorkItem w/o a computationId.");
381+
checkNotNull(pendingRequest.getComputationId(), "Cannot commit WorkItem w/o a computationId.");
342382
ByteString serializedCommit = pendingRequest.serializedCommit();
343383
synchronized (this) {
344384
if (isShutdown) {
@@ -359,7 +399,7 @@ private void issueMultiChunkRequest(long id, PendingRequest pendingRequest)
359399
StreamingCommitRequestChunk.newBuilder()
360400
.setRequestId(id)
361401
.setSerializedWorkItemCommit(chunk)
362-
.setComputationId(pendingRequest.computationId())
402+
.setComputationId(pendingRequest.getComputationId())
363403
.setShardingKey(pendingRequest.shardingKey());
364404
int remaining = serializedCommit.size() - end;
365405
if (remaining > 0) {
@@ -376,34 +416,46 @@ private void issueMultiChunkRequest(long id, PendingRequest pendingRequest)
376416
}
377417
}
378418

379-
@AutoValue
380-
abstract static class PendingRequest {
419+
private static class PendingRequest {
420+
private final String computationId;
421+
private final WorkItemCommitRequest request;
422+
private final Consumer<CommitStatus> onDone;
423+
private final long startTimeMillis;
381424

382-
private static PendingRequest create(
425+
private PendingRequest(
383426
String computationId, WorkItemCommitRequest request, Consumer<CommitStatus> onDone) {
384-
return new AutoValue_GrpcCommitWorkStream_PendingRequest(computationId, request, onDone);
427+
this.computationId = computationId;
428+
this.request = request;
429+
this.onDone = onDone;
430+
this.startTimeMillis = System.currentTimeMillis();
385431
}
386432

387-
abstract String computationId();
433+
String getComputationId() {
434+
return computationId;
435+
}
388436

389-
abstract WorkItemCommitRequest request();
437+
WorkItemCommitRequest getRequest() {
438+
return request;
439+
}
390440

391-
abstract Consumer<CommitStatus> onDone();
441+
long getStartTimeMillis() {
442+
return startTimeMillis;
443+
}
392444

393445
private long getBytes() {
394-
return (long) request().getSerializedSize() + computationId().length();
446+
return (long) request.getSerializedSize() + computationId.length();
395447
}
396448

397449
private ByteString serializedCommit() {
398-
return request().toByteString();
450+
return request.toByteString();
399451
}
400452

401453
private void completeWithStatus(CommitStatus commitStatus) {
402-
onDone().accept(commitStatus);
454+
onDone.accept(commitStatus);
403455
}
404456

405457
private long shardingKey() {
406-
return request().getShardingKey();
458+
return request.getShardingKey();
407459
}
408460

409461
private void abort() {
@@ -462,7 +514,7 @@ public boolean commitWorkItem(
462514
return false;
463515
}
464516

465-
PendingRequest request = PendingRequest.create(computation, commitRequest, onDone);
517+
PendingRequest request = new PendingRequest(computation, commitRequest, onDone);
466518
add(idGenerator.incrementAndGet(), request);
467519
return true;
468520
}

runners/google-cloud-dataflow-java/worker/src/main/java/org/apache/beam/runners/dataflow/worker/windmill/client/grpc/GrpcWindmillStreamFactory.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,7 @@ public class GrpcWindmillStreamFactory implements StatusDataProvider {
100100
private final Consumer<List<ComputationHeartbeatResponse>> processHeartbeatResponses;
101101
private final java.time.Duration directStreamingRpcPhysicalStreamHalfCloseAfter;
102102
private final Supplier<ScheduledExecutorService> executorServiceSupplier;
103+
private final java.time.Duration commitWorkStreamRetryTimeout;
103104

104105
private GrpcWindmillStreamFactory(
105106
JobHeader jobHeader,
@@ -111,6 +112,7 @@ private GrpcWindmillStreamFactory(
111112
Consumer<List<ComputationHeartbeatResponse>> processHeartbeatResponses,
112113
Supplier<Duration> maxBackOffSupplier,
113114
java.time.Duration directStreamingRpcPhysicalStreamHalfCloseAfter,
115+
java.time.Duration commitWorkStreamRetryTimeout,
114116
Supplier<ScheduledExecutorService> executorServiceSupplier) {
115117
this.jobHeader = jobHeader;
116118
this.logEveryNStreamFailures = logEveryNStreamFailures;
@@ -132,6 +134,7 @@ private GrpcWindmillStreamFactory(
132134
this.directStreamingRpcPhysicalStreamHalfCloseAfter =
133135
directStreamingRpcPhysicalStreamHalfCloseAfter;
134136
this.executorServiceSupplier = executorServiceSupplier;
137+
this.commitWorkStreamRetryTimeout = commitWorkStreamRetryTimeout;
135138
}
136139

137140
/** @implNote Used for {@link AutoBuilder} {@link Builder} class, do not call directly. */
@@ -146,6 +149,7 @@ static GrpcWindmillStreamFactory create(
146149
Supplier<Duration> maxBackOffSupplier,
147150
int healthCheckIntervalMillis,
148151
java.time.Duration directStreamingRpcPhysicalStreamHalfCloseAfter,
152+
java.time.Duration commitWorkStreamRetryTimeout,
149153
Supplier<ScheduledExecutorService> scheduledExecutorServiceSupplier) {
150154
GrpcWindmillStreamFactory streamFactory =
151155
new GrpcWindmillStreamFactory(
@@ -158,6 +162,7 @@ static GrpcWindmillStreamFactory create(
158162
processHeartbeatResponses,
159163
maxBackOffSupplier,
160164
directStreamingRpcPhysicalStreamHalfCloseAfter,
165+
commitWorkStreamRetryTimeout,
161166
scheduledExecutorServiceSupplier);
162167

163168
if (healthCheckIntervalMillis >= 0) {
@@ -200,6 +205,7 @@ public static GrpcWindmillStreamFactory.Builder of(JobHeader jobHeader) {
200205
.setProcessHeartbeatResponses(ignored -> {})
201206
.setDirectStreamingRpcPhysicalStreamHalfCloseAfter(
202207
DEFAULT_DIRECT_STREAMING_RPC_PHYSICAL_STREAM_HALF_CLOSE_AFTER)
208+
.setCommitWorkStreamRetryTimeout(java.time.Duration.ZERO)
203209
.setScheduledExecutorServiceSupplier(() -> null);
204210
}
205211

@@ -347,6 +353,7 @@ public CommitWorkStream createCommitWorkStream(CloudWindmillServiceV1Alpha1Stub
347353
streamIdGenerator,
348354
streamingRpcBatchLimit,
349355
java.time.Duration.ZERO,
356+
commitWorkStreamRetryTimeout,
350357
executorForDispatchedStreams("CommitWork"));
351358
}
352359

@@ -363,6 +370,7 @@ public CommitWorkStream createDirectCommitWorkStream(WindmillConnection connecti
363370
streamIdGenerator,
364371
streamingRpcBatchLimit,
365372
directStreamingRpcPhysicalStreamHalfCloseAfter,
373+
java.time.Duration.ZERO,
366374
executorForDirectStreams(connection.backendWorkerToken(), "CommitWork"));
367375
}
368376

@@ -426,6 +434,8 @@ Builder setProcessHeartbeatResponses(
426434

427435
Builder setDirectStreamingRpcPhysicalStreamHalfCloseAfter(java.time.Duration timeout);
428436

437+
Builder setCommitWorkStreamRetryTimeout(java.time.Duration timeout);
438+
429439
Builder setScheduledExecutorServiceSupplier(
430440
Supplier<ScheduledExecutorService> scheduledExecutorServiceSupplier);
431441

0 commit comments

Comments
 (0)