1use chrono::{DateTime, Duration as ChronoDuration, Utc};
34use dashmap::DashMap;
35use serde::{Deserialize, Serialize};
36use std::cmp::max;
37use std::collections::HashMap;
38use std::sync::{Arc, Mutex};
39use std::time::Duration;
40use tracing::{debug, trace, warn};
41
42#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
48pub struct Watermark {
49 pub timestamp: i64,
51}
52
53impl Watermark {
54 pub fn new(timestamp: i64) -> Self {
56 Self { timestamp }
57 }
58
59 pub fn from_datetime(dt: DateTime<Utc>) -> Self {
61 Self {
62 timestamp: dt.timestamp_millis(),
63 }
64 }
65
66 pub fn to_datetime(&self) -> DateTime<Utc> {
68 DateTime::from_timestamp_millis(self.timestamp)
69 .unwrap_or_else(|| Utc::now())
70 }
71
72 pub fn min() -> Self {
74 Self { timestamp: i64::MIN }
75 }
76
77 pub fn max() -> Self {
79 Self { timestamp: i64::MAX }
80 }
81
82 pub fn is_before(&self, timestamp: i64) -> bool {
84 self.timestamp < timestamp
85 }
86
87 pub fn is_after(&self, timestamp: i64) -> bool {
89 self.timestamp > timestamp
90 }
91
92 pub fn is_min(&self) -> bool {
94 self.timestamp == i64::MIN
95 }
96
97 pub fn is_max(&self) -> bool {
99 self.timestamp == i64::MAX
100 }
101}
102
103impl Default for Watermark {
104 fn default() -> Self {
105 Self::min()
106 }
107}
108
109impl std::fmt::Display for Watermark {
110 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
111 write!(f, "Watermark({})", self.to_datetime())
112 }
113}
114
115pub trait WatermarkGenerator: Send + Sync {
117 fn on_event(&mut self, timestamp: i64, partition: u32) -> Option<Watermark>;
126
127 fn on_periodic_check(&mut self) -> Option<Watermark>;
132
133 fn current_watermark(&self) -> Watermark;
135
136 fn reset(&mut self);
138
139 fn partition_watermark(&self, partition: u32) -> Option<Watermark>;
141}
142
143#[derive(Debug, Clone, Serialize, Deserialize)]
145pub struct BoundedOutOfOrdernessConfig {
146 pub max_out_of_orderness: Duration,
148 pub idle_timeout: Option<Duration>,
150 pub emit_per_event: bool,
152}
153
154impl Default for BoundedOutOfOrdernessConfig {
155 fn default() -> Self {
156 Self {
157 max_out_of_orderness: Duration::from_secs(5),
158 idle_timeout: Some(Duration::from_secs(60)),
159 emit_per_event: false,
160 }
161 }
162}
163
164pub struct BoundedOutOfOrdernessWatermark {
188 max_timestamp: i64,
190 partition_max_timestamps: DashMap<u32, i64>,
192 partition_last_activity: DashMap<u32, DateTime<Utc>>,
194 config: BoundedOutOfOrdernessConfig,
196 current_watermark: Arc<Mutex<Watermark>>,
198 partition_watermarks: DashMap<u32, Watermark>,
200}
201
202impl BoundedOutOfOrdernessWatermark {
203 pub fn new(max_out_of_orderness: Duration, idle_timeout: Option<Duration>) -> Self {
209 Self {
210 max_timestamp: i64::MIN,
211 partition_max_timestamps: DashMap::new(),
212 partition_last_activity: DashMap::new(),
213 config: BoundedOutOfOrdernessConfig {
214 max_out_of_orderness,
215 idle_timeout,
216 emit_per_event: false,
217 },
218 current_watermark: Arc::new(Mutex::new(Watermark::min())),
219 partition_watermarks: DashMap::new(),
220 }
221 }
222
223 pub fn with_config(config: BoundedOutOfOrdernessConfig) -> Self {
225 Self {
226 max_timestamp: i64::MIN,
227 partition_max_timestamps: DashMap::new(),
228 partition_last_activity: DashMap::new(),
229 config,
230 current_watermark: Arc::new(Mutex::new(Watermark::min())),
231 partition_watermarks: DashMap::new(),
232 }
233 }
234
235 fn compute_watermark(&self, max_ts: i64) -> Watermark {
237 let delay_ms = self.config.max_out_of_orderness.as_millis() as i64;
238 let watermark_ts = max_ts.saturating_sub(delay_ms);
239 Watermark::new(watermark_ts)
240 }
241
242 fn update_partition_activity(&self, partition: u32) {
244 self.partition_last_activity.insert(partition, Utc::now());
245 }
246
247 fn is_partition_idle(&self, partition: u32) -> bool {
249 if let Some(timeout) = self.config.idle_timeout {
250 if let Some(last_activity) = self.partition_last_activity.get(&partition) {
251 let elapsed = Utc::now().signed_duration_since(*last_activity);
252 return elapsed > ChronoDuration::from_std(timeout).unwrap_or_default();
253 }
254 }
255 false
256 }
257
258 pub fn active_partitions(&self) -> Vec<u32> {
260 self.partition_max_timestamps
261 .iter()
262 .filter(|entry| !self.is_partition_idle(*entry.key()))
263 .map(|entry| *entry.key())
264 .collect()
265 }
266
267 pub fn idle_partitions(&self) -> Vec<u32> {
269 self.partition_max_timestamps
270 .iter()
271 .filter(|entry| self.is_partition_idle(*entry.key()))
272 .map(|entry| *entry.key())
273 .collect()
274 }
275
276 fn merge_partition_watermarks(&self) -> Option<Watermark> {
281 let active_partitions: Vec<_> = self.active_partitions();
282
283 if active_partitions.is_empty() {
284 return None;
285 }
286
287 let min_watermark = active_partitions
288 .iter()
289 .filter_map(|&partition| self.partition_watermarks.get(&partition).map(|w| *w))
290 .min()?;
291
292 Some(min_watermark)
293 }
294
295 pub fn is_late_event(&self, timestamp: i64) -> bool {
297 let current = self.current_watermark.lock().unwrap();
298 timestamp < current.timestamp
299 }
300
301 pub fn get_lateness(&self, timestamp: i64) -> i64 {
305 let current = self.current_watermark.lock().unwrap();
306 if timestamp < current.timestamp {
307 current.timestamp - timestamp
308 } else {
309 0
310 }
311 }
312}
313
314impl WatermarkGenerator for BoundedOutOfOrdernessWatermark {
315 fn on_event(&mut self, timestamp: i64, partition: u32) -> Option<Watermark> {
316 trace!(
317 timestamp = timestamp,
318 partition = partition,
319 "Processing event for watermark"
320 );
321
322 self.update_partition_activity(partition);
324
325 self.partition_max_timestamps
327 .entry(partition)
328 .and_modify(|max_ts| *max_ts = max(*max_ts, timestamp))
329 .or_insert(timestamp);
330
331 self.max_timestamp = max(self.max_timestamp, timestamp);
333
334 let partition_watermark = self.compute_watermark(timestamp);
336 self.partition_watermarks.insert(partition, partition_watermark);
337
338 if let Some(new_watermark) = self.merge_partition_watermarks() {
340 let mut current = self.current_watermark.lock().unwrap();
341 if new_watermark > *current {
342 *current = new_watermark;
343 debug!(
344 watermark = %new_watermark,
345 "Advanced watermark"
346 );
347 return if self.config.emit_per_event {
348 Some(new_watermark)
349 } else {
350 None
351 };
352 }
353 }
354
355 None
356 }
357
358 fn on_periodic_check(&mut self) -> Option<Watermark> {
359 let idle = self.idle_partitions();
361 if !idle.is_empty() {
362 warn!(partitions = ?idle, "Detected idle partitions");
363 }
364
365 if let Some(new_watermark) = self.merge_partition_watermarks() {
367 let mut current = self.current_watermark.lock().unwrap();
368 if new_watermark > *current {
369 *current = new_watermark;
370 debug!(watermark = %new_watermark, "Periodic watermark update");
371 return Some(new_watermark);
372 }
373 }
374
375 None
376 }
377
378 fn current_watermark(&self) -> Watermark {
379 *self.current_watermark.lock().unwrap()
380 }
381
382 fn reset(&mut self) {
383 self.max_timestamp = i64::MIN;
384 self.partition_max_timestamps.clear();
385 self.partition_last_activity.clear();
386 self.partition_watermarks.clear();
387 *self.current_watermark.lock().unwrap() = Watermark::min();
388 debug!("Watermark generator reset");
389 }
390
391 fn partition_watermark(&self, partition: u32) -> Option<Watermark> {
392 self.partition_watermarks.get(&partition).map(|w| *w)
393 }
394}
395
396#[derive(Debug, Clone, Serialize, Deserialize)]
398pub struct PeriodicWatermarkConfig {
399 pub interval: Duration,
401 pub max_out_of_orderness: Duration,
403}
404
405impl Default for PeriodicWatermarkConfig {
406 fn default() -> Self {
407 Self {
408 interval: Duration::from_secs(1),
409 max_out_of_orderness: Duration::from_secs(5),
410 }
411 }
412}
413
414pub struct PeriodicWatermark {
419 last_emission: DateTime<Utc>,
421 config: PeriodicWatermarkConfig,
423 bounded_generator: BoundedOutOfOrdernessWatermark,
425}
426
427impl PeriodicWatermark {
428 pub fn new(config: PeriodicWatermarkConfig) -> Self {
430 Self {
431 last_emission: Utc::now(),
432 config: config.clone(),
433 bounded_generator: BoundedOutOfOrdernessWatermark::new(
434 config.max_out_of_orderness,
435 None,
436 ),
437 }
438 }
439
440 fn should_emit(&self) -> bool {
442 let elapsed = Utc::now().signed_duration_since(self.last_emission);
443 elapsed >= ChronoDuration::from_std(self.config.interval).unwrap_or_default()
444 }
445}
446
447impl WatermarkGenerator for PeriodicWatermark {
448 fn on_event(&mut self, timestamp: i64, partition: u32) -> Option<Watermark> {
449 self.bounded_generator.on_event(timestamp, partition);
451 None
452 }
453
454 fn on_periodic_check(&mut self) -> Option<Watermark> {
455 if self.should_emit() {
456 self.last_emission = Utc::now();
457 let watermark = self.bounded_generator.current_watermark();
458 if !watermark.is_min() {
459 debug!(watermark = %watermark, "Periodic watermark emission");
460 return Some(watermark);
461 }
462 }
463 None
464 }
465
466 fn current_watermark(&self) -> Watermark {
467 self.bounded_generator.current_watermark()
468 }
469
470 fn reset(&mut self) {
471 self.bounded_generator.reset();
472 self.last_emission = Utc::now();
473 }
474
475 fn partition_watermark(&self, partition: u32) -> Option<Watermark> {
476 self.bounded_generator.partition_watermark(partition)
477 }
478}
479
480pub struct PunctuatedWatermark {
485 extractor: Box<dyn Fn(i64, u32) -> Option<i64> + Send + Sync>,
487 current_watermark: Watermark,
489 partition_watermarks: HashMap<u32, Watermark>,
491}
492
493impl PunctuatedWatermark {
494 pub fn new<F>(extractor: F) -> Self
500 where
501 F: Fn(i64, u32) -> Option<i64> + Send + Sync + 'static,
502 {
503 Self {
504 extractor: Box::new(extractor),
505 current_watermark: Watermark::min(),
506 partition_watermarks: HashMap::new(),
507 }
508 }
509
510 pub fn every_n_events(n: u64) -> Self {
512 let counter = Arc::new(Mutex::new(0u64));
513 Self::new(move |timestamp, _partition| {
514 let mut count = counter.lock().unwrap();
515 *count += 1;
516 if *count % n == 0 {
517 Some(timestamp)
518 } else {
519 None
520 }
521 })
522 }
523
524 pub fn on_timestamp_boundary(boundary_ms: i64) -> Self {
526 let last_boundary = Arc::new(Mutex::new(0i64));
527 Self::new(move |timestamp, _partition| {
528 let mut last = last_boundary.lock().unwrap();
529 let current_boundary = (timestamp / boundary_ms) * boundary_ms;
530 if current_boundary > *last {
531 *last = current_boundary;
532 Some(current_boundary)
533 } else {
534 None
535 }
536 })
537 }
538}
539
540impl WatermarkGenerator for PunctuatedWatermark {
541 fn on_event(&mut self, timestamp: i64, partition: u32) -> Option<Watermark> {
542 if let Some(watermark_ts) = (self.extractor)(timestamp, partition) {
543 let new_watermark = Watermark::new(watermark_ts);
544
545 self.partition_watermarks.insert(partition, new_watermark);
547
548 if new_watermark > self.current_watermark {
550 self.current_watermark = new_watermark;
551 debug!(
552 watermark = %new_watermark,
553 partition = partition,
554 "Punctuated watermark emission"
555 );
556 return Some(new_watermark);
557 }
558 }
559 None
560 }
561
562 fn on_periodic_check(&mut self) -> Option<Watermark> {
563 None }
565
566 fn current_watermark(&self) -> Watermark {
567 self.current_watermark
568 }
569
570 fn reset(&mut self) {
571 self.current_watermark = Watermark::min();
572 self.partition_watermarks.clear();
573 }
574
575 fn partition_watermark(&self, partition: u32) -> Option<Watermark> {
576 self.partition_watermarks.get(&partition).copied()
577 }
578}
579
580#[derive(Debug, Clone, Serialize, Deserialize)]
582pub struct LateEventConfig {
583 pub allowed_lateness: Duration,
585 pub log_late_events: bool,
587 pub emit_metrics: bool,
589}
590
591impl Default for LateEventConfig {
592 fn default() -> Self {
593 Self {
594 allowed_lateness: Duration::from_secs(60),
595 log_late_events: true,
596 emit_metrics: true,
597 }
598 }
599}
600
601pub struct LateEventHandler {
603 config: LateEventConfig,
605 dropped_count: Arc<Mutex<u64>>,
607 accepted_count: Arc<Mutex<u64>>,
609 total_lateness: Arc<Mutex<i64>>,
611}
612
613impl LateEventHandler {
614 pub fn new(config: LateEventConfig) -> Self {
616 Self {
617 config,
618 dropped_count: Arc::new(Mutex::new(0)),
619 accepted_count: Arc::new(Mutex::new(0)),
620 total_lateness: Arc::new(Mutex::new(0)),
621 }
622 }
623
624 pub fn handle_late_event(&self, lateness: i64, partition: u32) -> bool {
629 let lateness_duration = Duration::from_millis(lateness as u64);
630
631 if lateness_duration > self.config.allowed_lateness {
632 *self.dropped_count.lock().unwrap() += 1;
634
635 if self.config.log_late_events {
636 warn!(
637 lateness_ms = lateness,
638 partition = partition,
639 "Dropping late event beyond allowed lateness"
640 );
641 }
642
643 false
644 } else {
645 *self.accepted_count.lock().unwrap() += 1;
647 *self.total_lateness.lock().unwrap() += lateness;
648
649 if self.config.log_late_events {
650 debug!(
651 lateness_ms = lateness,
652 partition = partition,
653 "Accepting late event within allowed lateness"
654 );
655 }
656
657 true
658 }
659 }
660
661 pub fn get_stats(&self) -> LateEventStats {
663 let dropped = *self.dropped_count.lock().unwrap();
664 let accepted = *self.accepted_count.lock().unwrap();
665 let total_lateness = *self.total_lateness.lock().unwrap();
666
667 let avg_lateness = if accepted > 0 {
668 total_lateness / accepted as i64
669 } else {
670 0
671 };
672
673 LateEventStats {
674 dropped_count: dropped,
675 accepted_count: accepted,
676 average_lateness_ms: avg_lateness,
677 }
678 }
679
680 pub fn reset_stats(&self) {
682 *self.dropped_count.lock().unwrap() = 0;
683 *self.accepted_count.lock().unwrap() = 0;
684 *self.total_lateness.lock().unwrap() = 0;
685 }
686}
687
688#[derive(Debug, Clone, Serialize, Deserialize)]
690pub struct LateEventStats {
691 pub dropped_count: u64,
693 pub accepted_count: u64,
695 pub average_lateness_ms: i64,
697}
698
699#[cfg(test)]
700mod tests {
701 use super::*;
702
703 #[test]
704 fn test_watermark_creation() {
705 let wm = Watermark::new(1000);
706 assert_eq!(wm.timestamp, 1000);
707 assert!(wm.is_before(2000));
708 assert!(wm.is_after(500));
709 }
710
711 #[test]
712 fn test_watermark_ordering() {
713 let wm1 = Watermark::new(1000);
714 let wm2 = Watermark::new(2000);
715 assert!(wm1 < wm2);
716 assert!(wm2 > wm1);
717 }
718
719 #[test]
720 fn test_watermark_min_max() {
721 let min = Watermark::min();
722 let max = Watermark::max();
723 assert!(min.is_min());
724 assert!(max.is_max());
725 assert!(min < max);
726 }
727
728 #[test]
729 fn test_bounded_watermark_basic() {
730 let mut generator = BoundedOutOfOrdernessWatermark::new(
731 Duration::from_secs(5),
732 None,
733 );
734
735 generator.on_event(10000, 0);
737 generator.on_event(15000, 0);
738 generator.on_event(20000, 0);
739
740 let wm = generator.current_watermark();
741 assert_eq!(wm.timestamp, 15000);
743 }
744
745 #[test]
746 fn test_bounded_watermark_out_of_order() {
747 let mut generator = BoundedOutOfOrdernessWatermark::new(
748 Duration::from_secs(10),
749 None,
750 );
751
752 generator.on_event(30000, 0);
754 generator.on_event(20000, 0);
755 generator.on_event(25000, 0);
756
757 let wm = generator.current_watermark();
758 assert_eq!(wm.timestamp, 20000);
760 }
761
762 #[test]
763 fn test_bounded_watermark_multiple_partitions() {
764 let mut generator = BoundedOutOfOrdernessWatermark::new(
765 Duration::from_secs(5),
766 None,
767 );
768
769 generator.on_event(10000, 0);
771 generator.on_event(20000, 1);
772 generator.on_event(15000, 2);
773
774 let wm = generator.current_watermark();
780 assert_eq!(wm.timestamp, 5000);
781 }
782
783 #[test]
784 fn test_bounded_watermark_late_event_detection() {
785 let mut generator = BoundedOutOfOrdernessWatermark::new(
786 Duration::from_secs(5),
787 None,
788 );
789
790 generator.on_event(20000, 0);
791
792 assert!(generator.is_late_event(10000));
794 assert!(!generator.is_late_event(16000));
795 }
796
797 #[test]
798 fn test_bounded_watermark_lateness_calculation() {
799 let mut generator = BoundedOutOfOrdernessWatermark::new(
800 Duration::from_secs(5),
801 None,
802 );
803
804 generator.on_event(20000, 0);
805
806 assert_eq!(generator.get_lateness(10000), 5000);
808 assert_eq!(generator.get_lateness(16000), 0);
809 }
810
811 #[test]
812 fn test_periodic_watermark() {
813 let config = PeriodicWatermarkConfig {
814 interval: Duration::from_millis(100),
815 max_out_of_orderness: Duration::from_secs(5),
816 };
817 let mut generator = PeriodicWatermark::new(config);
818
819 generator.on_event(10000, 0);
821 generator.on_event(20000, 0);
822
823 assert!(generator.on_event(30000, 0).is_none());
825
826 std::thread::sleep(Duration::from_millis(150));
828 let wm = generator.on_periodic_check();
829 assert!(wm.is_some());
830 }
831
832 #[test]
833 fn test_punctuated_watermark_every_n() {
834 let mut generator = PunctuatedWatermark::every_n_events(3);
835
836 assert!(generator.on_event(1000, 0).is_none());
838 assert!(generator.on_event(2000, 0).is_none());
839
840 let wm = generator.on_event(3000, 0);
842 assert!(wm.is_some());
843 assert_eq!(wm.unwrap().timestamp, 3000);
844 }
845
846 #[test]
847 fn test_punctuated_watermark_boundary() {
848 let mut generator = PunctuatedWatermark::on_timestamp_boundary(10000);
849
850 assert!(generator.on_event(5000, 0).is_none());
852 assert!(generator.on_event(8000, 0).is_none());
853
854 let wm = generator.on_event(15000, 0);
856 assert!(wm.is_some());
857 assert_eq!(wm.unwrap().timestamp, 10000);
858
859 let wm2 = generator.on_event(25000, 0);
861 assert!(wm2.is_some());
862 assert_eq!(wm2.unwrap().timestamp, 20000);
863 }
864
865 #[test]
866 fn test_late_event_handler_within_lateness() {
867 let config = LateEventConfig {
868 allowed_lateness: Duration::from_secs(10),
869 log_late_events: false,
870 emit_metrics: true,
871 };
872 let handler = LateEventHandler::new(config);
873
874 assert!(handler.handle_late_event(5000, 0));
876
877 let stats = handler.get_stats();
878 assert_eq!(stats.accepted_count, 1);
879 assert_eq!(stats.dropped_count, 0);
880 }
881
882 #[test]
883 fn test_late_event_handler_beyond_lateness() {
884 let config = LateEventConfig {
885 allowed_lateness: Duration::from_secs(10),
886 log_late_events: false,
887 emit_metrics: true,
888 };
889 let handler = LateEventHandler::new(config);
890
891 assert!(!handler.handle_late_event(15000, 0));
893
894 let stats = handler.get_stats();
895 assert_eq!(stats.accepted_count, 0);
896 assert_eq!(stats.dropped_count, 1);
897 }
898
899 #[test]
900 fn test_late_event_handler_stats() {
901 let config = LateEventConfig::default();
902 let handler = LateEventHandler::new(config);
903
904 handler.handle_late_event(5000, 0);
905 handler.handle_late_event(10000, 0);
906 handler.handle_late_event(80000, 0); let stats = handler.get_stats();
909 assert_eq!(stats.accepted_count, 2);
910 assert_eq!(stats.dropped_count, 1);
911 assert_eq!(stats.average_lateness_ms, 7500);
912 }
913
914 #[test]
915 fn test_watermark_generator_reset() {
916 let mut generator = BoundedOutOfOrdernessWatermark::new(
917 Duration::from_secs(5),
918 None,
919 );
920
921 generator.on_event(20000, 0);
922 assert_ne!(generator.current_watermark(), Watermark::min());
923
924 generator.reset();
925 assert_eq!(generator.current_watermark(), Watermark::min());
926 }
927
928 #[test]
929 fn test_partition_watermark_tracking() {
930 let mut generator = BoundedOutOfOrdernessWatermark::new(
931 Duration::from_secs(5),
932 None,
933 );
934
935 generator.on_event(10000, 0);
936 generator.on_event(20000, 1);
937
938 let wm0 = generator.partition_watermark(0).unwrap();
939 let wm1 = generator.partition_watermark(1).unwrap();
940
941 assert_eq!(wm0.timestamp, 5000);
942 assert_eq!(wm1.timestamp, 15000);
943 }
944
945 #[test]
946 fn test_watermark_datetime_conversion() {
947 let dt = DateTime::from_timestamp(1000, 0).unwrap();
948 let wm = Watermark::from_datetime(dt);
949 let _dt2 = wm.to_datetime();
950
951 assert_eq!(wm.timestamp, 1000000); }
953
954 #[test]
955 fn test_idle_partition_detection() {
956 let mut generator = BoundedOutOfOrdernessWatermark::new(
957 Duration::from_secs(5),
958 Some(Duration::from_millis(100)),
959 );
960
961 generator.on_event(10000, 0);
962 generator.on_event(20000, 1);
963
964 assert_eq!(generator.idle_partitions().len(), 0);
966
967 std::thread::sleep(Duration::from_millis(150));
969
970 let idle = generator.idle_partitions();
972 assert_eq!(idle.len(), 2);
973 }
974
975 #[test]
976 fn test_watermark_monotonicity() {
977 let mut generator = BoundedOutOfOrdernessWatermark::new(
978 Duration::from_secs(5),
979 None,
980 );
981
982 generator.on_event(20000, 0);
983 let wm1 = generator.current_watermark();
984
985 generator.on_event(15000, 0);
987 let wm2 = generator.current_watermark();
988
989 assert!(wm2 >= wm1);
990 }
991}