Skip to content

Commit dedbf9c

Browse files
authored
Prevent CommitTooLargeException in OrderedEventProcessor on duplicate floods (apache#40242)
* Fix: Prevent CommitTooLargeException in OrderedEventProcessor on duplicate floods * Apply spotless formatting fixes
1 parent 5a218f6 commit dedbf9c

5 files changed

Lines changed: 70 additions & 6 deletions

File tree

‎sdks/java/extensions/ordered/src/main/java/org/apache/beam/sdk/extensions/ordered/GlobalSequencesProcessorDoFn.java‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,7 @@ public void processElement(
175175
if (numberOfResultsBeforeBundleStart == null) {
176176
// Per key processing is synchronized by Beam. There is no need to have it here.
177177
numberOfResultsBeforeBundleStart = processingState.getResultCount();
178+
numberOfDuplicatesBeforeBundleStart = processingState.getDuplicates();
178179
}
179180

180181
processingState.eventReceived();
@@ -262,6 +263,7 @@ public void onBatchEmission(
262263
}
263264

264265
this.numberOfResultsBeforeBundleStart = processingState.getResultCount();
266+
this.numberOfDuplicatesBeforeBundleStart = processingState.getDuplicates();
265267

266268
state =
267269
processBufferedEventRange(

‎sdks/java/extensions/ordered/src/main/java/org/apache/beam/sdk/extensions/ordered/ProcessingState.java‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -313,6 +313,10 @@ public long resultsProducedInBundle(long numberOfResultsBeforeBundleStart) {
313313
return resultCount - numberOfResultsBeforeBundleStart;
314314
}
315315

316+
public long duplicatesProducedInBundle(long numberOfDuplicatesBeforeBundleStart) {
317+
return duplicates - numberOfDuplicatesBeforeBundleStart;
318+
}
319+
316320
public void updateGlobalSequenceDetails(ContiguousSequenceRange updated) {
317321
if (thereAreGloballySequencedEventsToBeProcessed()) {
318322
// We don't update the timer if we can already process events in the onTimer batch.

‎sdks/java/extensions/ordered/src/main/java/org/apache/beam/sdk/extensions/ordered/ProcessorDoFn.java‎

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ abstract class ProcessorDoFn<
6363
private final long maxNumberOfResultsToProduce;
6464

6565
protected @Nullable Long numberOfResultsBeforeBundleStart = 0L;
66+
protected @Nullable Long numberOfDuplicatesBeforeBundleStart = 0L;
6667

6768
ProcessorDoFn(
6869
EventExaminer<EventT, StateT> eventExaminer,
@@ -85,12 +86,14 @@ abstract class ProcessorDoFn<
8586
@StartBundle
8687
public void onBundleStart() {
8788
numberOfResultsBeforeBundleStart = null;
89+
numberOfDuplicatesBeforeBundleStart = null;
8890
}
8991

9092
@FinishBundle
9193
public void onBundleFinish() {
9294
// This might be necessary because this field is also used in a Timer
9395
numberOfResultsBeforeBundleStart = null;
96+
numberOfDuplicatesBeforeBundleStart = null;
9497
}
9598

9699
/** Returns true if each event needs to be examined. */
@@ -267,10 +270,14 @@ protected void emitProcessingStatus(
267270

268271
protected boolean reachedMaxResultCountForBundle(
269272
ProcessingState<EventKeyT> processingState, Timer largeBatchEmissionTimer) {
270-
boolean exceeded =
273+
long resultsEmitted =
271274
processingState.resultsProducedInBundle(
272-
numberOfResultsBeforeBundleStart == null ? 0 : numberOfResultsBeforeBundleStart)
273-
>= maxNumberOfResultsToProduce;
275+
numberOfResultsBeforeBundleStart == null ? 0 : numberOfResultsBeforeBundleStart);
276+
long duplicatesEmitted =
277+
processingState.duplicatesProducedInBundle(
278+
numberOfDuplicatesBeforeBundleStart == null ? 0 : numberOfDuplicatesBeforeBundleStart);
279+
280+
boolean exceeded = (resultsEmitted + duplicatesEmitted) >= maxNumberOfResultsToProduce;
274281
if (exceeded) {
275282
if (LOG.isTraceEnabled()) {
276283
LOG.trace(
@@ -354,9 +361,10 @@ StateT processBufferedEventRange(
354361
beforeInitialSequence
355362
? Reason.before_initial_sequence
356363
: Reason.duplicate))));
357-
// TODO: When there is a large number of duplicates this can cause a situation where
358-
// we produce too much output and the runner will start throwing unrecoverable errors.
359-
// Need to add counting logic to accumulate both the normal and DLQ outputs.
364+
if (reachedMaxResultCountForBundle(processingState, largeBatchEmissionTimer)) {
365+
endClearRange = fromLong(eventSequence + 1);
366+
break;
367+
}
360368
continue;
361369
}
362370

‎sdks/java/extensions/ordered/src/main/java/org/apache/beam/sdk/extensions/ordered/SequencePerKeyProcessorDoFn.java‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,7 @@ public void processElement(
159159
if (numberOfResultsBeforeBundleStart == null) {
160160
// Per key processing is synchronized by Beam. There is no need to have it here.
161161
numberOfResultsBeforeBundleStart = processingState.getResultCount();
162+
numberOfDuplicatesBeforeBundleStart = processingState.getDuplicates();
162163
}
163164

164165
processingState.eventReceived();
@@ -259,6 +260,7 @@ public void onBatchEmission(
259260
LOG.debug("Starting to process batch for key '{}'", processingState.getKey());
260261

261262
this.numberOfResultsBeforeBundleStart = processingState.getResultCount();
263+
this.numberOfDuplicatesBeforeBundleStart = processingState.getDuplicates();
262264

263265
processBufferedEvents(
264266
processingState, state, bufferedEventsState, outputReceiver, largeBatchEmissionTimer);

‎sdks/java/extensions/ordered/src/test/java/org/apache/beam/sdk/extensions/ordered/OrderedEventProcessorPerKeySequenceTest.java‎

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -246,6 +246,54 @@ public void testHandlingOfDuplicateSequences() throws CannotProvideCoderExceptio
246246
DONT_PRODUCE_STATUS_ON_EVERY_EVENT);
247247
}
248248

249+
@Test
250+
public void testLargeNumberOfDuplicatesPaginatesCorrectly() throws CannotProvideCoderException {
251+
int maxResultsPerOutput = 10;
252+
int duplicateCount = 25;
253+
List<Event> events = new ArrayList<>();
254+
events.add(Event.create(0, "id-1", "a"));
255+
256+
// Add 25 duplicates of the next event. The maxResultsPerOutput is 10, so it should
257+
// paginate the duplicates across multiple bundles.
258+
for (int i = 0; i < duplicateCount; i++) {
259+
events.add(Event.create(1, "id-1", "b"));
260+
}
261+
262+
Collection<KV<String, OrderedProcessingStatus>> expectedStatuses = new ArrayList<>();
263+
expectedStatuses.add(
264+
KV.of(
265+
"id-1",
266+
OrderedProcessingStatus.create(
267+
1L,
268+
0,
269+
null,
270+
null,
271+
events.size(),
272+
2L,
273+
duplicateCount - 1, // one is processed, the rest are duplicates
274+
false,
275+
NOT_USED_FOR_TESTING)));
276+
277+
Collection<KV<String, String>> expectedOutput = new ArrayList<>();
278+
expectedOutput.add(KV.of("id-1", "a"));
279+
expectedOutput.add(KV.of("id-1", "ab"));
280+
281+
Collection<KV<String, KV<Long, UnprocessedEvent<String>>>> duplicates = new ArrayList<>();
282+
for (int i = 0; i < duplicateCount - 1; i++) {
283+
duplicates.add(KV.of("id-1", KV.of(1L, UnprocessedEvent.create("b", Reason.duplicate))));
284+
}
285+
286+
testPerKeySequenceProcessing(
287+
events.toArray(new Event[0]),
288+
expectedStatuses,
289+
expectedOutput,
290+
duplicates,
291+
EMISSION_FREQUENCY_ON_EVERY_ELEMENT,
292+
INITIAL_SEQUENCE_OF_0,
293+
maxResultsPerOutput,
294+
DONT_PRODUCE_STATUS_ON_EVERY_EVENT);
295+
}
296+
249297
@Test
250298
public void testHandlingOfCheckedExceptions() throws CannotProvideCoderException {
251299
Event[] events = {

0 commit comments

Comments
 (0)