Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -431,9 +431,7 @@ public void open() throws Exception {
sourceMetricGroup,
getProcessingTimeService(),
getExecutionConfig().getAutoWatermarkInterval(),
mainInputActivityClock,
getProcessingTimeService().getClock(),
taskIOMetricGroup);
mainInputActivityClock);
} else {
eventTimeLogic =
TimestampsAndWatermarks.createNoOpEventTimeLogic(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.apache.flink.streaming.api.operators.source;

import org.apache.flink.annotation.Internal;
import org.apache.flink.runtime.event.WatermarkEvent;
import org.apache.flink.streaming.api.operators.util.PausableRelativeClock;
import org.apache.flink.streaming.api.watermark.Watermark;
import org.apache.flink.streaming.runtime.io.PushingAsyncDataInput;
import org.apache.flink.streaming.runtime.streamrecord.LatencyMarker;
import org.apache.flink.streaming.runtime.streamrecord.RecordAttributes;
import org.apache.flink.streaming.runtime.streamrecord.StreamRecord;
import org.apache.flink.streaming.runtime.watermarkstatus.WatermarkStatus;

import static org.apache.flink.util.Preconditions.checkNotNull;

/**
* A {@link PushingAsyncDataInput.DataOutput} decorator that pauses the source's input activity
* clock while the downstream operators process what was emitted.
*
* <p>Everything emitted by a source is processed synchronously by the chained operators on the task
* thread before control returns to the source reader. During that time no split can be polled, so
* the elapsed time says nothing about whether a split has records. Without this decorator a slow
* chained operator, or a window firing triggered by a watermark, can exceed the idleness timeout
* and get a split with pending records marked idle, after which those records arrive behind an
* already advanced watermark and are dropped as late.
*
* <p>This is the same idea as pausing the clock during backpressure (FLIP-471): the idleness
* timeout only counts time during which the source was actually able to make progress on its input.
*
* @param <T> The type of the emitted records.
*/
@Internal
public final class ActivityClockPausingDataOutput<T>
implements PushingAsyncDataInput.DataOutput<T> {

private final PushingAsyncDataInput.DataOutput<T> delegate;
private final PausableRelativeClock inputActivityClock;

public ActivityClockPausingDataOutput(
PushingAsyncDataInput.DataOutput<T> delegate,
PausableRelativeClock inputActivityClock) {
this.delegate = checkNotNull(delegate);
this.inputActivityClock = checkNotNull(inputActivityClock);
}

@Override
public void emitRecord(StreamRecord<T> streamRecord) throws Exception {
inputActivityClock.pause();
try {
delegate.emitRecord(streamRecord);
} finally {
inputActivityClock.unPause();
}
}

@Override
public void emitWatermark(Watermark watermark) throws Exception {
inputActivityClock.pause();
try {
delegate.emitWatermark(watermark);
} finally {
inputActivityClock.unPause();
}
}

@Override
public void emitWatermarkStatus(WatermarkStatus watermarkStatus) throws Exception {
inputActivityClock.pause();
try {
delegate.emitWatermarkStatus(watermarkStatus);
} finally {
inputActivityClock.unPause();
}
}

@Override
public void emitLatencyMarker(LatencyMarker latencyMarker) throws Exception {
inputActivityClock.pause();
try {
delegate.emitLatencyMarker(latencyMarker);
} finally {
inputActivityClock.unPause();
}
}

@Override
public void emitRecordAttributes(RecordAttributes recordAttributes) throws Exception {
inputActivityClock.pause();
try {
delegate.emitRecordAttributes(recordAttributes);
} finally {
inputActivityClock.unPause();
}
}

@Override
public void emitWatermark(WatermarkEvent watermark) throws Exception {
inputActivityClock.pause();
try {
delegate.emitWatermark(watermark);
} finally {
inputActivityClock.unPause();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,9 @@
import org.apache.flink.api.common.eventtime.WatermarkOutputMultiplexer;
import org.apache.flink.api.connector.source.ReaderOutput;
import org.apache.flink.api.connector.source.SourceOutput;
import org.apache.flink.runtime.metrics.groups.TaskIOMetricGroup;
import org.apache.flink.streaming.api.operators.util.PausableRelativeClock;
import org.apache.flink.streaming.runtime.io.PushingAsyncDataInput;
import org.apache.flink.streaming.runtime.tasks.ProcessingTimeService;
import org.apache.flink.util.clock.Clock;
import org.apache.flink.util.clock.RelativeClock;

import javax.annotation.Nullable;

Expand Down Expand Up @@ -66,11 +63,7 @@ public class ProgressiveTimestampsAndWatermarks<T> implements TimestampsAndWater

private final long periodicWatermarkInterval;

private final RelativeClock mainInputActivityClock;

private final Clock clock;

private final TaskIOMetricGroup taskIOMetricGroup;
private final PausableRelativeClock mainInputActivityClock;

@Nullable private SplitLocalOutputs<T> currentPerSplitOutputs;

Expand All @@ -84,17 +77,13 @@ public ProgressiveTimestampsAndWatermarks(
TimestampsAndWatermarksContextProvider watermarksContextProvider,
ProcessingTimeService timeService,
Duration periodicWatermarkInterval,
RelativeClock mainInputActivityClock,
Clock clock,
TaskIOMetricGroup taskIOMetricGroup) {
PausableRelativeClock mainInputActivityClock) {

this.timestampAssigner = timestampAssigner;
this.watermarksFactory = watermarksFactory;
this.watermarksContextProvider = watermarksContextProvider;
this.timeService = timeService;
this.mainInputActivityClock = mainInputActivityClock;
this.clock = clock;
this.taskIOMetricGroup = taskIOMetricGroup;

long periodicWatermarkIntervalMillis;
try {
Expand All @@ -120,28 +109,36 @@ public ReaderOutput<T> createMainOutput(
currentMainOutput == null && currentPerSplitOutputs == null,
"already created a main output");

final WatermarkOutput watermarkOutput =
new WatermarkToDataOutput(output, watermarkUpdateListener);
IdlenessManager idlenessManager = new IdlenessManager(watermarkOutput);

final WatermarkGenerator<T> watermarkGenerator =
watermarksFactory.createWatermarkGenerator(
watermarksContextProvider.create(mainInputActivityClock));

// Downstream operators process every emitted element synchronously on the task thread,
// during which no split can be polled. When something measures input activity time (i.e.
// idleness detection is configured), hide that time from the activity clocks; otherwise
// keep the plain output to leave the hot path untouched.
final PushingAsyncDataInput.DataOutput<T> recordOutput =
watermarksContextProvider.isInputActivityClockRequested()
? new ActivityClockPausingDataOutput<>(output, mainInputActivityClock)
: output;

final WatermarkOutput watermarkOutput =
new WatermarkToDataOutput(recordOutput, watermarkUpdateListener);
IdlenessManager idlenessManager = new IdlenessManager(watermarkOutput);

currentPerSplitOutputs =
new SplitLocalOutputs<>(
output,
recordOutput,
idlenessManager.getSplitLocalOutput(),
watermarkUpdateListener,
timestampAssigner,
watermarksFactory,
watermarksContextProvider,
clock,
taskIOMetricGroup);
mainInputActivityClock);

currentMainOutput =
new StreamingReaderOutput<>(
output,
recordOutput,
idlenessManager.getMainOutput(),
timestampAssigner,
watermarkGenerator,
Expand Down Expand Up @@ -237,8 +234,7 @@ private static final class SplitLocalOutputs<T> {
private final WatermarkGeneratorSupplier<T> watermarksFactory;
private final TimestampsAndWatermarksContextProvider watermarksContextProvider;
private final WatermarkUpdateListener watermarkUpdateListener;
private final Clock clock;
private final TaskIOMetricGroup taskIOMetricGroup;
private final PausableRelativeClock mainInputActivityClock;

private SplitLocalOutputs(
PushingAsyncDataInput.DataOutput<T> recordOutput,
Expand All @@ -247,16 +243,14 @@ private SplitLocalOutputs(
TimestampAssigner<T> timestampAssigner,
WatermarkGeneratorSupplier<T> watermarksFactory,
TimestampsAndWatermarksContextProvider watermarksContextProvider,
Clock clock,
TaskIOMetricGroup taskIOMetricGroup) {
PausableRelativeClock mainInputActivityClock) {

this.recordOutput = recordOutput;
this.timestampAssigner = timestampAssigner;
this.watermarksFactory = watermarksFactory;
this.watermarksContextProvider = watermarksContextProvider;
this.watermarkUpdateListener = watermarkUpdateListener;
this.clock = clock;
this.taskIOMetricGroup = taskIOMetricGroup;
this.mainInputActivityClock = mainInputActivityClock;

this.watermarkMultiplexer = new WatermarkOutputMultiplexer(watermarkOutput);
this.localOutputs =
Expand Down Expand Up @@ -303,21 +297,20 @@ public void onIdleUpdate(boolean idle) {
}

private PausableRelativeClock createInputActivityClock(String splitId) {
// Dedicated inputActivityClock for a particular split. It will be paused both in case
// of back pressure and when split is paused due to watermark alignment.
PausableRelativeClock inputActivityClock = new PausableRelativeClock(clock);
// Dedicated inputActivityClock for a particular split, layered on the main input
// activity clock: it is paused whenever the main clock is (backpressure, downstream
// processing) and additionally when this split is paused due to watermark alignment.
PausableRelativeClock inputActivityClock =
new PausableRelativeClock(mainInputActivityClock);
inputActivityClocks.put(splitId, inputActivityClock);
taskIOMetricGroup.registerBackPressureListener(inputActivityClock);
return inputActivityClock;
}

void releaseOutputForSplit(String splitId) {
watermarkUpdateListener.splitFinished(splitId);
localOutputs.remove(splitId);
watermarkMultiplexer.unregisterOutput(splitId);
PausableRelativeClock inputActivityClock =
requireNonNull(inputActivityClocks.remove(splitId));
taskIOMetricGroup.unregisterBackPressureListener(inputActivityClock);
requireNonNull(inputActivityClocks.remove(splitId));
}

void emitPeriodicWatermark() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,9 @@
import org.apache.flink.api.common.eventtime.WatermarkStrategy;
import org.apache.flink.api.connector.source.ReaderOutput;
import org.apache.flink.metrics.MetricGroup;
import org.apache.flink.runtime.metrics.groups.TaskIOMetricGroup;
import org.apache.flink.streaming.api.operators.util.PausableRelativeClock;
import org.apache.flink.streaming.runtime.io.PushingAsyncDataInput;
import org.apache.flink.streaming.runtime.tasks.ProcessingTimeService;
import org.apache.flink.util.clock.Clock;
import org.apache.flink.util.clock.RelativeClock;

import java.time.Duration;
Expand Down Expand Up @@ -104,9 +103,7 @@ static <E> TimestampsAndWatermarks<E> createProgressiveEventTimeLogic(
MetricGroup metrics,
ProcessingTimeService timeService,
long periodicWatermarkIntervalMillis,
RelativeClock mainInputActivityClock,
Clock clock,
TaskIOMetricGroup taskIOMetricGroup) {
PausableRelativeClock mainInputActivityClock) {

TimestampsAndWatermarksContextProvider contextProvider =
new TimestampsAndWatermarksContextProvider(metrics);
Expand All @@ -120,9 +117,7 @@ static <E> TimestampsAndWatermarks<E> createProgressiveEventTimeLogic(
contextProvider,
timeService,
Duration.ofMillis(periodicWatermarkIntervalMillis),
mainInputActivityClock,
clock,
taskIOMetricGroup);
mainInputActivityClock);
}

static <E> TimestampsAndWatermarks<E> createNoOpEventTimeLogic(
Expand All @@ -142,12 +137,30 @@ static <E> TimestampsAndWatermarks<E> createNoOpEventTimeLogic(
class TimestampsAndWatermarksContextProvider {
private final MetricGroup metrics;

/** Whether any created context handed out the input activity clock. */
private boolean inputActivityClockRequested;

public TimestampsAndWatermarksContextProvider(MetricGroup metrics) {
this.metrics = metrics;
}

public TimestampsAndWatermarksContext create(RelativeClock inputActivityClock) {
return new TimestampsAndWatermarksContext(metrics, inputActivityClock);
return new TimestampsAndWatermarksContext(
metrics, inputActivityClock, () -> inputActivityClockRequested = true);
}

/**
* Returns true if a timestamp assigner or watermark generator created through this provider
* asked for the input activity clock, which is the case with {@link
* org.apache.flink.api.common.eventtime.WatermarkStrategy#withIdleness(java.time.Duration)}.
* Only then is it worth paying for keeping that clock accurate.
*
* <p>The decision to hide downstream processing time from the clock is taken once, when the
* main output is created. A supplier that asks for the clock lazily, after its first {@code
* createWatermarkGenerator} call returned, therefore keeps the previous behaviour.
*/
public boolean isInputActivityClockRequested() {
return inputActivityClockRequested;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -35,12 +35,27 @@ public final class TimestampsAndWatermarksContext
implements TimestampAssignerSupplier.Context, WatermarkGeneratorSupplier.Context {

private final MetricGroup metricGroup;

private final RelativeClock inputActivityClock;

private final Runnable onInputActivityClockRequested;

public TimestampsAndWatermarksContext(
MetricGroup metricGroup, RelativeClock inputActivityClock) {
this(metricGroup, inputActivityClock, () -> {});
}

/**
* @param onInputActivityClockRequested invoked every time {@link #getInputActivityClock()} is
* called, so that the owner can tell whether anything measures input activity time.
*/
public TimestampsAndWatermarksContext(
MetricGroup metricGroup,
RelativeClock inputActivityClock,
Runnable onInputActivityClockRequested) {
this.metricGroup = checkNotNull(metricGroup);
this.inputActivityClock = inputActivityClock;
this.onInputActivityClockRequested = checkNotNull(onInputActivityClockRequested);
}

@Override
Expand All @@ -50,6 +65,7 @@ public MetricGroup getMetricGroup() {

@Override
public RelativeClock getInputActivityClock() {
onInputActivityClockRequested.run();
return inputActivityClock;
}
}
Loading