This episode covers the reliability of Spark applications: checkpointing in Structured Streaming, fault tolerance and recovery behavior, state management for streaming jobs, and best practices for long-running streaming applications that run without stopping.

Production streaming applications run for days or even months without stopping. During that time, many things can happen: a node dies, the network drops, Kafka becomes unresponsive. Episode 16 covers how Spark stays reliable through all of it — via checkpointing and fault tolerance.
Without a recovery mechanism, a single small failure can force a streaming job to restart from scratch, losing already-processed data or producing duplicates. Understanding how Spark stores progress and state is the key to building streaming pipelines you can trust.
This episode covers four things: checkpointing in Structured Streaming, fault tolerance and recovery behavior, state management for streaming jobs, and best practices for long-running streaming applications.
A checkpoint is the location where Spark periodically stores processing metadata. Its contents include:
query = df.writeStream \
.format("parquet") \
.option("path", "data/hasil") \
.option("checkpointLocation", "data/checkpoint") \
.outputMode("append") \
.start()checkpointLocation must be stored on durable storage (HDFS, S3, or a persistent Kubernetes volume) — not on a worker's local disk that can be lost. This metadata is how Spark knows where to resume from.
An important rule: a checkpoint location must not be shared by two different queries, and it must not be reused by a query whose structure has changed drastically. If the logic changes significantly, use a new checkpoint location.
When a streaming application is force-stopped or a node dies, the recovery flow goes like this:
restart driver → read checkpoint → resume from last offset → reprocessDelivery semantics depend on the sink:
spark-submit --conf spark.sql.streaming.schemaInference=true \
--conf spark.sql.streaming.fileSink.log.deletion=true job.pyspark.sql.streaming.fileSink.log.deletion is one of the file sink metadata cleanup configurations. For stricter duplicate guarantees in batch processing, you can use Delta Lake, which provides idempotent transactions.
Every stateful aggregation (window, groupBy) stores intermediate results in memory. Watermarks help clean up state that's no longer relevant. If state grows without control, the application can slow down or run out of memory.
For custom stateful logic — such as session tracking — Spark provides the mapGroupsWithState and flatMapGroupsWithState APIs:
import org.apache.spark.sql.streaming.GroupState
def updateState(userId: String,
events: Iterator[Event],
state: GroupState[UserState]): Iterator[Output] = {
val current = state.getOption.getOrElse(UserState.empty)
val updated = current.update(events)
state.update(updated)
Iterator(Output(userId, updated.summary))
}GroupState[UserState] stores per-key state that is persisted periodically to the checkpoint. You must define a timeout so the state of inactive keys can be removed — otherwise state will only grow.
state.setTimeoutDuration("30 minutes")state.setTimeoutDuration("30 minutes") automatically triggers the timeout callback for keys that haven't received events for 30 minutes — the standard pattern for sessionization.
Several practices keep streaming applications healthy for months:
query.stop() and the driver is given time to finish writing the last batch.When upgrading code or libraries:
1. submit a new query with a new checkpoint (test mode)
2. verify no duplicate data and the sink is normal
3. cutover: stop the old query, run the new oneThis pattern avoids a failure in the middle of the night caused by changes that were never tested against the old checkpoint format.
Warning
A checkpoint is not a replacement for data backup. If the data source (for example Kafka) has already deleted old offsets due to retention, Spark can't re-read data that no longer exists. Balance Kafka retention against your long-term recovery needs.
Episode 16 rounds out the reliability aspect: checkpoints store offsets, state, and metadata so jobs can continue after failure; recovery restores position and state automatically; state management with mapGroupsWithState controls custom logic; and best practices keep streaming applications healthy over the long term.
Key takeaways:
In the next episode, episode 17, we'll discuss custom extensions and UDFs — writing UDFs, UDAFs, and UDTs in Scala, Java, and Python, custom serialization with Kryo, extending Spark with external libraries, and packaging libraries for reuse across many jobs.