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

@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

Comment thread flink-core/src/main/java/org/apache/flink/configuration/CheckpointingOptions.java Outdated
@github-actions github-actions Bot added the community-reviewed PR has been reviewed by the community. label Jul 16, 2026
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.

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.

Yes, this is doesn't work if there is more than one timer service - which is rare. Only I think Fabian's new operator is using two timer services?

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.

After giving it the second thought, instead of starting from the same timer service always, I will implement some round robin here. One interruption will only handle one service, without advancing the minReachedWatermark, but every interruption will be alternating between different timer services and subsequently advancing the minReachedWatermark across multiple calls. Given that the interruptions will happen every ~200ms (default auto watermarking interval), that should be perfectly fine. WDYT?

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.

Yes, this is doesn't work if there is more than one timer service - which is rare. Only I think Fabian's new operator is using two timer services?

AbstractProcessTableOperator for PTFs?

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.

Oh, and can we stop the iteration ince fullyAdvanced is false?

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.

@rkhachatryan rkhachatryan left a comment

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.

Thanks for updating the PR, LGTM.

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);
}
}

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.

Yes, this is doesn't work if there is more than one timer service - which is rare. Only I think Fabian's new operator is using two timer services?

AbstractProcessTableOperator for PTFs?

@rkhachatryan
rkhachatryan self-requested a review July 17, 2026 21:22
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