18
18
19
19
package org .apache .flink .table .runtime .operators .wmassigners ;
20
20
21
+ import org .apache .flink .annotation .VisibleForTesting ;
21
22
import org .apache .flink .api .common .functions .DefaultOpenContext ;
22
23
import org .apache .flink .api .common .functions .util .FunctionUtils ;
23
24
import org .apache .flink .streaming .api .operators .AbstractStreamOperator ;
30
31
import org .apache .flink .table .data .RowData ;
31
32
import org .apache .flink .table .runtime .generated .WatermarkGenerator ;
32
33
34
+ import static org .apache .flink .util .Preconditions .checkArgument ;
33
35
import static org .apache .flink .util .Preconditions .checkNotNull ;
34
36
35
37
/**
@@ -52,12 +54,22 @@ public class WatermarkAssignerOperator extends AbstractStreamOperator<RowData>
52
54
53
55
private transient long watermarkInterval ;
54
56
57
+ private transient long timerInterval ;
58
+
55
59
private transient long currentWatermark ;
56
60
57
- private transient long lastRecordTime ;
61
+ // Last time watermark have been (periodically) emitted
62
+ private transient long lastWatermarkPeriodicEmitTime ;
63
+
64
+ // Last time idleness status has been checked
65
+ private transient long timeSinceLastIdleCheck ;
58
66
59
67
private transient WatermarkStatus currentStatus = WatermarkStatus .ACTIVE ;
60
68
69
+ private transient long processedElements ;
70
+
71
+ private transient long lastIdleCheckProcessedElements = -1 ;
72
+
61
73
/**
62
74
* Create a watermark assigner operator.
63
75
*
@@ -87,11 +99,14 @@ public void open() throws Exception {
87
99
// watermark and timestamp should start from 0
88
100
this .currentWatermark = 0 ;
89
101
this .watermarkInterval = getExecutionConfig ().getAutoWatermarkInterval ();
90
- this .lastRecordTime = getProcessingTimeService ().getCurrentProcessingTime ();
102
+ long now = getProcessingTimeService ().getCurrentProcessingTime ();
103
+ this .lastWatermarkPeriodicEmitTime = now ;
104
+ this .timeSinceLastIdleCheck = now ;
91
105
92
- if (watermarkInterval > 0 ) {
93
- long now = getProcessingTimeService ().getCurrentProcessingTime ();
94
- getProcessingTimeService ().registerTimer (now + watermarkInterval , this );
106
+ if (watermarkInterval > 0 || idleTimeout > 0 ) {
107
+ this .timerInterval =
108
+ calculateProcessingTimeTimerInterval (watermarkInterval , idleTimeout );
109
+ getProcessingTimeService ().registerTimer (now + timerInterval , this );
95
110
}
96
111
97
112
FunctionUtils .setFunctionRuntimeContext (watermarkGenerator , getRuntimeContext ());
@@ -100,12 +115,11 @@ public void open() throws Exception {
100
115
101
116
@ Override
102
117
public void processElement (StreamRecord <RowData > element ) throws Exception {
103
- if (idleTimeout > 0 ) {
104
- if (currentStatus .equals (WatermarkStatus .IDLE )) {
105
- // mark the channel active
106
- emitWatermarkStatus (WatermarkStatus .ACTIVE );
107
- }
108
- lastRecordTime = getProcessingTimeService ().getCurrentProcessingTime ();
118
+ processedElements ++;
119
+
120
+ if (isIdlenessEnabled () && currentStatus .equals (WatermarkStatus .IDLE )) {
121
+ // mark the channel active
122
+ emitWatermarkStatus (WatermarkStatus .ACTIVE );
109
123
}
110
124
111
125
RowData row = element .getValue ();
@@ -139,19 +153,28 @@ private void advanceWatermark() {
139
153
140
154
@ Override
141
155
public void onProcessingTime (long timestamp ) throws Exception {
142
- advanceWatermark ();
156
+ // timestamp and now can be off in case TM is heavily overloaded.
157
+ long now = getProcessingTimeService ().getCurrentProcessingTime ();
143
158
144
- if (idleTimeout > 0 && currentStatus .equals (WatermarkStatus .ACTIVE )) {
145
- final long currentTime = getProcessingTimeService ().getCurrentProcessingTime ();
146
- if (currentTime - lastRecordTime > idleTimeout ) {
147
- // mark the channel as idle to ignore watermarks from this channel
148
- emitWatermarkStatus (WatermarkStatus .IDLE );
149
- }
159
+ if (watermarkInterval > 0 && lastWatermarkPeriodicEmitTime + watermarkInterval <= now ) {
160
+ lastWatermarkPeriodicEmitTime = now ;
161
+ advanceWatermark ();
162
+ }
163
+
164
+ if (processedElements != lastIdleCheckProcessedElements ) {
165
+ timeSinceLastIdleCheck = now ;
166
+ lastIdleCheckProcessedElements = processedElements ;
167
+ }
168
+
169
+ if (isIdlenessEnabled ()
170
+ && currentStatus .equals (WatermarkStatus .ACTIVE )
171
+ && timeSinceLastIdleCheck + idleTimeout <= now ) {
172
+ // mark the channel as idle to ignore watermarks from this channel
173
+ emitWatermarkStatus (WatermarkStatus .IDLE );
150
174
}
151
175
152
176
// register next timer
153
- long now = getProcessingTimeService ().getCurrentProcessingTime ();
154
- getProcessingTimeService ().registerTimer (now + watermarkInterval , this );
177
+ getProcessingTimeService ().registerTimer (now + timerInterval , this );
155
178
}
156
179
157
180
/**
@@ -163,7 +186,7 @@ public void processWatermark(Watermark mark) throws Exception {
163
186
// if we receive a Long.MAX_VALUE watermark we forward it since it is used
164
187
// to signal the end of input and to not block watermark progress downstream
165
188
if (mark .getTimestamp () == Long .MAX_VALUE && currentWatermark != Long .MAX_VALUE ) {
166
- if (idleTimeout > 0 && currentStatus .equals (WatermarkStatus .IDLE )) {
189
+ if (isIdlenessEnabled () && currentStatus .equals (WatermarkStatus .IDLE )) {
167
190
// mark the channel active
168
191
emitWatermarkStatus (WatermarkStatus .ACTIVE );
169
192
}
@@ -193,4 +216,36 @@ public void close() throws Exception {
193
216
FunctionUtils .closeFunction (watermarkGenerator );
194
217
super .close ();
195
218
}
219
+
220
+ private boolean isIdlenessEnabled () {
221
+ return idleTimeout > 0 ;
222
+ }
223
+
224
+ @ VisibleForTesting
225
+ static long calculateProcessingTimeTimerInterval (long watermarkInterval , long idleTimeout ) {
226
+ checkArgument (watermarkInterval > 0 || idleTimeout > 0 );
227
+ if (watermarkInterval <= 0 ) {
228
+ return idleTimeout ;
229
+ }
230
+ if (idleTimeout <= 0 ) {
231
+ return watermarkInterval ;
232
+ }
233
+
234
+ long smallerInterval = Math .min (watermarkInterval , idleTimeout );
235
+ long largerInterval = Math .max (watermarkInterval , idleTimeout );
236
+
237
+ // If one of the intervals is 5x smaller, just pick the smaller one. The firing interval
238
+ // for the smaller one this way will be perfectly accurate, while for the larger one it will
239
+ // be good enough™. For example one timer is every 2s the other every 11s, the 2nd timer
240
+ // will be effectively checked every 12s, which is an acceptable accuracy.
241
+ long timerInterval ;
242
+ if (smallerInterval * 5 < largerInterval ) {
243
+ timerInterval = smallerInterval ;
244
+ } else {
245
+ // Otherwise, just pick an interval 5x smaller than the smaller interval. Again accuracy
246
+ // will be good enough™.
247
+ timerInterval = smallerInterval / 5 ;
248
+ }
249
+ return Math .max (timerInterval , 1 );
250
+ }
196
251
}
0 commit comments