Skip to content

[FLINK-40101][runtime] Emit intermediate watermarks while firing timers#28752

Open
pnowojski wants to merge 2 commits into
apache:masterfrom
pnowojski:f40101
Open

[FLINK-40101][runtime] Emit intermediate watermarks while firing timers#28752
pnowojski wants to merge 2 commits into
apache:masterfrom
pnowojski:f40101

Conversation

@pnowojski

Copy link
Copy Markdown
Contributor

With unaligned checkpoints + interruptible timers, an operator's output watermark could stall for hours (surviving restarts) because it only advances once an entire due-timer backlog drains in one uninterrupted pass — a large backlog (e.g. after a rescale) can outlast every single attempt.

InternalTimerServiceImpl/InternalTimeServiceManagerImpl now track the highest watermark known to be fully fired even when interrupted partway, and MailboxWatermarkProcessor emits that as an intermediate watermark instead of withholding all progress. This progress lives in a new field, not currentWatermark, since currentWatermark's eager semantics are relied on elsewhere (WindowOperator cleanup timers, user ProcessFunctions). Emission is paced by a configurable interval (default 5s, 0 disables) via an internal no-op processing-time nudge, avoiding per-timer clock checks.

Verifying this change

Added new unit tests and is covered by various existing unit/IT tests.

Does this pull request potentially affect one of the following parts:

  • Dependencies (does it add or upgrade a dependency): (yes / no)
  • The public API, i.e., is any changed class annotated with @Public(Evolving): (yes / no)
  • The serializers: (yes / no / don't know)
  • The runtime per-record code paths (performance sensitive): (yes / no / don't know)
  • Anything that affects deployment or recovery: JobManager (and its components), Checkpointing, Kubernetes/Yarn, ZooKeeper: (yes / no / don't know)
  • The S3 file system connector: (yes / no / don't know)

Documentation

  • Does this pull request introduce a new feature? (yes / no)
  • If yes, how is the feature documented? (not applicable / docs / JavaDocs / not documented)

Was generative AI tooling used to co-author this PR?

Yes, claude 5.0

With unaligned checkpoints + interruptible timers, an operator's output
watermark could stall for hours (surviving restarts) because it only
advances once an entire due-timer backlog drains in one uninterrupted
pass — a large backlog (e.g. after a rescale) can outlast every single
attempt.

InternalTimerServiceImpl/InternalTimeServiceManagerImpl now track the
highest watermark known to be fully fired even when interrupted
partway, and MailboxWatermarkProcessor emits that as an intermediate
watermark instead of withholding all progress. This progress lives in
a new field, not currentWatermark, since currentWatermark's eager
semantics are relied on elsewhere (WindowOperator cleanup timers, user
ProcessFunctions). Emission is paced by a configurable interval
(default 5s, 0 disables) via an internal no-op processing-time nudge,
avoiding per-timer clock checks.
@flinkbot

flinkbot commented Jul 15, 2026

Copy link
Copy Markdown
Collaborator

CI report:

Bot commands The @flinkbot bot supports the following commands:
  • @flinkbot run azure re-run the last Azure build

ConfigOptions.key(
"execution.checkpointing.unaligned.interruptible-timers.intermediate-watermark-interval")
.durationType()
.defaultValue(Duration.ofSeconds(5))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't 5s a long wait for WM progress?
being X25 pipeline.auto-watermark-interval default

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤔 Maybe... maybe this should be changed to a boolean flag, and the interval should taken directly from pipeline.auto-watermark-interval?

I've picked 5s default here, because anything > 1s doesn't matter for performance. 200ms would have marginal effect if any, hard to say without benchmarking. However indeed it seems as if re-using here pipeline.auto-watermark-interval makes sense, as it has almsot the same performance implications vs trying to keep up with real time?

Previously I've thought this features matter only if there is a huge backpressure, and huge watermark stall. However if we have a lot of fine grained timers to fire, smaller intervals here can smooth out workload for downstream operators. For example if job is generally speaking keeping up with the pending records, but there is an occasional couple of seconds hiccup when a lot of timers fire.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've implemented the change to use auto-watermark-interval - please check fixup commit.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd keep the .intermediate-watermark-interval and only make it equal to pipeline.auto-watermark-interval by default
But either way works

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think reducing the amount of knobs is a good idea here.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Future us would appreciate this

@github-actions github-actions Bot added the community-reviewed PR has been reviewed by the community. label Jul 16, 2026
ConfigOptions.key(
"execution.checkpointing.unaligned.interruptible-timers.emit-intermediate-watermarks")
.booleanType()
.defaultValue(true)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this should be disabled by default for 1 release to minimize potential impact.

Comment on lines 233 to 242
for (InternalTimerServiceImpl<?, ?> service : timerServices.values()) {
if (!service.tryAdvanceWatermark(watermark.getTimestamp(), shouldStopAdvancingFn)) {
return false;
// Once one service is interrupted, stop attempting to fire on the remaining ones this
// round, but still fold their (possibly stale, from an earlier round) reachedWatermark
// into the min below: a service we don't retry this round may be even further behind.
if (fullyAdvanced) {
fullyAdvanced =
service.tryAdvanceWatermark(
watermark.getTimestamp(), shouldStopAdvancingFn);
}
}

@rkhachatryan rkhachatryan Jul 16, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry but I don't understand this loop :)

First, once fullyAdvance is false, we still iterate, but just do nothing? Why not just break?

Second, if the iteration order is the same next round (likely) then we won't update min wm if there are more services with lower wm after the interrupted one.
I think in practice this renders the feature as no-op with high probability.

Comment on lines +400 to +407
if (getContainingTask()
.getJobConfiguration()
.get(
CheckpointingOptions
.UNALIGNED_INTERRUPTIBLE_TIMERS_EMIT_INTERMEDIATE_WATERMARKS)) {
timeServiceManager.configureIntermediateWatermarkInterval(
Duration.ofMillis(getExecutionConfig().getAutoWatermarkInterval()));
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about V2?

if (reachedWatermark > lastEmittedIntermediateWatermark) {
// Firing was interrupted before completing; surface the progress made so far instead
// of leaving watermark advancement stalled until the whole drain finishes.
lastEmittedIntermediateWatermark = reachedWatermark;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we also update lastEmittedIntermediateWatermark in the short-cut branch above?

// Firing was interrupted before completing; surface the progress made so far instead
// of leaving watermark advancement stalled until the whole drain finishes.
lastEmittedIntermediateWatermark = reachedWatermark;
output.emitWatermark(new Watermark(reachedWatermark));

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we honor the config option here?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

community-reviewed PR has been reviewed by the community.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants