udayaw opened a new pull request, #40176:
URL: https://github.com/apache/beam/pull/40176

   Might be related to #20908
   
   ## Problem
   
   `CustomTimestampPolicyWithLimitedDelay.getWatermark` advances an idle 
partition
   only when three conditions hold:
   
   | gate | condition |
   | --- | --- |
   | 1 | `ctx.getMessageBacklog() == 0` |
   | 2 | `ctx.getBacklogCheckTime().minus(maxDelay).isAfter(maxEventTimestamp)` 
|
   | 3 | `maxEventTimestamp.getMillis() > 0` — *"Read at least one record with 
a positive timestamp."* |
   
   `maxEventTimestamp` is initialised to
   `previousWatermark.orElse(TIMESTAMP_MIN_VALUE).plus(maxDelay)` and is **only 
ever
   raised by `getTimestampForRecord`**, which runs once per delivered record.
   
   So on a partition which delivers nothing after the job starts:
   
   ```
   maxEventTimestamp = TIMESTAMP_MIN_VALUE + maxDelay   // still hugely 
negative, gate 3 fails
   getWatermark()    → maxEventTimestamp.minus(maxDelay)
                     = TIMESTAMP_MIN_VALUE              // the +maxDelay and 
-maxDelay cancel
   ```
   
   which is why affected partitions report *exactly* 
`-290308-12-21T19:59:05.225Z`
   rather than a value `maxDelay` away from it. Because a stage's watermark is 
the
   minimum over its partitions, one such partition pins the whole stage at the
   floor, for the life of the job or until that partition's first record 
arrives.
   
   **The asymmetry is the defect.** `getWatermark` is driven by the backlog 
check —
   a timer, roughly every 2s — and is therefore independent of data. Gate 3 
makes
   that data-independent report conditional on a variable only data can move, 
so a
   partition with no data holds a live reporting path and a dead source for the
   value it reports.
   
   **Two conditions have to coincide, and they do different work.** *Never
   delivered* is what leaves `maxEventTimestamp` negative. *Caught up* is what 
makes
   blocking wrong — had the partition not been caught up, holding at the floor 
would
   be defensible, since there is then unread data whose timestamps are unknown.
   
   **So it is launch-dependent, not purely producer-dependent.** A partition is
   affected when it has zero backlog at launch (a previously drained pipeline 
had
   fully ingested it, so it resumes at the log end) *and* receives nothing 
after.
   Two jobs launched minutes apart against the same topic get different affected
   sets.
   
   **And it is sticky rather than recurring.** `createTimestampPolicy` is 
called per
   bundle, so policy instances are rebuilt continuously, but `previousWatermark`
   carries the state across — a healthy partition stays healthy and a pinned one
   stays pinned.
   
   ## Impact
   
   The consequence depends on how the pipeline triggers. Where windowing is on
   event time, a pinned partition means no window ever closes, so aggregations 
never
   fire and output is silently withheld while records accumulate in state — the
   symptom described in #20908. Where triggers are processing-time based, 
output is
   unaffected, but the reported watermark is meaningless: `TIMESTAMP_MIN_VALUE` 
is
   Beam's sentinel for "no watermark", so a runner's data-freshness metric 
cannot
   distinguish a caught-up stage from a stalled one, and monitoring keyed on it 
is
   blind to real lag.
   
   Either way one quiet partition is enough, and it stays pinned until its next
   record — which for config, lookup or CDC topics can be hours or days.
   
   
   ## Change
   
   **Gate 3 removed.** It is redundant with gate 1: at `getMessageBacklog() == 
0`
   the reader has a position and knows it is at the log end, so there is no 
unread
   data which could arrive late, whether or not a record has ever been seen. 
Having
   seen a record adds no safety once the backlog is zero.
   
   **A monotonicity clamp added.** `getWatermark` has two ways to answer — the 
idle
   path, `backlogCheckTime - maxDelay`, which is roughly now; and the fallback 
path,
   `maxEventTimestamp - maxDelay`, taken whenever the partition is not caught 
up.
   The idle path never writes `maxEventTimestamp`, so the two can disagree and 
the
   answer can move backwards:
   
   ```
   backlog 0, nothing read yet      → idle path     → now - maxDelay
   first record arrives, backlog 1  → fallback path → maxEventTimestamp - 
maxDelay
   ```
   
   Today gate 3 keeps that regression to seconds, because the idle path cannot 
run
   until `maxEventTimestamp` holds a real record timestamp. Without the gate
   `maxEventTimestamp` may still be the floor, so the watermark would collapse 
at
   the exact moment a quiet partition finally receives data. `getWatermark` now
   remembers the last value it returned and never returns anything earlier.
   
   Note the clamp matters most on the legacy read path, where
   `KafkaUnboundedReader.updateAndGetWatermark` assigns the policy's answer 
straight
   to `lastWatermark` with no max. On the SDF path a regression would be masked 
by
   `WatermarkEstimators.Manual`, which is already monotonic. Clamping in the 
policy
   makes the class correct on its own terms rather than relying on its caller.
   
   Watermark semantics are otherwise unchanged:
   `Min(now, maxEventTimestamp) - maxDelay`, advanced to
   `backlogCheckTime - maxDelay` while caught up.
   
   ## Validation
   
   The equivalent of this patch — the same two changes applied as a local 
subclass
   of the policy — was run on two long-running streaming jobs reading several
   hundred Kafka partitions each, instrumented to report per-partition watermark
   progress.
   
   Before: 95% and 87% of partitions pinned at launch, still 81% and 60% pinned 
47
   hours later, the count decaying only as partitions received their first
   post-launch record. After: zero pinned partitions on either job, held for 23
   consecutive hours including a full night — the quiet period the symptom 
requires.
   
   ## Tests
   
   - `testWatermarkAdvancesWhenIdleWithoutAnyRecord` — a caught-up partition 
which
     has delivered no record advances to `backlogCheckTime - maxDelay` instead 
of
     returning `TIMESTAMP_MIN_VALUE`. Fails before this change.
   - `testWatermarkDoesNotRegressWhenFirstRecordArrives` — after advancing while
     idle, a first record carrying a much older timestamp does not drag the
     watermark back. Fails without the clamp.
   - `testCustomTimestampPolicyWithLimitedDelay` — unchanged and still passing,
     including its `TIMESTAMP_MIN_VALUE` assertion, which holds a non-zero 
backlog
     and so exercises the fallback path.
   
   ------------------------
   
   Thank you for your contribution! Follow this checklist to help us 
incorporate your contribution quickly and easily:
   
    - [ ] Mention the appropriate issue in your description (for example: 
`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, comment `fixes #<ISSUE NUMBER>` instead.
    - [ ] Update `CHANGES.md` with noteworthy changes.
    - [ ] If this contribution is large, please file an Apache [Individual 
Contributor License Agreement](https://www.apache.org/licenses/icla.pdf).
   
   See the [Contributor Guide](https://beam.apache.org/contribute) for more 
tips on [how to make review process 
smoother](https://github.com/apache/beam/blob/master/CONTRIBUTING.md#make-the-reviewers-job-easier).
   
   To check the build health, please visit 
[https://github.com/apache/beam/blob/master/.test-infra/BUILD_STATUS.md](https://github.com/apache/beam/blob/master/.test-infra/BUILD_STATUS.md)
   
   GitHub Actions Tests Status (on master branch)
   
------------------------------------------------------------------------------------------------
   [![Build python source distribution and 
wheels](https://github.com/apache/beam/actions/workflows/build_wheels.yml/badge.svg?event=schedule&&?branch=master)](https://github.com/apache/beam/actions?query=workflow%3A%22Build+python+source+distribution+and+wheels%22+branch%3Amaster+event%3Aschedule)
   [![Python 
tests](https://github.com/apache/beam/actions/workflows/python_tests.yml/badge.svg?event=schedule&&?branch=master)](https://github.com/apache/beam/actions?query=workflow%3A%22Python+Tests%22+branch%3Amaster+event%3Aschedule)
   [![Java 
tests](https://github.com/apache/beam/actions/workflows/java_tests.yml/badge.svg?event=schedule&&?branch=master)](https://github.com/apache/beam/actions?query=workflow%3A%22Java+Tests%22+branch%3Amaster+event%3Aschedule)
   [![Go 
tests](https://github.com/apache/beam/actions/workflows/go_tests.yml/badge.svg?event=schedule&&?branch=master)](https://github.com/apache/beam/actions?query=workflow%3A%22Go+tests%22+branch%3Amaster+event%3Aschedule)
   
   See [CI.md](https://github.com/apache/beam/blob/master/CI.md) for more 
information about GitHub Actions CI or the [workflows 
README](https://github.com/apache/beam/blob/master/.github/workflows/README.md) 
to see a list of phrases to trigger workflows.
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to