CertSafari
    Databricks Certified Data Engineer Professional· Lessons

    Domain 1 · Lesson 12/46

    Structured Streaming Watermarks and Output Modes

    Apply Structured Streaming stateful-processing semantics, including watermarks, output modes, foreachBatch, and checkpoints for fault-tolerant exactly-once state recovery.

    10 min read
    1.92% of exam
    2 sources
    Published 2 Oct 2026
    Docs as of 30 Sep 2026

    What you will be able to do

    • Declare a watermark with withWatermark and predict when state for a window or key is dropped
    • Choose a watermark duration and a multi-stream watermark policy, and know which stateful operations require a watermark
    • Pick append, update or complete output mode for a stateful query, given its latency needs, how downstream readers use it, and the sink

    Key concept

    Watermark — A watermark is an event-time column plus a lateness threshold. The engine uses it to decide when a piece of state (a window or a join key) is final. Once the threshold passes, that state can be emitted and dropped, so state stays bounded instead of growing forever.

    1.Why stateful queries need a watermark

    A stateless streaming query only has to remember which source rows it has already processed. A stateful query also keeps intermediate results across micro-batches, so each batch can update results incrementally instead of recomputing everything. Typical state entities are a time-window aggregation and the unique keys in a stream-stream join. If nothing removes state, it keeps growing, and that leads to memory errors and rising latency. The watermark is what removes it.

    You declare a watermark on a streaming DataFrame by naming a timestamp column and a lateness threshold. As data arrives, the state manager tracks the latest timestamp it has seen in that column and processes records that fall within the threshold. The guarantee only goes one way. Records inside the threshold are always processed. Records outside it might still be processed, but that is not guaranteed, so never build logic that relies on late rows being dropped.

    A 10-minute watermark on a 5-minute tumbling-window count per idpython
    from pyspark.sql.functions import window
    
    (df
      .withWatermark("event_time", "10 minutes")
      .groupBy(
        window("event_time", "5 minutes"),
        "id")
      .count()
    )

    In groupBy() and window(), refer to the event-time column by name (a string or col()) so the event-time marker carries through to the aggregation.

    Checkpoint 1 of 5· Check yourself

    In the sample above, when is the state for one id's 5-minute window kept until?

    Sources1

    2.Choosing thresholds: duration, joins, multiple streams and deduplication

    Watermark length trades latency against tolerance for late data. A short watermark keeps less state and writes results sooner, but it tolerates very little late data. A long watermark tolerates more late data, but the query holds more state and waits longer before it writes results.

    Stream-stream joins add some rules. For inner joins, Databricks recommends a watermark on each side so old records can be discarded. Without one, every key from both sides is joined on each trigger. For outer joins a watermark is mandatory. Unmatched rows come out with nulls, but only after the lateness threshold passes, because joins only support append mode. Each input can have its own threshold:

    Stream-stream join with a different lateness threshold on each inputpython
    (input_stream1.withWatermark("eventTime1", "1 hour")
      .join(
        input_stream2.withWatermark("eventTime2", "2 hours"),
        joinCondition)
    )

    The engine tracks the maximum event time for each input separately, works out each input's watermark, and then picks one global watermark. By default it uses the minimum, so the query moves at the pace of the slowest stream. If one stream stalls, for example because of an upstream failure, its data is not wrongly marked as late. Setting spark.sql.streaming.multipleWatermarkPolicy to max uses the fastest stream's watermark instead. That lowers latency but drops data from the slower streams.

    The two global-watermark policies
    spark.sql.streaming.multipleWatermarkPolicyGlobal watermark followsConsequence
    min (default)The slowest input streamSafe: a lagging stream's data is not marked late, but output can be delayed
    maxThe fastest input streamLower latency, but data from the slowest streams is dropped. Use with caution

    Deduplication is stateful as well. distinct() keeps every unique record in state, so without a watermark that state grows forever. Structured Streaming guarantees exactly-once processing, but it does not remove duplicates that already exist in the source. To deduplicate on a key such as a GUID, even when duplicates have different event or arrival times, use dropDuplicatesWithinWatermark (Databricks Runtime 13.3 LTS and above). This method requires a watermark. To be sure every duplicate is dropped, set the threshold larger than the maximum time gap between duplicate events.

    Checkpoint 2 of 5· Fill the gap

    This query deduplicates on guid within a 10-hour window based on eventTime. Which method fills the blank?

    (streamingDf
      . ? ("eventTime", "10 hours")
      .dropDuplicatesWithinWatermark(["guid"])
    )

    Sources1

    3.Output modes: what a stateful query emits each trigger

    Output mode controls which rows a query emits on each trigger. It matters only for stateful queries with aggregations. For stateless queries all modes behave the same. Joins support only append. Output mode has no effect on deduplication, and it does not change how mapGroupsWithState or flatMapGroupsWithState emit rows. If you set no mode, the query runs in append mode.

    The three output modes
    ModeRows emitted per triggerLatency and costDelta Lake sink
    Append (default)Only rows that won't change in future triggers. The watermark decides when that isDelayed by at least the watermark delay. Each record is written onceSupported
    UpdateEvery row that changed during the trigger, even if it may change again laterFreshest results. One write per trigger per aggregate valueNot supported. For similar behavior, see Merge in streaming
    CompleteEvery row the operator has ever produced (streaming aggregations only)Can perform poorly as data scalesSupported

    After that first batch, state holds [2pm, 3pm] at $25 and [3pm, 4pm] at $30. Update mode emits both rows because both changed. Complete mode emits everything. Then $20 at 3:20pm arrives and the watermark moves to 3:05pm. That is past the end of [2pm, 3pm], so append mode now emits that window, which can no longer change. Update mode emits only [3pm, 4pm], whose value went from $30 to $50. Complete mode again emits every row.

    Checkpoint 3 of 5· Put it in order

    Put the events of the revenue example in order, ending with the first row append mode writes.

    1. 1.The watermark advances to 3:05pm
    2. 2.The watermark becomes 2:55pm and append mode emits nothing
    3. 3.The first micro-batch brings $15 at 2:40pm, $10 at 2:30pm and $30 at 3:10pm
    4. 4.Append mode emits the [2pm, 3pm] window
    5. 5.A $20 record at 3:20pm arrives

    Choose the mode based on how downstream consumers use the data. If a consumer takes one action per write, such as a notification service, use append so each record is written only once. If consumers need the freshest values, such as a real-time dashboard or an ML model reading features, use update. Then check that the sink supports the mode. Kafka supports all three. Delta Lake, which backs every Unity Catalog managed table, supports append and complete but not update, so an update-mode write to a managed table fails.

    Setting the output mode explicitly on a write to a Unity Catalog tablepython
    # Append output mode (same as default behavior)
    (df.writeStream
      .outputMode("append")
      .toTable("target_table")
    )

    Checkpoint 4 of 5· Check yourself

    A windowed aggregation must keep a Unity Catalog managed table as current as possible, with rows overwritten as values change. Which statement is correct?

    Checkpoint 5 of 5· Exam question

    A team runs the following Structured Streaming query: ```python agg = (df.withWatermark("event_time", "15 minutes") .groupBy(window("event_time", "5 minutes")) .count()) agg.writeStream \ .outputMode("complete") \ .format("delta") \ .option("checkpointLocation", chk_path) \ .toTable("orders.window_counts") ``` After running for several weeks, the checkpoint's state store directory keeps growing and the cluster shows increasing memory pressure. What is the most likely cause?

    Sources2

    Exam traps

    Each one states something that sounds right. Open it to see what is actually true.

    1. 1.Update mode works on any sink, including a Delta table.Why is that wrong?

      Delta Lake, and so every Unity Catalog managed table, supports only append and complete. An update-mode write to a managed table fails. Use a merge pattern instead.

      Covered in Output modes: what a stateful query emits each trigger

    2. 2.Setting multipleWatermarkPolicy to max is a free way to cut the latency of a multi-stream query.Why is that wrong?

      With max, the global watermark follows the fastest stream, so late-looking data from slower streams is dropped. The default, min, is the safe choice.

      Covered in Choosing thresholds: duration, joins, multiple streams and deduplication

    3. 3.Append mode emits an aggregate window as soon as the window's time range ends.Why is that wrong?

      Append mode waits until the watermark (maximum event time minus the delay) passes the window's end, so output is delayed by at least the watermark delay.

      Covered in Output modes: what a stateful query emits each trigger

    Sources

    Every claim above is drawn from one of these pages, quoted as it was written on the date shown.

    1. 1.
      “Queries always process records that arrive within the threshold. Queries might still process records that arrive outside the threshold, but this isn't guaranteed.”
      ↩︎ Why stateful queries need a watermark
      “By default, Structured Streaming uses the minimum as the global watermark.”
      ↩︎ Choosing thresholds: duration, joins, multiple streams and deduplication
      “Structured Streaming guarantees exactly-once processing but doesn't deduplicate records from data sources.”
      ↩︎ Choosing thresholds: duration, joins, multiple streams and deduplication
      “Watermarks control the threshold for when a query stops processing a state entity.”
      ↩︎ Key concept
      “However, this configuration drops data from the slowest streams.”
      ↩︎ Exam trap 2
      “State information is maintained for each count until the end of the window is 10 minutes older than the latest observed event_time.”
      ↩︎ Checkpoint
    2. 2.
      “By default, streaming queries run in append mode.”
      ↩︎ Output modes: what a stateful query emits each trigger
      “Update mode results in one write per trigger per aggregate value.”
      ↩︎ Output modes: what a stateful query emits each trigger
      “Delta Lake sinks support only append and complete modes, so the update output mode example fails against a managed table.”
      ↩︎ Exam trap 1
      “your records have at least a 1-hour delay before being emitted downstream”
      ↩︎ Exam trap 3
      “The streaming aggregation operator does not emit anything downstream.”
      ↩︎ Prediction
      “The watermark updates to 3:05pm because the engine subtracts 15 minutes from 3:20pm.”
      ↩︎ Checkpoint
      “Delta Lake, which backs all Unity Catalog managed tables, supports append and complete modes but not update mode.”
      ↩︎ Checkpoint

    Continue to page 2 of 2

    Streaming Checkpoints and foreachBatch for Exactly-Once Recovery

    Spotted a mistake, or was something unclear? Tell us.