Hi Kiran,
A heap that climbs steadily for 40 hours while GC gets worse looks like something holding on to memory, not a job that was always borderline. More memory would mostly move the crash from 40 hours to 80.
How I tell the two apart:
- Watch the floor, not the peaks. Look at heap used right after a full GC. If that floor keeps rising at a steady input rate, something is retaining memory. If it stays flat and the job only dies on spikes (a big batch, a backlog after a restart, a skewed key), it's borderline, and more memory or smaller batches is a fair fix.
- Heap or native. In the compute metrics, switch from All nodes to the driver and to single workers, and compare JVM heap usage with Container memory usage. If the container climbs while the heap stays flat, it's native memory (RocksDB, Python workers, Arrow). A java.lang.OutOfMemoryError means heap; a process killed by the OS (often exit code 137) points to native. GC getting worse points at the heap.
What I check first in streaming jobs:
1. State that never expires. In query.lastProgress, look at stateOperators: numRowsTotal and memoryUsedBytes. If numRowsTotal only goes up, an aggregation, dropDuplicates or stream-stream join has no watermark, or one much longer than it needs.
2. Where the state lives. Below DBR 17.3 LTS the default state store keeps state on the executor heap, which fits a slow heap climb. RocksDB moves it off-heap; on those versions also cap it with spark.sql.streaming.stateStore.rocksdb.boundedMemoryUsage and maxMemoryUsageMB. From 17.3 LTS, RocksDB is the default and its memory is capped automatically.
3. foreachBatch code. collect() or toPandas(), DataFrames cached and never unpersisted, or a Python list or dict that grows every batch. These grow the driver, not the executors.
Heap dumps are worth it once you know it's the heap, but start with something cheaper. On classic compute, take class histograms of the driver an hour apart from a %sh cell (jcmd <pid> GC.class_histogram) and compare the top classes. The one that keeps growing usually names the culprit. For a full dump, add -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/local_disk0 to spark.driver.extraJavaOptions or spark.executor.extraJavaOptions, copy the .hprof to a volume, and open it in Eclipse MAT. It will be as big as the heap.
Docs:
https://docs.databricks.com/aws/en/structured-streaming/rocksdb-state-store
https://docs.databricks.com/aws/en/compute/cluster-metrics
// Suranga