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.
Python code
52 linesfrom 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
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
- Use monotonic timestamps (epoch seconds) instead of `datetime` objects for simpler arithmetic.
- Track a rolling max with a deque to limit memory on unbounded streams.
- 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
More from Big data & Spark
Keep learning
Related tutorials and quizzes for this topic.