Conversation
|
Assigning reviewers: R: @Abacn for label java. Note: If you would like to opt out of this review, comment Available commands:
The PR bot will only process comments in the main thread (not review comments). |
| } else if (ctx.getMessageBacklog() == 0 | ||
| && ctx.getBacklogCheckTime().minus(maxDelay).isAfter(maxEventTimestamp) // Idle | ||
| && maxEventTimestamp.getMillis() > 0) { // Read at least one record with positive timestamp. | ||
| && ctx.getBacklogCheckTime().minus(maxDelay).isAfter(maxEventTimestamp)) { // Idle | ||
| // A zero backlog means the reader has a position and knows it is at the log end, so no | ||
| // unread record can arrive late regardless of whether one has ever been read. Requiring a | ||
| // record to have been read here as well would pin a partition which is caught up but has | ||
| // delivered nothing since the job started at 'maxEventTimestamp - maxDelay' forever, because | ||
| // only a delivered record can advance 'maxEventTimestamp'. | ||
| return ctx.getBacklogCheckTime().minus(maxDelay); |
There was a problem hiding this comment.
Unconditionally changing this may break existing users.
If a new partition is added or an existing partition is cleared before running a pipeline with the intent being to produce records to the partition after the pipeline is running and healthy, then there's a good reason to hold the watermark at TIMESTAMP_MIN_VALUE.
I'd consider making the proposed change configurable or splitting it out to a separate class instead of changing it unconditionally.
There was a problem hiding this comment.
After looking at this some more it seems like the intent was to advance the watermark unconditionally according to this comment.
Still, this change will break users with an intentional or unintentional dependency on implemented instead of designed behavior. 😅
Note that ctx.getMessageBacklog() may return UnboundedReader.BACKLOG_UNKNOWN.
The changes proposed in #39830 (port of ReadFromKafkaDoFn changes in #39285 to KafkaUnboundedReader) should make it less likely that the position gets ahead of the end offset (assuming that currentLag() is generally present after polling), because the end offset is no longer fetched by separate consumers and threads.
I'm wondering if it makes sense for this policy to also advance the watermark to backlog check time (last succeeded backlog check time?) when ctx.getMessageBacklog() <= 0 after the event time has advanced past BoundedWindow.TIMESTAMP_MIN_VALUE.
There was a problem hiding this comment.
Hi @sjvanrossum , thanks for taking a look 🙇♂️ .
Pushed an update. It's opt-in constructor flag, default off, so nothing
changes for existing users. That should cover the "might break users" worry.
I also dropped the monotonicity clamp, which is where both your code suggestions
were, sorry about that. The reason: the watermark can already go backwards today.
Advance while idle, then a record arrives and you fall back to
maxEventTimestamp - maxDelay, which can be well behind the idle value. That's
existing behaviour with the gate in place, so fixing it felt like a separate
thing. The diff is now just the gate condition plus a constructor overload.
On BACKLOG_UNKNOWN: -1 fails == 0, so the watermark just holds. Safe, and
this PR doesn't change it.
On advancing at <= 0 — I'd keep that as a separate issue, since
BACKLOG_UNKNOWN means "I don't know if I'm caught up", which isn't the same as
== 0.
However, right now the flag is only reachable by building the policy yourself and
passing it through withTimestampPolicyFactory. I left it off from KafkaIO.Read to
keep this special behaviour away from default use cases.
Might be related to #20908
Problem
CustomTimestampPolicyWithLimitedDelay.getWatermarkonly advances an idlepartition when three things are true:
ctx.getMessageBacklog() == 0ctx.getBacklogCheckTime().minus(maxDelay).isAfter(maxEventTimestamp)maxEventTimestamp.getMillis() > 0— "Read at least one record with a positive timestamp."maxEventTimestampstarts atpreviousWatermark.orElse(TIMESTAMP_MIN_VALUE).plus(maxDelay),and only
getTimestampForRecordever raises it. So if a partition is caught upbut hasn't delivered anything since the job started, check 3 never passes:
It stays there until that partition's first record shows up. A stage takes the
minimum watermark across its partitions, so one quiet partition holds back the
whole stage.
It's also sticky. The policy is rebuilt every bundle, but
previousWatermarkcarries the floor value over, so a pinned partition stays pinned.
Two things have to line up: the partition is caught up, and it hasn't delivered
a record. Being caught up is what makes holding wrong — if there were unread
data, holding at the floor would make sense, since you don't know what
timestamps are coming.
Impact
Depends on the pipeline. With event-time windows nothing closes, so aggregations
don't fire and output never comes out — the symptom in #20908. With
processing-time triggers output is fine, but the watermark is meaningless:
TIMESTAMP_MIN_VALUEjust means "no watermark yet", so data freshness can't tella caught-up stage from a stuck one.
We hit this on Dataflow, with a lot of partitions pinned from launch. The jobs
showed data freshness climbing with the wall clock, but only where at least one
partition of a topic was still getting records. If every partition of a topic was
pinned to the
BoundedWindow.TIMESTAMP_MIN_VALUE, no chart showed at all. So the metric never pointed at the real problem —it took per-partition logging to find.
Change
A new constructor taking
advanceWatermarkBeforeFirstRecord. When true, check 3is skipped, so a caught-up partition advances whether or not it has read
anything. A zero backlog means the reader is at the log end, so there's no unread
record that could turn up late.
Default is false, and the existing constructor passes false, so nothing changes
unless you ask for it.
Tests
testIdleWatermarkIsPinnedBeforeFirstRecordByDefault— default still returnsTIMESTAMP_MIN_VALUE.testIdleWatermarkAdvancesBeforeFirstRecordWhenEnabled— with the flag itadvances to
backlogCheckTime - maxDelay.testCustomTimestampPolicyWithLimitedDelay— unchanged, still passes.Thank you for your contribution! Follow this checklist to help us incorporate your contribution quickly and easily:
addresses #123), if applicable. This will automatically add a link to the pull request in the issue. If you would like the issue to automatically close on merging the pull request, commentfixes #<ISSUE NUMBER>instead.CHANGES.mdwith noteworthy changes.See the Contributor Guide for more tips on how to make review process smoother.
To check the build health, please visit https://github.com/apache/beam/blob/master/.test-infra/BUILD_STATUS.md
GitHub Actions Tests Status (on master branch)
See CI.md for more information about GitHub Actions CI or the workflows README to see a list of phrases to trigger workflows.