3131 * watermark at any time is '({@code Min(now(), Max(event timestamp so far)) - max delay})'.
3232 * However, watermark is never set in future and capped to 'now - max delay'. In addition, watermark
3333 * advanced to 'now - max delay' when a partition is idle.
34+ *
35+ * <p>By default the idle advance requires at least one record to have been read, so a partition
36+ * which is caught up but has delivered nothing since the job started holds its watermark at {@link
37+ * BoundedWindow#TIMESTAMP_MIN_VALUE} until its first record arrives. See {@link
38+ * #CustomTimestampPolicyWithLimitedDelay(SerializableFunction, Duration, Optional, boolean)} to opt
39+ * out of that.
3440 */
3541public class CustomTimestampPolicyWithLimitedDelay <K , V > extends TimestampPolicy <K , V > {
3642
3743 private final Duration maxDelay ;
3844 private final SerializableFunction <KafkaRecord <K , V >, Instant > timestampFunction ;
45+ private final boolean advanceWatermarkBeforeFirstRecord ;
3946 private Instant maxEventTimestamp ;
4047
4148 /**
@@ -53,8 +60,28 @@ public CustomTimestampPolicyWithLimitedDelay(
5360 SerializableFunction <KafkaRecord <K , V >, Instant > timestampFunction ,
5461 Duration maxDelay ,
5562 Optional <Instant > previousWatermark ) {
63+ this (timestampFunction , maxDelay , previousWatermark , false );
64+ }
65+
66+ /**
67+ * As {@link #CustomTimestampPolicyWithLimitedDelay(SerializableFunction, Duration, Optional)},
68+ * with control over whether an idle partition may advance its watermark before it has delivered
69+ * any record.
70+ *
71+ * @param advanceWatermarkBeforeFirstRecord When true, a partition with a backlog of zero advances
72+ * its watermark whether or not a record has been read. A zero backlog means the reader is at
73+ * the log end, so no unread record can arrive late. Leaving this false keeps such a partition
74+ * pinned at {@link BoundedWindow#TIMESTAMP_MIN_VALUE} until its first record arrives, which
75+ * holds back the watermark of every stage reading it.
76+ */
77+ public CustomTimestampPolicyWithLimitedDelay (
78+ SerializableFunction <KafkaRecord <K , V >, Instant > timestampFunction ,
79+ Duration maxDelay ,
80+ Optional <Instant > previousWatermark ,
81+ boolean advanceWatermarkBeforeFirstRecord ) {
5682 this .maxDelay = maxDelay ;
5783 this .timestampFunction = timestampFunction ;
84+ this .advanceWatermarkBeforeFirstRecord = advanceWatermarkBeforeFirstRecord ;
5885
5986 // 'previousWatermark' is not the same as maxEventTimestamp (e.g. it could have been in future).
6087 // Initialize it such that watermark before reading any event same as previousWatermark.
@@ -90,7 +117,8 @@ Instant getWatermark(PartitionContext ctx, Instant now) {
90117 return now .minus (maxDelay ); // (a) above.
91118 } else if (ctx .getMessageBacklog () == 0
92119 && ctx .getBacklogCheckTime ().minus (maxDelay ).isAfter (maxEventTimestamp ) // Idle
93- && maxEventTimestamp .getMillis () > 0 ) { // Read at least one record with positive timestamp.
120+ // Read at least one record with positive timestamp, unless opted out of.
121+ && (maxEventTimestamp .getMillis () > 0 || advanceWatermarkBeforeFirstRecord )) {
94122 return ctx .getBacklogCheckTime ().minus (maxDelay );
95123 } else {
96124 return maxEventTimestamp .minus (maxDelay );
0 commit comments