CertSafari
    Databricks Certified Data Engineer Professional - October 9 onwards· Lessons

    Domain 1 · Lesson 12/46

    Streaming Checkpoints and foreachBatch for Exactly-Once Recovery

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

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

    What you will be able to do

    • Explain what a checkpoint directory stores and how it enables exactly-once recovery after a restart
    • Tell query changes that can reuse a checkpoint apart from changes that need a new one
    • Write a foreachBatch function that handles empty batches, consumes the whole batch, writes idempotently using batchId, and fails fast on errors

    1.What a checkpoint stores

    A streaming query recovers from failure because it records its progress. Checkpoints and write-ahead logs work together to give Structured Streaming its processing guarantees. The checkpoint holds everything that identifies the query, including its state and which records it has processed. That is also why deleting the checkpoint files, or pointing the query at a new location, makes the next run start from scratch.

    Contents of a checkpoint directory
    ComponentWhat it recordsWhy it matters
    OffsetsSource offsets processed in each micro-batchThe query resumes exactly where it stopped, without reprocessing data
    CommitsWhich micro-batches have been committed to the sinkEnables exactly-once semantics
    StateStateful-operator metadata, the state schema, and state store contentsAggregations, stream-stream joins, deduplication and transformWithState pick up their state again
    MetadataThe unique query ID. Configuration settings are stored in the offset logIdentifies the query across restarts

    Set checkpointLocation before you start the query, and give every query its own location. Queries must never share one. Some sinks, such as display() in notebooks and the memory sink, create a temporary checkpoint if you leave it out. Those temporary locations provide no fault-tolerance or consistency guarantees and might not be cleaned up properly, so Databricks recommends always setting one.

    Specifying a checkpoint location in a Unity Catalog volumepython
    (df.writeStream
      .option("checkpointLocation", "/Volumes/catalog/schema/volume/path")
      .toTable("catalog.schema.table")
    )

    Checkpoint 1 of 6· Match them up

    Match each checkpoint component to its role in recovery

    Tap a term, then the definition that fits it.

    Sources1

    2.Which query changes a checkpoint survives

    Structured Streaming automatically checkpoints state data to fault-tolerant storage and restores it after a restart. This only works if the restarted query still matches what the checkpoint describes. Changes to filters, rate limits and trigger intervals are generally safe. Changes to the input sources, the subscribed Kafka topics or Auto Loader paths, the type of stateful operation, the state schema, or the sink type generally need a new checkpoint. Sources are identified by their position in the query plan, which is why adding or reordering them breaks recovery unless source naming is turned on.

    Common changes between restarts from the same checkpoint
    ChangeVerdict
    Adding or removing a filterAllowed
    Adding, removing or changing rate limits such as maxOffsetsPerTriggerAllowed
    Switching the subscribed Kafka topicGenerally not allowed: results are unpredictable
    Changing the output directory of a file sinkNot allowed
    Changing the output topic of a Kafka sinkAllowed
    Changing grouping keys or aggregates of a streaming aggregationNot allowed
    Changing a stream-stream join from inner to outerNot allowed
    Changing logic inside the mapGroupsWithState function, keeping the same state schemaAllowed, but the semantics depend on your code

    Other settings are fixed when the checkpoint is created. The number of shuffle partitions is one: changing spark.sql.shuffle.partitions has no effect on a query that already has a checkpoint. Databricks Runtime 18 LTS and above adds on-demand repartitioning for stateful queries. The state management scheme cannot change between restarts either, so switching state store means starting from a new checkpoint. Queries that use dropDuplicates() or dropDuplicatesWithinWatermark() can also fail a state schema compatibility check when you change the compute access mode. Do not change the access mode for those queries.

    Checkpoint 2 of 6· Check yourself

    A stateful aggregation has been running with an existing checkpoint. The team raises spark.sql.shuffle.partitions from 200 to 400 and restarts it from the same checkpoint on an older runtime. What happens?

    Checkpoint 3 of 6· Exam question

    A Structured Streaming job has been running for weeks against the checkpoint location `s3://data/chk/orders-agg`. The team wants to ship a code update without deleting or resetting that checkpoint. Which two of the following changes can be deployed safely against the existing checkpoint, without breaking state recovery on restart? (Select 2)(Select 2)

    Sources12

    3.foreachBatch: arbitrary sinks with exactly-once you build yourself

    writeStream.foreachBatch(...) runs batch code on the output of each micro-batch. Your function receives two arguments: the micro-batch's output DataFrame and the micro-batch's unique ID. It is how you write to sinks that have no streaming writer, and Delta Lake MERGE in Structured Streaming requires it. Delta sinks don't support update output mode, so running MERGE INTO inside foreachBatch is the standard way to write streaming aggregates to a Delta table with update semantics. foreachBatch relies on micro-batch execution, so it does not work in continuous processing mode.

    Because a retried batch arrives with the same batchId, you can use it to make the writes idempotent. For Delta table writes, set the txnAppId and txnVersion write options and bind txnVersion to batchId. For custom destinations such as OLTP databases, queues or APIs, you have to build idempotency yourself: running a given batchId again must produce the same result. Your function also has to cope with an empty DataFrame. A Delta source can produce one, for example after an OPTIMIZE with no files to process or when file pruning removes every record.

    Skipping empty micro-batches inside foreachBatchpython
    def process_batch(output_df, batch_id):
      # Process valid DataFrames only
      if not output_df.isEmpty():
        # business logic
        pass
    
    streamingDF.writeStream.foreachBatch(process_batch).start()

    With a stateful operator upstream, such as dropDuplicatesWithinWatermark, each call must consume the entire DataFrame or restart the query. If it doesn't, the query fails on the next batch. A function that only calls batch_df.show(2) consumes just part of the batch, so it has to drain the rest explicitly:

    Checkpoint 4 of 6· Fill the gap

    The function below shows two rows and must then silently consume the rest of the batch so the stateful query doesn't fail. Which method fills the blank?

    def partial_func(batch_df, batch_id):
      batch_df.show(2)
      batch_df. ? (do_nothing) # silently consume the rest of the batch

    For errors, Databricks recommends letting the query fail fast and leaving retries to the orchestration layer, such as Lakeflow Jobs or Airflow. Retry loops inside your function risk losing data. A try/except that swallows the error might also leave the batch partly consumed, so either re-raise the error or drain the rest of the batch. If you write to several sinks, prefer separate streaming writers: writing them all from one foreachBatch serializes the writes and adds latency.

    Recommended handling of exceptions inside foreachBatch
    Exception typeExamplesRecommended action
    Transient sink errorsSQLTransientConnectionException, HTTP 429, timeoutsCatch: retry, or send to a dead-letter queue
    Duplicate or key constraint violations when the sink is idempotentSQLIntegrityConstraintViolationExceptionCatch: log and suppress
    Logic or schema errorsNullPointerException, AttributeError, schema mismatchPropagate: let Spark fail the query
    Critical failuresOutOfMemoryError, corrupted state, data integrity violationsPropagate: let Spark fail the query

    Checkpoint 5 of 6· Check yourself

    A foreachBatch function appends each micro-batch to a Delta table. After a failure and restart, the same batch runs again. What prevents duplicate rows?

    Checkpoint 6 of 6· Exam question

    Which Structured Streaming output mode rewrites the entire result table to the sink on every trigger, making it unsuitable for a large, unbounded aggregation whose full state must be retained indefinitely?

    Sources3

    Exam traps

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

    1. 1.Because the query has a checkpoint, writes made in foreachBatch are exactly-once.Why is that wrong?

      foreachBatch guarantees only at-least-once writes. You get exactly-once only by using batchId to make the writes idempotent, for example with txnAppId/txnVersion on Delta.

      Covered in foreachBatch: arbitrary sinks with exactly-once you build yourself

    2. 2.You can add a grouping key to a running streaming aggregation and restart it from the same checkpoint.Why is that wrong?

      The checkpointed state has a fixed schema. Adding, removing or changing a stateful operation is not allowed between restarts, so this change needs a new checkpoint.

      Covered in Which query changes a checkpoint survives

    3. 3.Two streaming queries writing to the same table can share one checkpoint location.Why is that wrong?

      A checkpoint identifies a single query, through its query ID, offsets and commits. Every query needs its own location.

      Covered in What a checkpoint stores

    Sources

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

    1. 1.
      “Offsets: The source offsets processed in each micro-batch.”
      ↩︎ What a checkpoint stores
      “These temporary checkpoint locations do not ensure any fault tolerance or data consistency guarantees”
      ↩︎ What a checkpoint stores
      “Structured Streaming automatically checkpoints the state data to fault-tolerant storage”
      ↩︎ Which query changes a checkpoint survives
      “Changes that are generally safe include adding or removing filters, changing rate limits, trigger intervals”
      ↩︎ Which query changes a checkpoint survives
      “Any change in number or type of grouping keys or aggregates is not allowed.”
      ↩︎ Exam trap 2
      “Each query must have a different checkpoint location. Multiple queries should never share the same location.”
      ↩︎ Exam trap 3
      “Commits: A record of which micro-batches have been committed to the sink, enabling exactly-once semantics.”
      ↩︎ Checkpoint
    2. 2.
      “The state management scheme can't be changed between query restarts.”
      ↩︎ Which query changes a checkpoint survives
      “Changing spark.sql.shuffle.partitions has no effect on a streaming query that already has a checkpoint”
      ↩︎ Checkpoint
    3. 3.
      “You must use foreachBatch for Delta Lake merge operations in Structured Streaming.”
      ↩︎ foreachBatch: arbitrary sinks with exactly-once you build yourself
      “each batch iteration must consume the entire DataFrame or restart the query.”
      ↩︎ foreachBatch: arbitrary sinks with exactly-once you build yourself
      “Databricks recommends that you allow the streaming query to fail fast and instead rely on the orchestration layer”
      ↩︎ foreachBatch: arbitrary sinks with exactly-once you build yourself
      “you can use the batchId provided to the function as way to deduplicate the output and get an exactly-once guarantee.”
      ↩︎ Exam trap 1
      “foreachBatch() provides only at-least-once write guarantees.”
      ↩︎ Prediction
      “You must use the txnAppId and txnVersion write options, binding txnVersion to batchId, to guarantee idempotency”
      ↩︎ Checkpoint

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