Watermark math, no mercy
Track max(event_time) − delay, not wall-clock lag. Once old state is cleaned up, late records are outside the processing guarantee.
withWatermark("event_time", "10 minutes") is not a grace period — it's a drop contract. The watermark is computed from your data, never the wall clock, and any event older than the watermark is dropped from watermarked stateful aggregations — no error, no built-in late-record log. The window itself stays open until the watermark passes its end. Plus the initial-snapshot ordering trap that mass-drops rows on day one, and the one option that prevents it.
Built for engineers who need an answer they can test in the next notebook, job run, or incident review.
Track max(event_time) − delay, not wall-clock lag. Once old state is cleaned up, late records are outside the processing guarantee.
Enable withEventTimeOrder before a stateful stream consumes an existing Delta snapshot.
The stream can be internally consistent and still incomplete. Prove the gap per window, then replay from source.
Get the answer →Last verified against official Databricks docs: September 30, 2026. Correction history: first publication; Sep 30, 2026 — clarified that the 10:25 watermark does not finalize the 10:00–11:00 window (finalization needs the watermark to pass the window end) and scoped the drop claim to watermarked stateful aggregations.
withWatermark("event_time", "10 minutes") does not hold every row for ten minutes. Structured Streaming tracks the greatest event_time it has observed and derives the watermark from that value:
The input data advances the watermark; the wall clock does not. If a source stalls, the watermark freezes. If a burst of fresh events arrives, it can push the watermark forward past older stragglers. Records within the threshold are guaranteed to be processed. Records older than it are outside the guarantee; they are dropped from the watermarked stateful result once older than the watermark.
That contract matters in append-mode stateful operations: windowed aggregations, dropDuplicatesWithinWatermark, and outer stream-stream joins. There is no automatic dead-letter queue, warning, or exception for a late record that misses cleaned-up state. If the data must be inspected, you need a separate quarantine or reconciliation path.
from pyspark.sql.functions import window, col
hourly = (
spark.readStream.table("bronze_clicks")
.withWatermark("event_time", "10 minutes")
.groupBy(window(col("event_time"), "1 hour"))
.count()
)
The query uses a 10-minute watermark. Events stamped 10:02, 10:05, and 10:08 arrive. Then an out-of-order fresh burst includes an event at 10:35, moving the maximum observed event time to 10:35 and the watermark to 10:25. A straggler stamped 10:20 arrives after that boundary.
Primary reference: Databricks docs: apply watermarks to control data processing thresholds
When a stateful query first starts against an existing Delta table, it consumes the table's initial snapshot. By default, those files are processed in last-modified order, not event-time order. A recently rewritten file full of old events can therefore arrive after newer files have already advanced the watermark. The old rows now look late and can be mass-dropped on the first run.
Enable withEventTimeOrder on the stream before it begins:
from pyspark.sql.functions import window, col
stream = (
spark.readStream
.option("withEventTimeOrder", "true")
.table("bronze_clicks")
.withWatermark("event_time", "10 minutes")
.groupBy(window(col("event_time"), "1 hour"))
.count()
)
The option divides the initial snapshot's event-time range into buckets. Each micro-batch processes one bucket in event-time order. maxFilesPerTrigger and maxBytesPerTrigger still constrain each batch approximately. The feature is available in Databricks Runtime 11.3 LTS and above and has been generally available since February 2025.
# Cluster-wide switch for all applicable streaming queries
spark.databricks.delta.withEventTimeOrder.enabled true
withEventTimeOrder while the initial snapshot is actively processing without deleting the checkpoint.Initial snapshot processing can be slower because each micro-batch scans the snapshot for its event-time bucket. Mitigate that cost by using a real Delta source column as event time so data skipping can apply, and partitioning the table by event time where that layout fits the workload.
dropDuplicatesWithinWatermark requires a watermark and is available in Databricks Runtime 13.3 LTS and above. It guarantees deduplication only for duplicates arriving within the threshold. To guarantee that all duplicate copies are dropped, set the threshold greater than the maximum timestamp difference between duplicate events.spark.sql.streaming.multipleWatermarkPolicy=max makes the global watermark follow the fastest stream. That reduces latency, but the official docs warn that it drops data from slower streams. The default min policy moves at the pace of the slowest stream.multipleWatermarkPolicy=max but cannot accept loss from the slowest stream.dropDuplicatesWithinWatermark; 11.3 LTS+ for withEventTimeOrder; Delta Lake source tables; append output mode; standard compute.Probably incomplete, not wrong. A batch query reads the source rows that exist now. A stateful stream with a watermark applies a time-bounded processing contract: records that arrive beyond that boundary may be dropped. The disagreement often appears after restarts, backfills, or a first run that consumed the initial snapshot in file modification order.
Prove it by reconciling source and output per event-time window. If missing events cluster in the first windows after a restart or backfill, the watermark is the prime suspect.
-- Late-drop audit: source events vs. streamed output, per window
WITH src AS (
SELECT date_trunc('hour', event_time) AS hr, COUNT(*) AS source_events
FROM bronze_clicks
GROUP BY 1
),
out AS (
SELECT date_trunc('hour', window.start) AS hr, SUM(cnt) AS streamed_events
FROM click_hourly_counts
GROUP BY 1
)
SELECT s.hr,
s.source_events,
COALESCE(o.streamed_events, 0) AS streamed_events,
s.source_events - COALESCE(o.streamed_events, 0) AS missing_events
FROM src s
LEFT JOIN out o USING (hr)
ORDER BY s.hr;
This audit assumes click_hourly_counts retains one or more count rows that can be summed per hourly window. Adapt the output-side aggregation if your sink writes one final row per window or uses a different count column.
Start with the product contract. Then turn it into an operating habit you can test.
Official reference: Databricks docs: watermarks
Practice path: Data engineering workflows — pipelines, orchestration, and streaming practice
Partner links: we may earn a commission if you enroll through these recommendations, at no extra cost to you.
dropDuplicatesWithinWatermark).withEventTimeOrder, initial snapshot processing, constraints, and performance guidance).withEventTimeOrder, event-time buckets, and checkpoint constraints).withEventTimeOrder general availability).