The watermark is a direction, not a decoration
Operator order and key choice determine whether old dedup state can leave. Get either one wrong and the store keeps growing.
Understand the traps →A Structured Streaming dropDuplicates wearing a watermark can still grow its state store forever. Two silent traps: a withWatermark placed after dropDuplicates never reaches the dedup operator, and an event-time column missing from the dedup keys keeps the dedup global. The fix is two lines.
A watermark is useful only when it reaches the stateful operator — and plain watermarked dedup is bounded only when event time participates in the key.
Operator order and key choice determine whether old dedup state can leave. Get either one wrong and the store keeps growing.
Understand the traps →Compare the reversed chain, the missing-key chain, the documented fix, and the bounded dropDuplicatesWithinWatermark alternative.
Check the chain, check the keys, then inspect state. Pick global dedup only when arbitrary lateness is a real requirement.
Get the answers →Last verified against official Databricks documentation: October 4, 2026. Correction history: first publication — no corrections.
First, apply withWatermark before dropDuplicates. Every official example uses this order, and the Databricks documentation says it plainly: # Apply watermark before distinct operation. Reverse the chain and the watermark never reaches the dedup operator. Spark documents the consequence under “Without watermark”: “the query stores the data from all the past records as state.” No error is documented for the reversed order. The dangerous outcome is a query that runs while state keeps accumulating.
Second, put the event-time column in the dedup key list. Correct order alone is not enough for dropDuplicates. In the Databricks Community deep-dive's documented run, the chain was .withWatermark("event_time", "5 minutes").dropDuplicates(["event_id"]): the order was right, but event_time was missing from the keys. After the watermark advanced to 10:02, keys A and B remained in the state store. The write-up calls this global deduplication: the state store grows continuously, potentially causing out-of-memory issues.
streamingDf.withWatermark("eventTime", "10 seconds").dropDuplicates(["guid", "eventTime"]). With event time in the key, a record at event time T is removed from state when the watermark reaches or passes T.The A/B result above is the outcome of that documented run, not a deterministic per-record arrival guarantee. Watermarks are threshold semantics: they control when state is eligible to be evicted as the engine's watermark advances.
events = (
spark.readStream
.format("delta")
.table("main.bronze.events")
.select("event_time", "event_id", "payload")
)
reversed_order = (
events
.dropDuplicates(["event_id", "event_time"])
.withWatermark("event_time", "5 minutes")
)
global_dedup = (
events
.withWatermark("event_time", "5 minutes")
.dropDuplicates(["event_id"])
)
deduplicated = (
streamingDf
.withWatermark("eventTime", "10 seconds")
.dropDuplicates(["guid", "eventTime"])
)
eventTime in the dedup keys, a record with event time T is removed from the state store once the watermark is at or beyond T.bounded_dedup = (
streamingDf
.withWatermark("eventTime", "10 seconds")
.dropDuplicatesWithinWatermark(["guid"])
)
dropDuplicatesWithinWatermark requires Databricks Runtime 13.3 LTS or above. It guarantees deduplication only for duplicates arriving within the watermark threshold. A key is removed when the watermark reaches or passes event_time + delay. Set the threshold greater than the maximum timestamp difference between duplicate events. It needs a watermark to work at all.
Use the state-store reader from the deep-dive to list stored keys. Run it against the checkpoint for your query, advance the input past the watermark, and confirm old keys disappear.
state_keys = (
spark.read
.format("statestore")
.load("<checkpointLocation>")
)
state_keys.show(truncate=False)
withWatermark in the code; verify the state store.withWatermark is applied before dropDuplicates.dropDuplicatesWithinWatermark on Databricks Runtime 13.3 LTS+.Deduplicating a stream where duplicates arrive within a known, bounded delay. Choose a watermark threshold that exceeds the maximum timestamp difference between duplicate events, then verify eviction in state.
Duplicates can arrive arbitrarily late. Plain global dropDuplicates without a watermark is the documented option, but it stores all past records as state. Treat that as a memory budget, not a config flag. Do not use dropDuplicatesWithinWatermark below Databricks Runtime 13.3 LTS.
withWatermark applied before dropDuplicates, and is the event-time column in the dedup keys? Then inspect the state store with the statestore reader to confirm whether old keys are actually evicting.event_time + delay, and requires a watermark to work at all.dropDuplicates without a watermark is Spark's documented “without watermark” option. It stores everything, so budget the state store and checkpoint accordingly.dropDuplicates with withWatermark; Databricks Runtime 13.3 LTS+ for dropDuplicatesWithinWatermark; a Delta or other streaming source; standard compute.Start with the two-line repair, then strengthen the platform and streaming skills behind it.
Strengthen the platform basics behind notebooks, jobs, data workflows, and collaborative development.
Browse DataCamp Databricks courses →Check the chain, check the key, then inspect the state store and prove that old keys leave.
Save the audit →Pipelines, orchestration, and streaming practice that turn this issue's tactics into repeatable habits.
Browse DataCamp data engineering →Partner links: we may earn a commission if you enroll through these recommendations, at no extra cost to you.