How to Implement a Streaming Watermark in Python

Mock structured streaming watermarks in Python to track late event times and compute a watermark for windowed processing.

Medium Python 3.9+ Aug 9, 2026 Big data & Spark 14 views 0 copies

Python code

52 lines
Python 3.9+
from datetime import datetime, timedelta
import time

class StreamingWatermark:
    """Mock watermark tracker for structured streaming."""

    def __init__(self, watermark_delay_seconds):
        self.watermark_delay = timedelta(seconds=watermark_delay_seconds)
        self.max_event_time = None

    def observe_event(self, event_time):
        """Track the latest event timestamp seen."""
        if self.max_event_time is None or event_time > self.max_event_time:
            self.max_event_time = event_time

    def get_watermark(self, current_time):
        """Compute watermark = max_event_time - delay."""
        if self.max_event_time is None:
            return None
        return self.max_event_time - self.watermark_delay


def simulate_stream(events, watermark_delay_seconds=10):
    """Simulate a stream and show how the watermark evolves."""
    tracker = StreamingWatermark(watermark_delay_seconds)
    print(f"Watermark delay: {watermark_delay_seconds}s")
    print("=" * 50)

    current_time = datetime(2024, 1, 1, 12, 0, 0)
    for event_time in events:
        tracker.observe_event(event_time)
        current_time += timedelta(seconds=3)  # simulate clock ticking
        watermark = tracker.get_watermark(current_time)

        print(f"Event: {event_time.strftime('%H:%M:%S')} | "
              f"MaxEvent: {tracker.max_event_time.strftime('%H:%M:%S')} | "
              f"Watermark: {watermark.strftime('%H:%M:%S')}")


if __name__ == "__main__":
    # Events arriving with some out-of-order data
    base = datetime(2024, 1, 1, 12, 0, 0)
    events = [
        base + timedelta(seconds=0),
        base + timedelta(seconds=5),
        base + timedelta(seconds=3),   # out of order
        base + timedelta(seconds=12),
        base + timedelta(seconds=8),   # out of order
        base + timedelta(seconds=20),
    ]

    simulate_stream(events, watermark_delay_seconds=10)

Output

stdout
Watermark delay: 10s
==================================================
Event: 12:00:00 | MaxEvent: 12:00:00 | Watermark: 11:59:50
Event: 12:00:05 | MaxEvent: 12:00:05 | Watermark: 11:59:55
Event: 12:00:03 | MaxEvent: 12:00:05 | Watermark: 11:59:55
Event: 12:00:12 | MaxEvent: 12:00:12 | Watermark: 12:00:02
Event: 12:00:08 | MaxEvent: 12:00:12 | Watermark: 12:00:02
Event: 12:00:20 | MaxEvent: 12:00:20 | Watermark: 12:00:10

How it works

The StreamingWatermark class tracks the maximum event time across all seen events. The watermark is computed by subtracting a configurable delay from that max event time — this simulates how structured streaming (e.g., Spark) handles late-arriving data. The observe_event method updates the max whenever a newer timestamp arrives, ignoring older out-of-order events. The simulate_stream function advances a mock clock and prints both the max event time and the evolving watermark for each incoming event. This pattern lets you test windowed aggregation logic locally without a real Spark cluster or Kafka stream.

Common mistakes

  • Using current processing time instead of event time when computing the watermark.
  • Not initializing `max_event_time` to handle the first event correctly.
  • Forgetting that the watermark can move backward if max event time decreases.
  • Confusing watermark delay with the window duration in aggregation logic.

Variations

  1. Use monotonic timestamps (epoch seconds) instead of `datetime` objects for simpler arithmetic.
  2. Track a rolling max with a deque to limit memory on unbounded streams.
  3. Expose the watermark as a property that recomputes on access.

Real-world use cases

  • Unit testing Spark Structured Streaming windowed aggregations without spinning up a cluster.
  • Simulating clickstream event processing to validate late-data handling before production deployment.
  • Guiding window join logic in streaming pipelines where delayed events affect join correctness.

Sponsored

Run this sample

Open the browser IDE to tweak the example and see results without installing anything.

Open editor

More from Big data & Spark

Related tutorials and quizzes for this topic.