This is an automated email from the ASF dual-hosted git repository.

cloud-fan pushed a commit to branch branch-4.x
in repository https://gitbox.apache.org/repos/asf/spark.git


The following commit(s) were added to refs/heads/branch-4.x by this push:
     new a7e43c6dbdb2 [SPARK-58659][SS] Set Real-Time Mode config defaults
a7e43c6dbdb2 is described below

commit a7e43c6dbdb255304c1525e534a85eaabfc41967
Author: Boyang Jerry Peng <[email protected]>
AuthorDate: Wed Aug 12 08:03:19 2026 +0800

    [SPARK-58659][SS] Set Real-Time Mode config defaults
    
    ### What changes were proposed in this pull request?
    
    This PR sets the configuration defaults a Real-Time Mode (RTM) streaming 
query needs, resolves the commit log format version from the checkpoint rather 
than blindly from the session config, and rejects incompatible session configs 
up front. All three are ported from the Databricks Runtime implementation of 
RTM.
    
    **1. RTM configuration defaults.** A single method 
`setSparkSessionConfigsForRealTimeMode`, run once at query start on the query's 
isolated session, defaults the settings RTM wants but that are not the right 
engine-wide default. Each is applied only when the user has not set the key, so 
an explicit choice is preserved:
    
    - `spark.sql.streaming.stateStore.checkpointFormatVersion=2` and the 
RocksDB state store provider that v2 requires, guarded independently (pinning 
the provider does not suppress the version default).
    - 
`spark.sql.streaming.stateStore.rocksdb.changelogCheckpointing.enabled=true` 
(RocksDB only), which shortens the state-commit step on the critical path 
between batches.
    - `spark.sql.execution.sortBeforeRepartition=false`, consolidated here from 
`MicroBatchExecution` (originally added for RTM in SPARK-58508). The local sort 
before a round-robin repartition never drains an unbounded stream, so it would 
hang an RTM query; RTM does not retry tasks, so the determinism it provides is 
not needed.
    
    **2. Commit log format version resolution.** Previously the commit log 
version was read from the session config at `CommitLog` construction 
(`CommitLog.defaultVersion`), which was forced early during `StreamExecution` 
initialization and ignored the format an existing checkpoint was created with. 
This PR removes that field and resolves the write version explicitly, mirroring 
the runtime:
    
    - `CommitLog.createMetadata` requires callers to pass the resolved version 
explicitly.
    - `CheckpointVersionManager` gains a `CommitLogType` and 
`resolveCommitLogVersion`: ordinary writes to an existing checkpoint keep its 
recorded version, while a fresh checkpoint uses the version implied by the 
state store checkpoint format. The sink-evolution path may independently 
upgrade writes to commit log v3.
    - The resolved version travels on the execution context 
(`commitLogFormatVersionOpt`) and is written on both the synchronous and the 
async-progress-tracking commit paths.
    
    **3. RTM requires commit log v2.** RTM re-executes a failed batch, and with 
checkpoint format v1 the re-execution can reuse the state file names of the 
partially-written failed batch and lose data; format v2 avoids this with 
per-batch state store checkpoint ids, which only a commit log at v2 or above 
can persist. Two guards enforce this, covering disjoint cases:
    
    - A pre-flight in `StreamingQueryManager` 
(`throwIfConfsAreRealTimeModeIncompatible`) rejects explicit RTM-incompatible 
session configs before the query is built (checkpoint format below v2, a 
non-RocksDB provider, or `sortBeforeRepartition=true`), with 
`STREAMING_REAL_TIME_MODE.SQL_CONFIGURATION_NOT_SUPPORTED`.
    - A fail-fast in `MicroBatchExecution.initializeExecution` rejects a 
resolved commit log version below v2 — the case of an existing v1 checkpoint, 
which carries no explicit config for the pre-flight to see — with 
`STREAMING_REAL_TIME_MODE.CHECKPOINT_FORMAT_V1_NOT_SUPPORTED`.
    
    Both are bypassed by the escape hatch 
`spark.sql.streaming.realTimeMode.dangerouslyAllowCheckpointV1.enabled` 
(default false), which lets a query start on a v1 checkpoint while accepting 
the data-loss risk on failure.
    
    One new internal config is added: 
`spark.sql.streaming.realTimeMode.dangerouslyAllowCheckpointV1.enabled` 
(default false).
    
    ### Why are the changes needed?
    
    RTM commits state at every batch boundary, and that commit sits on the 
critical path between batches, so RTM needs a low-latency, recovery-correct 
state store configuration (RocksDB + checkpoint format v2 + changelog 
checkpointing) that is not the right default for the engine as a whole. Without 
these defaults an RTM query either runs with a slower configuration or, in the 
`sortBeforeRepartition` case, does not make progress at all.
    
    Format v2 also matters for correctness: RTM re-executes a failed batch, and 
at v1 the re-execution can reuse the failed batch's state file names, so a 
stale file can be loaded (the checksum hazard documented on `StateStoreConf`). 
Resolving the commit log version from the checkpoint (rather than the session 
config) keeps ordinary writes at the format the checkpoint was created with, 
and the two guards prevent an RTM query from silently running on a v1 
checkpoint.
    
    This aligns OSS RTM with the Databricks Runtime, so the two share the same 
defaulting, version-resolution, and rejection behavior.
    
    ### Does this PR introduce _any_ user-facing change?
    
    Yes, for Real-Time Mode queries (`Trigger.RealTime(...)`) only. Behavior 
for every non-RTM query is unchanged.
    
    - An RTM query now defaults to the RocksDB state store, checkpoint format 
v2, and changelog checkpointing, when the user has not configured them. An 
explicit user setting is preserved.
    - Starting an RTM query with an explicit RTM-incompatible session config 
(checkpoint format below v2, a non-RocksDB provider, or 
`sortBeforeRepartition=true`) now fails at query start with 
`STREAMING_REAL_TIME_MODE.SQL_CONFIGURATION_NOT_SUPPORTED`.
    - Starting an RTM query on a checkpoint whose commit log is at version 1 
now fails at start with 
`STREAMING_REAL_TIME_MODE.CHECKPOINT_FORMAT_V1_NOT_SUPPORTED`.
    - The escape hatch 
`spark.sql.streaming.realTimeMode.dangerouslyAllowCheckpointV1.enabled=true` 
allows a v1 checkpoint to proceed, accepting the possibility of data loss on a 
failure.
    
    The new config is `internal()`. These are new-in-unreleased changes; there 
is no behavior change for released Spark versions, which have no Real-Time Mode.
    
    ### How was this patch tested?
    
    New and updated unit tests:
    
    - `StreamRealTimeModeDefaultConfsSuite` (new) covers: the v2 + RocksDB 
defaults are applied; the version and provider defaults are independent; 
explicit settings are preserved; an explicit incompatible config is rejected up 
front; the escape hatch bypasses the pre-flight; an existing v1 checkpoint is 
rejected; and the escape hatch allows it.
    - `CommitLogSuite` gains tests for fresh commit-log versions deriving from 
the state-store format, an existing checkpoint recorded version winning over 
the session setting, and commit metadata restoring the implied state-store 
format.
    - `StreamRealTimeModeSuite`'s `sortBeforeRepartition` test is updated to 
expect the up-front rejection.
    
    Verified against the existing RTM suites (`StreamRealTimeModeSuite`, 
`StreamRealTimeModeWithManualClockSuite`, the async-progress and streamline RTM 
suites) and, because the commit-log write path is shared, against 
`StreamingAggregationSuite` and `StreamingDeduplicationSuite` for non-RTM 
regressions.
    
    ### Was this patch authored or co-authored using generative AI tooling?
    
    co-authored with Claude Code
    
    Closes #57842 from jerrypeng/rtm-default-configs.
    
    Lead-authored-by: Boyang Jerry Peng <[email protected]>
    Co-authored-by: Wenchen Fan <[email protected]>
    Signed-off-by: Wenchen Fan <[email protected]>
    (cherry picked from commit a2d312ff9a19d66d6218459d49d1c1f805119dde)
    Signed-off-by: Wenchen Fan <[email protected]>
---
 .../src/main/resources/error/error-conditions.json |  11 +
 .../org/apache/spark/sql/internal/SQLConf.scala    |  23 ++
 .../spark/sql/classic/StreamingQueryManager.scala  |  51 ++++-
 .../checkpointing/CheckpointVersionManager.scala   |  60 +++++-
 .../streaming/checkpointing/CommitLog.scala        |  13 +-
 .../AsyncProgressTrackingMicroBatchExecution.scala |  21 +-
 .../streaming/runtime/MicroBatchExecution.scala    |  70 +++---
 .../streaming/runtime/StreamExecution.scala        |  89 +++++++-
 .../streaming/runtime/StreamExecutionContext.scala |   2 +
 .../execution/streaming/state/StateRewriter.scala  |   3 +-
 .../spark/sql/streaming/CommitLogSuite.scala       |  68 +++++-
 .../StreamRealTimeModeDefaultConfsSuite.scala      | 238 +++++++++++++++++++++
 .../sql/streaming/StreamRealTimeModeSuite.scala    |  22 +-
 .../test/StreamingSinkEvolutionSuite.scala         |  37 +++-
 14 files changed, 654 insertions(+), 54 deletions(-)

diff --git a/common/utils/src/main/resources/error/error-conditions.json 
b/common/utils/src/main/resources/error/error-conditions.json
index 099bffcf142e..db1bfd43ad23 100644
--- a/common/utils/src/main/resources/error/error-conditions.json
+++ b/common/utils/src/main/resources/error/error-conditions.json
@@ -7757,6 +7757,12 @@
           "The checkpointing interval for async progress tracking must be set 
to 0, which means each progress update is checkpointed. Set option 
asyncProgressTrackingCheckpointIntervalMs to 0 in DataStreamWriter options and 
retry your query."
         ]
       },
+      "CHECKPOINT_FORMAT_V1_NOT_SUPPORTED" : {
+        "message" : [
+          "Real-time mode does not support state store checkpoint format v1, 
because a re-executed batch can reuse the state file names of a 
partially-written failed batch and lose data. If you encountered this while 
switching an existing query to real-time mode, use a fresh checkpoint 
location.",
+          "If you must continue with your existing checkpoint and accept that 
doing so exposes you to the possibility of data loss on failure, set <config> 
to true."
+        ]
+      },
       "IDENTICAL_SOURCES_IN_UNION_NOT_SUPPORTED" : {
         "message" : [
           "Real-time mode does not support union on two or more identical 
streaming data sources in a single query. This includes scenarios such as 
referencing the same source DataFrame more than once, or using two data sources 
with identical configurations for some sources. For Kafka, avoid reusing the 
same DataFrame and create different ones. Sources provided in the query: 
<sources>"
@@ -7781,6 +7787,11 @@
         "message" : [
           "The <className> sink is currently not supported. See the Real-Time 
Mode User Guide for a list of supported sinks."
         ]
+      },
+      "SQL_CONFIGURATION_NOT_SUPPORTED" : {
+        "message" : [
+          "The following session configuration(s) are incompatible with 
Real-Time Mode: <invalidReasons>. Update or remove them and restart the query."
+        ]
       }
     },
     "sqlState" : "0A000"
diff --git 
a/sql/catalyst/src/main/scala/org/apache/spark/sql/internal/SQLConf.scala 
b/sql/catalyst/src/main/scala/org/apache/spark/sql/internal/SQLConf.scala
index 38bf897d9ba9..8803546e53f8 100644
--- a/sql/catalyst/src/main/scala/org/apache/spark/sql/internal/SQLConf.scala
+++ b/sql/catalyst/src/main/scala/org/apache/spark/sql/internal/SQLConf.scala
@@ -3624,6 +3624,21 @@ object SQLConf {
       .checkValue(v => Set(1, 2).contains(v), "Valid versions are 1 and 2")
       .createWithDefault(1)
 
+  val STREAMING_REAL_TIME_MODE_DANGEROUSLY_ALLOW_CHECKPOINT_V1 =
+    
buildConf("spark.sql.streaming.realTimeMode.dangerouslyAllowCheckpointV1.enabled")
+      .internal()
+      .doc("Whether to allow a Real-Time Mode query to start on a checkpoint 
whose commit log " +
+        "is at version 1. Real-Time Mode re-executes a failed batch, and with 
checkpoint format " +
+        "version 1 the re-execution can reuse the state file names of the 
partially-written " +
+        "failed batch, so starting on a version 1 checkpoint exposes the query 
to data loss on " +
+        "failure. Format version 2 avoids this with per-batch state store 
checkpoint ids, which " +
+        "only a commit log at version 2 or above can persist. Escape hatch 
only; prefer a fresh " +
+        "checkpoint location.")
+      .version("4.3.0")
+      .withBindingPolicy(ConfigBindingPolicy.NOT_APPLICABLE)
+      .booleanConf
+      .createWithDefault(false)
+
   val STREAMING_MAX_NUM_STATE_SCHEMA_FILES =
     buildConf("spark.sql.streaming.stateStore.maxNumStateSchemaFiles")
       .internal()
@@ -8787,6 +8802,14 @@ class SQLConf extends Serializable with Logging with 
SqlApiConf {
 
   def streamingOffsetLogFormatVersion: Int = 
getConf(STREAMING_OFFSET_LOG_FORMAT_VERSION)
 
+  // The commit log format version implied by the session config for a fresh 
checkpoint. A state
+  // store checkpoint format of v2 makes each batch write stateUniqueIds, 
which only a commit log at
+  // VERSION_2 or above can persist, so the commit log version tracks the 
state store format. It is
+  // capped at VERSION_2 here: VERSION_3 exists only to carry sink-evolution 
metadata and is written
+  // exclusively by the sink-evolution path in MicroBatchExecution, never 
derived from a config.
+  def streamingCommitLogFormatVersion: Int =
+    if (getConf(STATE_STORE_CHECKPOINT_FORMAT_VERSION) >= 2) 2 else 1
+
   def stateStoreEncodingFormat: String = 
getConf(STREAMING_STATE_STORE_ENCODING_FORMAT)
 
   def streamingValueStateSchemaEvolutionThreshold: Int =
diff --git 
a/sql/core/src/main/scala/org/apache/spark/sql/classic/StreamingQueryManager.scala
 
b/sql/core/src/main/scala/org/apache/spark/sql/classic/StreamingQueryManager.scala
index c934be69bc69..4755cfa9960a 100644
--- 
a/sql/core/src/main/scala/org/apache/spark/sql/classic/StreamingQueryManager.scala
+++ 
b/sql/core/src/main/scala/org/apache/spark/sql/classic/StreamingQueryManager.scala
@@ -35,7 +35,7 @@ import org.apache.spark.sql.errors.QueryExecutionErrors
 import org.apache.spark.sql.execution.streaming._
 import org.apache.spark.sql.execution.streaming.continuous.ContinuousExecution
 import 
org.apache.spark.sql.execution.streaming.runtime.{AsyncProgressTrackingMicroBatchExecution,
 MicroBatchExecution, StreamingQueryListenerBus, StreamingQueryWrapper}
-import org.apache.spark.sql.execution.streaming.state.StateStoreCoordinatorRef
+import 
org.apache.spark.sql.execution.streaming.state.{RocksDBStateStoreProvider, 
StateStoreCoordinatorRef}
 import org.apache.spark.sql.internal.SQLConf
 import org.apache.spark.sql.internal.StaticSQLConf.STREAMING_QUERY_LISTENERS
 import org.apache.spark.sql.streaming
@@ -186,6 +186,54 @@ class StreamingQueryManager private[sql] (
           
SQLConf.STREAMING_ASYNC_PROGRESS_TRACKING_REAL_TIME_MODE_ENABLED_BY_DEFAULT))
   }
 
+  /**
+   * Rejects, before the query is built, session configurations that are 
incompatible with
+   * Real-Time Mode. Real-Time Mode defaults these when the user has not set 
them
+   * (see StreamExecution.setSparkSessionConfigsForRealTimeMode), but an 
explicit incompatible
+   * value is a mistake worth surfacing up front rather than silently 
overriding or failing later
+   * mid-query. Mirrors the Databricks runtime's 
throwIfConfsAreRTMIncompatible, limited to the
+   * checks that apply in OSS.
+   *
+   *  - state store checkpoint format below v2 (unless the v1 escape hatch is 
set): Real-Time Mode
+   *    requires v2 (see the fail-fast in 
MicroBatchExecution.initializeExecution);
+   *  - a non-RocksDB state store provider: v2 requires RocksDB;
+   *  - sortBeforeRepartition left true: the blocking sort never completes on 
an unbounded stream.
+   */
+  private def throwIfConfsAreRealTimeModeIncompatible(sparkSession: 
SparkSession): Unit = {
+    val conf = sparkSession.sessionState.conf
+    // Preserve declaration order for a stable message.
+    val invalidReasons = new mutable.LinkedHashMap[String, String]
+
+    val allowCheckpointV1 =
+      
conf.getConf(SQLConf.STREAMING_REAL_TIME_MODE_DANGEROUSLY_ALLOW_CHECKPOINT_V1)
+    if (!allowCheckpointV1 &&
+        conf.contains(SQLConf.STATE_STORE_CHECKPOINT_FORMAT_VERSION.key) &&
+        conf.getConf(SQLConf.STATE_STORE_CHECKPOINT_FORMAT_VERSION) < 2) {
+      invalidReasons += (SQLConf.STATE_STORE_CHECKPOINT_FORMAT_VERSION.key -> 
"2 or above")
+    }
+
+    if (conf.contains(SQLConf.STATE_STORE_PROVIDER_CLASS.key) &&
+        conf.getConf(SQLConf.STATE_STORE_PROVIDER_CLASS) !=
+          classOf[RocksDBStateStoreProvider].getName) {
+      invalidReasons +=
+        (SQLConf.STATE_STORE_PROVIDER_CLASS.key -> 
classOf[RocksDBStateStoreProvider].getName)
+    }
+
+    if (conf.contains(SQLConf.SORT_BEFORE_REPARTITION.key) &&
+        conf.getConf(SQLConf.SORT_BEFORE_REPARTITION)) {
+      invalidReasons += (SQLConf.SORT_BEFORE_REPARTITION.key -> "false")
+    }
+
+    if (invalidReasons.nonEmpty) {
+      throw new SparkIllegalArgumentException(
+        errorClass = 
"STREAMING_REAL_TIME_MODE.SQL_CONFIGURATION_NOT_SUPPORTED",
+        messageParameters = Map(
+          "invalidReasons" -> invalidReasons.zipWithIndex.map {
+            case ((confName, req), index) => s"${index + 1}. $confName must be 
$req"
+          }.mkString("; ")))
+    }
+  }
+
   // scalastyle:off argcount
   private def createQuery(
       userSpecifiedName: Option[String],
@@ -217,6 +265,7 @@ class StreamingQueryManager private[sql] (
           )
         )
       }
+      throwIfConfsAreRealTimeModeIncompatible(sparkSession)
     }
 
     val dataStreamWritePlan = WriteToStreamStatement(
diff --git 
a/sql/core/src/main/scala/org/apache/spark/sql/execution/streaming/checkpointing/CheckpointVersionManager.scala
 
b/sql/core/src/main/scala/org/apache/spark/sql/execution/streaming/checkpointing/CheckpointVersionManager.scala
index 4f41f7cc46cf..7f7a614e9d33 100644
--- 
a/sql/core/src/main/scala/org/apache/spark/sql/execution/streaming/checkpointing/CheckpointVersionManager.scala
+++ 
b/sql/core/src/main/scala/org/apache/spark/sql/execution/streaming/checkpointing/CheckpointVersionManager.scala
@@ -35,6 +35,7 @@ case class StreamingCheckpointVersion(offsetLogVersion: Int) {
 
 sealed trait CheckpointLogType
 case object OffsetLogType extends CheckpointLogType
+case object CommitLogType extends CheckpointLogType
 
 /**
  * The `CheckpointVersionManager` is responsible for managing the versioning 
of the streaming
@@ -109,6 +110,35 @@ object CheckpointVersionManager extends Logging {
       logType: CheckpointLogType): Int = {
     logType match {
       case OffsetLogType => getOffsetLogVersion(sparkSessionForStream)
+      case CommitLogType => getCommitLogVersion(sparkSessionForStream)
+    }
+  }
+
+  /**
+   * The commit log format version requested by the session config.
+   * `streamingCommitLogFormatVersion` tracks 
[[SQLConf.STATE_STORE_CHECKPOINT_FORMAT_VERSION]]: a
+   * state store checkpoint format of v2 makes each batch write 
`stateUniqueIds`, which only a
+   * commit log at [[CommitLog.VERSION_2]] or above can persist, so a v2 state 
store format raises
+   * the commit log version to v2.
+   */
+  private def getCommitLogVersion(sparkSessionForStream: SparkSession): Int = {
+    val result = 
sparkSessionForStream.sessionState.conf.streamingCommitLogFormatVersion
+    logInfo(s"Retrieved commit log writer version=$result")
+    result
+  }
+
+  /**
+   * Determines the commit log format version for ordinary writes in this 
query run. An existing
+   * checkpoint wins, so a session config change cannot start writing a format 
the checkpoint was
+   * not created with. Only a fresh checkpoint takes the version from the 
session config. Sink
+   * evolution may independently upgrade writes to [[CommitLog.VERSION_3]].
+   */
+  def resolveCommitLogVersion(
+      sparkSessionForStream: SparkSession,
+      latestCommittedBatch: Option[(Long, CommitMetadataBase)]): Int = {
+    latestCommittedBatch match {
+      case Some((_, commitMetadata)) => commitMetadata.version
+      case None => getFormatVersionFromSession(sparkSessionForStream, 
CommitLogType)
     }
   }
 
@@ -142,10 +172,38 @@ object CheckpointVersionManager extends Logging {
   def setFormatVersion(
       sparkSessionForStream: SparkSession,
       logType: CheckpointLogType,
-      version: Int): Unit = {
+      version: Int,
+      commitMetadata: Option[CommitMetadataBase] = None): Unit = {
     logType match {
       case OffsetLogType =>
         setSparkSessionConfigsForOffsetLog(sparkSessionForStream, version)
+      case CommitLogType =>
+        setSparkSessionConfigsForCommitLog(sparkSessionForStream, version, 
commitMetadata)
+    }
+  }
+
+  /**
+   * Records the state store checkpoint format used by the existing commit 
log. VERSION_1 uses
+   * state store format v1 and VERSION_2 uses v2. VERSION_3 can represent 
either format because it
+   * was introduced independently for sink metadata; the presence of state 
store checkpoint ids
+   * distinguishes v2 from v1.
+   *
+   * The state store format is clamped to 2 rather than set to the commit log 
version, because the
+   * two version spaces are separate: the commit log has a VERSION_3 (sink 
metadata) with no state
+   * store counterpart, while the state store config accepts only versions 1 
and 2. Writing 3 here
+   * would report a state store format that does not exist.
+   */
+  private def setSparkSessionConfigsForCommitLog(
+      sparkSessionForStream: SparkSession,
+      commitLogFormatVersion: Int,
+      commitMetadata: Option[CommitMetadataBase]): Unit = {
+    val stateStoreVersion = commitMetadata match {
+      case Some(metadata) if metadata.version == CommitLog.VERSION_3 =>
+        if (metadata.stateUniqueIds.isDefined) 2 else 1
+      case _ =>
+        if (commitLogFormatVersion >= CommitLog.VERSION_2) 2 else 1
     }
+    sparkSessionForStream.conf
+      .set(SQLConf.STATE_STORE_CHECKPOINT_FORMAT_VERSION.key, 
stateStoreVersion.toString)
   }
 }
diff --git 
a/sql/core/src/main/scala/org/apache/spark/sql/execution/streaming/checkpointing/CommitLog.scala
 
b/sql/core/src/main/scala/org/apache/spark/sql/execution/streaming/checkpointing/CommitLog.scala
index b5271f664cd7..59ad6b986b0a 100644
--- 
a/sql/core/src/main/scala/org/apache/spark/sql/execution/streaming/checkpointing/CommitLog.scala
+++ 
b/sql/core/src/main/scala/org/apache/spark/sql/execution/streaming/checkpointing/CommitLog.scala
@@ -28,7 +28,6 @@ import org.json4s.jackson.Serialization
 import org.apache.spark.sql.SparkSession
 import org.apache.spark.sql.connector.read.streaming.{Offset => OffsetV2}
 import org.apache.spark.sql.errors.QueryExecutionErrors
-import org.apache.spark.sql.internal.SQLConf
 
 /**
  * Used to write log files that represent batch commit points in structured 
streaming.
@@ -56,11 +55,6 @@ class CommitLog(
 
   import CommitLog._
 
-  // The configured commit log format version. Used as the default version 
when callers
-  // construct metadata through [[createMetadata]].
-  private[sql] val defaultVersion: Int = sparkSession.conf.get(
-    SQLConf.STATE_STORE_CHECKPOINT_FORMAT_VERSION.key).toInt
-
   override protected[sql] def deserialize(in: InputStream): CommitMetadataBase 
= {
     CommitLog.readCommitMetadata(in)
   }
@@ -76,7 +70,10 @@ class CommitLog(
 
   /**
    * Factory for creating a [[CommitMetadataBase]] for the requested wire 
format version.
-   * Defaults to the version configured via 
[[SQLConf.STATE_STORE_CHECKPOINT_FORMAT_VERSION]].
+   *
+   * [[commitLogFormatVersion]] is a required parameter rather than a field 
read from the session
+   * config, so that a caller always supplies the version it resolved for this 
query run. Reading
+   * the config here would ignore the format an existing checkpoint was 
created with.
    *
    * For [[VERSION_3]], [[sinkMetadataMap]] must be non-empty and contain 
exactly one active
    * sink; [[CommitMetadataV3]] enforces this invariant.
@@ -85,7 +82,7 @@ class CommitLog(
       nextBatchWatermarkMs: Long = 0,
       stateUniqueIds: Option[Map[Long, Array[Array[String]]]] = None,
       sinkMetadataMap: Map[String, SinkMetadataInfo] = Map.empty,
-      commitLogFormatVersion: Int = defaultVersion): CommitMetadataBase = {
+      commitLogFormatVersion: Int): CommitMetadataBase = {
     commitLogFormatVersion match {
       case VERSION_3 =>
         CommitMetadataV3(nextBatchWatermarkMs, stateUniqueIds, sinkMetadataMap)
diff --git 
a/sql/core/src/main/scala/org/apache/spark/sql/execution/streaming/runtime/AsyncProgressTrackingMicroBatchExecution.scala
 
b/sql/core/src/main/scala/org/apache/spark/sql/execution/streaming/runtime/AsyncProgressTrackingMicroBatchExecution.scala
index d779f48f5e6e..a87c4c3574d1 100644
--- 
a/sql/core/src/main/scala/org/apache/spark/sql/execution/streaming/runtime/AsyncProgressTrackingMicroBatchExecution.scala
+++ 
b/sql/core/src/main/scala/org/apache/spark/sql/execution/streaming/runtime/AsyncProgressTrackingMicroBatchExecution.scala
@@ -26,7 +26,7 @@ import org.apache.spark.sql.catalyst.streaming.WriteToStream
 import org.apache.spark.sql.classic.SparkSession
 import org.apache.spark.sql.errors.QueryExecutionErrors
 import org.apache.spark.sql.execution.streaming.{AvailableNowTrigger, 
OneTimeTrigger, ProcessingTimeTrigger, RealTimeTrigger, StreamingErrors}
-import org.apache.spark.sql.execution.streaming.checkpointing.{AsyncCommitLog, 
AsyncOffsetSeqLog, CommitMetadata, OffsetSeqBase, OffsetSeqLog}
+import org.apache.spark.sql.execution.streaming.checkpointing.{AsyncCommitLog, 
AsyncOffsetSeqLog, CommitMetadataBase, OffsetSeqBase, OffsetSeqLog}
 import 
org.apache.spark.sql.execution.streaming.operators.stateful.StateStoreWriter
 import org.apache.spark.sql.streaming.Trigger
 import org.apache.spark.util.{Clock, ThreadUtils}
@@ -261,7 +261,7 @@ class AsyncProgressTrackingMicroBatchExecution(
         || isFirstBatch) {
         isFirstBatch = false
         commitLog
-          .addAsync(execCtx.batchId, 
CommitMetadata(watermarkTracker.currentWatermark))
+          .addAsync(execCtx.batchId, createAsyncCommitMetadata(execCtx))
           .thenAccept((batchId: Long) => {
             logInfo(log"Committed async commit log to disk for batch 
${MDC(BATCH_ID, batchId)}.")
           })
@@ -273,7 +273,7 @@ class AsyncProgressTrackingMicroBatchExecution(
           })
       } else {
         if (!commitLog.addInMemory(
-          execCtx.batchId, CommitMetadata(watermarkTracker.currentWatermark))) 
{
+          execCtx.batchId, createAsyncCommitMetadata(execCtx))) {
           throw QueryExecutionErrors.concurrentStreamLogUpdate(execCtx.batchId)
         }
         logInfo(
@@ -285,6 +285,21 @@ class AsyncProgressTrackingMicroBatchExecution(
     committedOffsets ++= execCtx.endOffsets
   }
 
+  /**
+   * Builds the commit log entry for an async batch at the version resolved 
for this run, so that a
+   * Real-Time Mode query writes the commit log format its state store 
checkpoint format implies
+   * (v2 for the RTM default) rather than always VERSION_1. Async progress 
tracking does not support
+   * stateful queries, so there are no state store checkpoint ids to persist.
+   */
+  private def createAsyncCommitMetadata(
+      execCtx: MicroBatchExecutionContext): CommitMetadataBase = {
+    commitLog.createMetadata(
+      nextBatchWatermarkMs = watermarkTracker.currentWatermark,
+      stateUniqueIds = None,
+      commitLogFormatVersion = execCtx.commitLogFormatVersionOpt.getOrElse(
+        
sparkSessionForStream.sessionState.conf.streamingCommitLogFormatVersion))
+  }
+
   /**
    * Categorize a raw IO failure surfaced via an async log-write future, then 
route it through
    * the standard async error handling path. CompletableFuture wraps the 
underlying cause in
diff --git 
a/sql/core/src/main/scala/org/apache/spark/sql/execution/streaming/runtime/MicroBatchExecution.scala
 
b/sql/core/src/main/scala/org/apache/spark/sql/execution/streaming/runtime/MicroBatchExecution.scala
index 8621271768f4..f0201ccf302a 100644
--- 
a/sql/core/src/main/scala/org/apache/spark/sql/execution/streaming/runtime/MicroBatchExecution.scala
+++ 
b/sql/core/src/main/scala/org/apache/spark/sql/execution/streaming/runtime/MicroBatchExecution.scala
@@ -46,7 +46,7 @@ import org.apache.spark.sql.execution.{SparkPlan, 
SQLExecution}
 import org.apache.spark.sql.execution.datasources.LogicalRelation
 import org.apache.spark.sql.execution.datasources.v2.{DataSourceV2Relation, 
RealTimeStreamScanExec, StreamingDataSourceV2Relation, 
StreamingDataSourceV2ScanRelation, StreamWriterCommitProgress, 
WriteToDataSourceV2Exec}
 import org.apache.spark.sql.execution.streaming.{AvailableNowTrigger, Offset, 
OneTimeTrigger, ProcessingTimeTrigger, RealTimeTrigger, Sink, Source, 
StreamingQueryPlanTraverseHelper}
-import 
org.apache.spark.sql.execution.streaming.checkpointing.{CheckpointFileManager, 
CheckpointVersionManager, CommitLog, CommitMetadataV3, OffsetLogType, 
OffsetSeqBase, OffsetSeqLog, OffsetSeqMetadata, OffsetSeqMetadataV2, 
SinkMetadataInfo}
+import 
org.apache.spark.sql.execution.streaming.checkpointing.{CheckpointFileManager, 
CheckpointVersionManager, CommitLog, CommitLogType, CommitMetadataV3, 
OffsetLogType, OffsetSeqBase, OffsetSeqLog, OffsetSeqMetadata, 
OffsetSeqMetadataV2, SinkMetadataInfo}
 import 
org.apache.spark.sql.execution.streaming.operators.stateful.{StatefulOperatorStateInfo,
 StatefulOpStateStoreCheckpointInfo, StateStoreWriter}
 import 
org.apache.spark.sql.execution.streaming.runtime.StreamingCheckpointConstants.{DIR_NAME_COMMITS,
 DIR_NAME_OFFSETS, DIR_NAME_STATE}
 import org.apache.spark.sql.execution.streaming.sources.{ForeachBatchSink, 
WriteToMicroBatchDataSource, WriteToMicroBatchDataSourceV1}
@@ -82,6 +82,7 @@ class MicroBatchExecution(
       -1,
       sparkSession,
       offsetLogFormatVersionOpt = None,
+      commitLogFormatVersionOpt = None,
       previousContext = None)
 
   override def getLatestExecutionContext(): StreamExecutionContext = 
latestExecutionContext
@@ -371,31 +372,6 @@ class MicroBatchExecution(
             messageParameters = Map("className" -> s.getClass.getName)
           )
       }
-
-      // `repartition(n)` uses RoundRobinPartitioning, and by default 
(SPARK-23207) Spark inserts a
-      // local sort before a round-robin shuffle so the row-to-partition 
assignment is deterministic
-      // -- otherwise a retried task could emit rows in a different order and 
lose data. That sort
-      // is fully blocking: it drains its whole input before emitting a row. A 
Real-Time Mode task
-      // reads an unbounded stream, so the input never ends, the producer 
never emits, and the query
-      // makes no progress at all. Note this sort is not a SortExec node (it 
lives inside
-      // ShuffleExchangeExec's RDD), so the Real-Time Mode operator allowlist 
cannot catch it.
-      //
-      // Determinism is not needed here: Real-Time Mode does not retry tasks 
(TaskSetManager caps a
-      // pipelined task set at one attempt, and its group is aborted as a unit 
rather than
-      // recomputed), so the hazard the sort guards against does not arise.
-      //
-      // This overrides an explicit `true` as well. Honouring it would 
reintroduce the hang above,
-      // and a query that never produces a row is worse than silently ignoring 
a config that only
-      // guards against a retry that cannot happen here. Log at warning level 
when overriding an
-      // explicit setting so the operator can see their config is not the one 
in force.
-      if 
(sparkSessionForStream.conf.contains(SQLConf.SORT_BEFORE_REPARTITION.key) &&
-        sparkSessionForStream.conf.get(SQLConf.SORT_BEFORE_REPARTITION)) {
-        logWarning(log"Ignoring ${MDC(LogKeys.CONFIG, 
SQLConf.SORT_BEFORE_REPARTITION.key)}=true " +
-          log"for this Real-Time Mode query: the local sort it inserts before 
a round-robin " +
-          log"repartition never completes on an unbounded stream. It is not 
needed because a " +
-          log"Real-Time Mode task is not retried.")
-      }
-      sparkSessionForStream.conf.set(SQLConf.SORT_BEFORE_REPARTITION.key, 
"false")
     }
 
     // Initializing TriggerExecutor relies on `sources`, hence calling this 
after initializing
@@ -579,9 +555,47 @@ class MicroBatchExecution(
     CheckpointVersionManager.setFormatVersion(
       sparkSessionForStream, OffsetLogType, offsetLogFormatVersion)
 
+    // Resolve the commit log format version the same way: an existing 
checkpoint keeps the version
+    // it was created with, and only a fresh one takes the version from the 
session config. Persist
+    // the implied state store checkpoint format so it agrees with what is 
actually being written.
+    val commitLogFormatVersion = 
CheckpointVersionManager.resolveCommitLogVersion(
+      sparkSessionForStream, latestCommittedBatch)
+    CheckpointVersionManager.setFormatVersion(
+      sparkSessionForStream,
+      CommitLogType,
+      commitLogFormatVersion,
+      latestCommittedBatch.map(_._2))
+
+    // Real-Time Mode requires commit log v2. Real-Time Mode writes the offset 
log at batch end
+    // (markMicroBatchStart is a no-op for it), so a mid-batch failure can 
leave durable state at a
+    // version that was never logged; the re-execution then rewrites that same 
state version. With
+    // checkpoint format v1 the rewritten files reuse the same names as the 
orphaned ones, so a load
+    // can pick up a stale file (see the checksum hazard documented on
+    // StateStoreConf.skipChecksumOnFileMissingChecksum). Format v2 avoids 
this because each batch
+    // run generates unique state store checkpoint ids, and only a commit log 
at v2 or above can
+    // persist them. Resolution above keeps an existing checkpoint at the 
version it was created
+    // with, so a v1 checkpoint stays v1. Reject any Real-Time Mode query 
whose resolved commit log
+    // version is below v2, with an escape hatch. This is unconditional, 
matching the Databricks
+    // runtime -- a fresh checkpoint reaches v1 here only when the user 
explicitly pinned it, which
+    // is exactly the case worth rejecting.
+    if (trigger.isInstanceOf[RealTimeTrigger] && commitLogFormatVersion < 
CommitLog.VERSION_2) {
+      if (!sparkSessionForStream.sessionState.conf
+          
.getConf(SQLConf.STREAMING_REAL_TIME_MODE_DANGEROUSLY_ALLOW_CHECKPOINT_V1)) {
+        throw new SparkIllegalArgumentException(
+          errorClass = 
"STREAMING_REAL_TIME_MODE.CHECKPOINT_FORMAT_V1_NOT_SUPPORTED",
+          messageParameters = Map(
+            "config" -> 
SQLConf.STREAMING_REAL_TIME_MODE_DANGEROUSLY_ALLOW_CHECKPOINT_V1.key))
+      }
+      logWarning(log"Starting a Real-Time Mode query on a commit log at 
version " +
+        log"${MDC(LogKeys.FILE_VERSION, commitLogFormatVersion)} because " +
+        log"${MDC(LogKeys.CONFIG, 
SQLConf.STREAMING_REAL_TIME_MODE_DANGEROUSLY_ALLOW_CHECKPOINT_V1
+          .key)} is set. A failed batch may lose data on rerun.")
+    }
+
     val execCtx = new MicroBatchExecutionContext(id, runId, name, 
triggerClock, sources, sink,
       progressReporter, -1, sparkSession,
       offsetLogFormatVersionOpt = Some(offsetLogFormatVersion),
+      commitLogFormatVersionOpt = Some(commitLogFormatVersion),
       previousContext = None)
 
     execCtx.offsetSeqMetadata = offsetLogFormatVersion match {
@@ -1524,7 +1538,9 @@ class MicroBatchExecution(
       } else {
         commitLog.createMetadata(
           nextBatchWatermarkMs = watermarkTracker.currentWatermark,
-          stateUniqueIds = stateStoreCkptId)
+          stateUniqueIds = stateStoreCkptId,
+          commitLogFormatVersion = execCtx.commitLogFormatVersionOpt.getOrElse(
+            
sparkSessionForStream.sessionState.conf.streamingCommitLogFormatVersion))
       }
       if (!commitLog.add(execCtx.batchId, metadata)) {
         throw QueryExecutionErrors.concurrentStreamLogUpdate(execCtx.batchId)
diff --git 
a/sql/core/src/main/scala/org/apache/spark/sql/execution/streaming/runtime/StreamExecution.scala
 
b/sql/core/src/main/scala/org/apache/spark/sql/execution/streaming/runtime/StreamExecution.scala
index 38b4a58385eb..cef636cc58b1 100644
--- 
a/sql/core/src/main/scala/org/apache/spark/sql/execution/streaming/runtime/StreamExecution.scala
+++ 
b/sql/core/src/main/scala/org/apache/spark/sql/execution/streaming/runtime/StreamExecution.scala
@@ -34,7 +34,7 @@ import org.apache.logging.log4j.CloseableThreadContext
 
 import org.apache.spark.{JobArtifactSet, SparkContext, SparkException, 
SparkThrowable}
 import org.apache.spark.internal.Logging
-import org.apache.spark.internal.LogKeys.{CHECKPOINT_PATH, CHECKPOINT_ROOT, 
LOGICAL_PLAN, PATH, PRETTY_ID_STRING, QUERY_ID, RUN_ID, SPARK_DATA_STREAM}
+import org.apache.spark.internal.LogKeys.{CHECKPOINT_PATH, CHECKPOINT_ROOT, 
CONFIG, LOGICAL_PLAN, PATH, PRETTY_ID_STRING, QUERY_ID, RUN_ID, 
SPARK_DATA_STREAM}
 import org.apache.spark.sql.catalyst.plans.logical.LogicalPlan
 import org.apache.spark.sql.catalyst.streaming.InternalOutputModes._
 import org.apache.spark.sql.classic.{SparkSession, StreamingQuery}
@@ -43,10 +43,11 @@ import 
org.apache.spark.sql.connector.read.streaming.{Offset => OffsetV2, ReadLi
 import org.apache.spark.sql.connector.write.{LogicalWriteInfoImpl, 
SupportsTruncate, Write}
 import org.apache.spark.sql.execution.SparkPlan
 import org.apache.spark.sql.execution.command.StreamingExplainCommand
-import org.apache.spark.sql.execution.streaming.ContinuousTrigger
+import org.apache.spark.sql.execution.streaming.{ContinuousTrigger, 
RealTimeTrigger}
 import 
org.apache.spark.sql.execution.streaming.checkpointing.{CheckpointFileManager, 
CommitLog, OffsetSeqLog, OffsetSeqMetadata}
 import 
org.apache.spark.sql.execution.streaming.operators.stateful.{StatefulOperator, 
StateStoreWriter}
 import 
org.apache.spark.sql.execution.streaming.sources.{ForeachBatchUserFuncException,
 ForeachUserFuncException}
+import org.apache.spark.sql.execution.streaming.state.{RocksDBConf, 
RocksDBStateStoreProvider}
 import 
org.apache.spark.sql.execution.streaming.state.OperatorStateMetadataV2FileManager
 import org.apache.spark.sql.internal.SQLConf
 import org.apache.spark.sql.internal.connector.SupportsStreamingUpdateAsAppend
@@ -325,6 +326,10 @@ abstract class StreamExecution(
           
sparkSessionForStream.conf.set(SQLConf.ADAPTIVE_EXECUTION_ENABLED.key, "false")
         }
 
+        if (trigger.isInstanceOf[RealTimeTrigger]) {
+          setSparkSessionConfigsForRealTimeMode(sparkSessionForStream)
+        }
+
         
sparkSessionForStream.conf.get(SQLConf.STATEFUL_SHUFFLE_PARTITIONS_INTERNAL) 
match {
           case Some(_) => // no-op
           case None =>
@@ -458,6 +463,86 @@ abstract class StreamExecution(
     }
   }
 
+  /**
+   * Applies the configuration a Real-Time Mode query needs but that is not 
the engine-wide default,
+   * because it is only the right choice for a low-latency, long-running 
batch. Runs once at query
+   * start, before the logical plan is forced, so a config read during 
planning sees the final
+   * value.
+   *
+   * Every setting here is a SOFT DEFAULT: applied only when the user has not 
set the key, so an
+   * explicit choice always wins. The state store settings, 
`changelogCheckpointing`, and
+   * `sortBeforeRepartition` are these. Except for checkpoint format v1 when 
its escape hatch is
+   * enabled, an explicit value that is incompatible with Real-Time Mode is 
rejected up front by the
+   * preflight in StreamingQueryManager 
(throwIfConfsAreRealTimeModeIncompatible). Therefore, an
+   * explicit value that reaches this method is safe to keep or was explicitly 
allowed.
+   *
+   * Every default applied here is logged, so those changes are recoverable 
from the driver log.
+   *
+   * Deliberately not set, though Databricks Runtime does default them for 
Real-Time Mode:
+   *  - The incremental state-cleanup factor: that mechanism does not exist in 
OSS yet.
+   *  - The Python/Pandas UDF latency knobs: those configs do not exist in OSS.
+   */
+  private def setSparkSessionConfigsForRealTimeMode(sparkSessionForStream: 
SparkSession): Unit = {
+    val conf = sparkSessionForStream.conf
+
+    // SOFT DEFAULTS. Real-Time Mode benefits from state store checkpoint 
format v2: it gives each
+    // batch run its own state store checkpoint ids, which prevents a 
re-executed batch from reusing
+    // the state file names of a partially-written failed batch (see the v1 
hazard described at the
+    // fail-fast in MicroBatchExecution.initializeExecution). v2 requires the 
RocksDB state store
+    // provider. These are defaulted with two INDEPENDENT guards, matching the 
Databricks runtime:
+    // each key is set only if the user has not set that key. An explicit 
non-RocksDB provider has
+    // already been rejected by the pre-flight in StreamingQueryManager
+    // (throwIfConfsAreRealTimeModeIncompatible), so by the time this runs the 
provider is either
+    // unset (defaulted to RocksDB just below) or already RocksDB; the version 
default is applied
+    // independently of the provider default.
+    val checkpointVersionKey = 
SQLConf.STATE_STORE_CHECKPOINT_FORMAT_VERSION.key
+    if (!conf.contains(checkpointVersionKey)) {
+      logInfo(log"Real-Time Mode: defaulting ${MDC(CONFIG, 
checkpointVersionKey)}=2")
+      conf.set(checkpointVersionKey, CommitLog.VERSION_2.toString)
+    }
+    val providerKey = SQLConf.STATE_STORE_PROVIDER_CLASS.key
+    if (!conf.contains(providerKey)) {
+      logInfo(log"Real-Time Mode: defaulting " +
+        log"${MDC(CONFIG, providerKey)}=RocksDBStateStoreProvider")
+      conf.set(providerKey, classOf[RocksDBStateStoreProvider].getName)
+    }
+
+    // SOFT DEFAULT. Changelog checkpointing writes a changelog rather than a 
full snapshot on each
+    // commit, which shortens the state-commit step at a batch boundary. That 
step is on the
+    // critical path between batches in Real-Time Mode, so this is a latency 
optimization. It is
+    // only meaningful with RocksDB, hence the check against the provider 
actually in force.
+    val changelogKey = 
s"${RocksDBConf.ROCKSDB_SQL_CONF_NAME_PREFIX}.changelogCheckpointing.enabled"
+    val usingRocksDb =
+      conf.get(SQLConf.STATE_STORE_PROVIDER_CLASS.key,
+        SQLConf.STATE_STORE_PROVIDER_CLASS.defaultValueString) ==
+        classOf[RocksDBStateStoreProvider].getName
+    if (usingRocksDb && !conf.contains(changelogKey)) {
+      logInfo(log"Real-Time Mode: defaulting ${MDC(CONFIG, 
changelogKey)}=true")
+      conf.set(changelogKey, "true")
+    }
+
+    // SOFT DEFAULT. `repartition(n)` uses RoundRobinPartitioning, and by 
default (SPARK-23207)
+    // Spark inserts a local sort before a round-robin shuffle so the 
row-to-partition assignment is
+    // deterministic -- otherwise a retried task could emit rows in a 
different order and lose data.
+    // That sort is fully blocking: it drains its whole input before emitting 
a row. A Real-Time
+    // Mode task reads an unbounded stream, so the input never ends, the 
producer never emits, and
+    // the query makes no progress at all. Note this sort is not a SortExec 
node (it lives inside
+    // ShuffleExchangeExec's RDD), so the Real-Time Mode operator allowlist 
cannot catch it.
+    //
+    // Determinism is not needed here: Real-Time Mode does not retry tasks 
(TaskSetManager caps a
+    // pipelined task set at one attempt, and its group is aborted as a unit 
rather than
+    // recomputed), so the hazard the sort guards against does not arise.
+    //
+    // This is a soft default rather than an override: an explicit `true` is 
rejected up front by
+    // the pre-flight in StreamingQueryManager 
(throwIfConfsAreRealTimeModeIncompatible), so by the
+    // time we get here an explicit value can only be `false`. Matches the 
Databricks runtime.
+    if (!conf.contains(SQLConf.SORT_BEFORE_REPARTITION.key)) {
+      logInfo(log"Real-Time Mode: defaulting " +
+        log"${MDC(CONFIG, SQLConf.SORT_BEFORE_REPARTITION.key)}=false")
+      conf.set(SQLConf.SORT_BEFORE_REPARTITION.key, "false")
+    }
+  }
+
   private def isInterruptedByStop(e: Throwable, sc: SparkContext): Boolean = {
     if (state.get == TERMINATED) {
       StreamExecution.isInterruptionException(e, sc)
diff --git 
a/sql/core/src/main/scala/org/apache/spark/sql/execution/streaming/runtime/StreamExecutionContext.scala
 
b/sql/core/src/main/scala/org/apache/spark/sql/execution/streaming/runtime/StreamExecutionContext.scala
index a7061d14d334..1c2fa064ca88 100644
--- 
a/sql/core/src/main/scala/org/apache/spark/sql/execution/streaming/runtime/StreamExecutionContext.scala
+++ 
b/sql/core/src/main/scala/org/apache/spark/sql/execution/streaming/runtime/StreamExecutionContext.scala
@@ -128,6 +128,7 @@ class MicroBatchExecutionContext(
     var _batchId: Long,
     sparkSession: SparkSession,
     val offsetLogFormatVersionOpt: Option[Int],
+    val commitLogFormatVersionOpt: Option[Int],
     var previousContext: Option[MicroBatchExecutionContext])
   extends StreamExecutionContext(
     id,
@@ -192,6 +193,7 @@ class MicroBatchExecutionContext(
       batchId + 1,
       sparkSession,
       offsetLogFormatVersionOpt,
+      commitLogFormatVersionOpt,
       Some(this))
   }
 
diff --git 
a/sql/core/src/main/scala/org/apache/spark/sql/execution/streaming/state/StateRewriter.scala
 
b/sql/core/src/main/scala/org/apache/spark/sql/execution/streaming/state/StateRewriter.scala
index 546a9a601964..3280671851e3 100644
--- 
a/sql/core/src/main/scala/org/apache/spark/sql/execution/streaming/state/StateRewriter.scala
+++ 
b/sql/core/src/main/scala/org/apache/spark/sql/execution/streaming/state/StateRewriter.scala
@@ -381,7 +381,8 @@ class StateRewriter(
     // StateRewriter from writing state files in a format that disagrees with 
the source
     // checkpoint. Using the read batch commit since the latest commit could 
be a skipped batch.
     readCheckpoint.commitLog.get(readBatchId).foreach { metadata =>
-      val configuredVersion = readCheckpoint.commitLog.defaultVersion
+      val configuredVersion =
+        
sparkSession.sessionState.conf.getConf(SQLConf.STATE_STORE_CHECKPOINT_FORMAT_VERSION)
       if (metadata.version != configuredVersion) {
         throw StateRewriterErrors.stateCheckpointFormatVersionMismatchError(
           checkpointLocationForRead,
diff --git 
a/sql/core/src/test/scala/org/apache/spark/sql/streaming/CommitLogSuite.scala 
b/sql/core/src/test/scala/org/apache/spark/sql/streaming/CommitLogSuite.scala
index ba785aa830ab..a733bd6d7571 100644
--- 
a/sql/core/src/test/scala/org/apache/spark/sql/streaming/CommitLogSuite.scala
+++ 
b/sql/core/src/test/scala/org/apache/spark/sql/streaming/CommitLogSuite.scala
@@ -20,7 +20,7 @@ package org.apache.spark.sql.streaming
 import java.io.{ByteArrayInputStream, FileInputStream, FileOutputStream}
 import java.nio.file.Path
 
-import org.apache.spark.sql.execution.streaming.checkpointing.{CommitLog, 
CommitMetadata, CommitMetadataBase, CommitMetadataV2, CommitMetadataV3, 
OffsetSeqLog, SinkMetadataInfo}
+import 
org.apache.spark.sql.execution.streaming.checkpointing.{CheckpointVersionManager,
 CommitLog, CommitLogType, CommitMetadata, CommitMetadataBase, 
CommitMetadataV2, CommitMetadataV3, OffsetSeqLog, SinkMetadataInfo}
 import org.apache.spark.sql.internal.SQLConf
 import org.apache.spark.sql.test.SharedSparkSession
 
@@ -250,4 +250,70 @@ class CommitLogSuite extends SharedSparkSession {
         commitLogFormatVersion = CommitLog.VERSION_1).version === 
CommitLog.VERSION_1)
     }
   }
+
+  /** The commit log version the session config asks for, via the public 
resolution entry point. */
+  private def sessionCommitLogVersion(): Int = {
+    CheckpointVersionManager.resolveCommitLogVersion(spark, 
latestCommittedBatch = None)
+  }
+
+  test("commit log version derives from the state store checkpoint format") {
+    // Nothing set: defaults to VERSION_1.
+    assert(sessionCommitLogVersion() === CommitLog.VERSION_1)
+
+    // State store checkpoint format v2 makes each batch write stateUniqueIds, 
which only a commit
+    // log at VERSION_2 or above can persist, so it raises the commit log 
version to v2.
+    withSQLConf(SQLConf.STATE_STORE_CHECKPOINT_FORMAT_VERSION.key -> "2") {
+      assert(sessionCommitLogVersion() === CommitLog.VERSION_2,
+        "state store v2 must raise the commit log to v2")
+    }
+
+    // The resolved version is capped at VERSION_2: VERSION_3 exists only to 
carry sink-evolution
+    // metadata and is written exclusively by the sink-evolution path, never 
derived from a config.
+    withSQLConf(SQLConf.STATE_STORE_CHECKPOINT_FORMAT_VERSION.key -> "3") {
+      assert(sessionCommitLogVersion() === CommitLog.VERSION_2,
+        "a config-derived commit log version must never resolve to v3")
+    }
+  }
+
+  test("an existing checkpoint's commit log version wins over the session 
config") {
+    // The whole point of resolution: a commit log created at one version 
keeps being written at
+    // that version, so a higher state store checkpoint format cannot start 
writing a format the
+    // checkpoint lacks.
+    withSQLConf(SQLConf.STATE_STORE_CHECKPOINT_FORMAT_VERSION.key -> "2") {
+      val existing: CommitMetadataBase = CommitMetadata(nextBatchWatermarkMs = 
0)
+      assert(existing.version === CommitLog.VERSION_1)
+      val resolved =
+        CheckpointVersionManager.resolveCommitLogVersion(spark, Some((7L, 
existing)))
+      assert(resolved === CommitLog.VERSION_1,
+        s"an existing V1 commit log must stay V1 even with the state store at 
v2, got $resolved")
+    }
+  }
+
+  test("recording a commit log version sets the implied state store format") {
+    val sinkMetadataMap = Map("sink" -> SinkMetadataInfo(
+      sinkName = "sink",
+      commitOffset = OffsetSeqLog.SERIALIZED_VOID_OFFSET,
+      providerName = "provider",
+      apiVersion = "DSv2"))
+    val session = spark.cloneSession()
+    val v3WithStateIds = CommitMetadataV3(0, Some(Map.empty), sinkMetadataMap)
+    CheckpointVersionManager.setFormatVersion(
+      session, CommitLogType, CommitLog.VERSION_3, Some(v3WithStateIds))
+    assert(session.conf.get(SQLConf.STATE_STORE_CHECKPOINT_FORMAT_VERSION.key) 
=== "2",
+      "state checkpoint ids in a V3 commit imply state store format v2")
+
+    val v3WithoutStateIdsSession = spark.cloneSession()
+    val v3WithoutStateIds = CommitMetadataV3(0, None, sinkMetadataMap)
+    CheckpointVersionManager.setFormatVersion(
+      v3WithoutStateIdsSession, CommitLogType, CommitLog.VERSION_3, 
Some(v3WithoutStateIds))
+    assert(v3WithoutStateIdsSession.conf.get(
+      SQLConf.STATE_STORE_CHECKPOINT_FORMAT_VERSION.key) === "1",
+      "a V3 commit without state checkpoint ids must preserve state store 
format v1")
+
+    val v1Session = spark.cloneSession()
+    CheckpointVersionManager.setFormatVersion(v1Session, CommitLogType, 
CommitLog.VERSION_1)
+    
assert(v1Session.conf.get(SQLConf.STATE_STORE_CHECKPOINT_FORMAT_VERSION.key) 
=== "1",
+      "a V1 commit log cannot carry state store checkpoint ids, so the state 
store must be v1")
+  }
+
 }
diff --git 
a/sql/core/src/test/scala/org/apache/spark/sql/streaming/StreamRealTimeModeDefaultConfsSuite.scala
 
b/sql/core/src/test/scala/org/apache/spark/sql/streaming/StreamRealTimeModeDefaultConfsSuite.scala
new file mode 100644
index 000000000000..5b84f4cfa03d
--- /dev/null
+++ 
b/sql/core/src/test/scala/org/apache/spark/sql/streaming/StreamRealTimeModeDefaultConfsSuite.scala
@@ -0,0 +1,238 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.spark.sql.streaming
+
+import scala.jdk.CollectionConverters._
+
+import org.apache.spark.{SparkIllegalArgumentException, SparkThrowable}
+import org.apache.spark.sql.execution.streaming.sources.{ContinuousMemorySink, 
LowLatencyMemoryStream}
+import 
org.apache.spark.sql.execution.streaming.state.{HDFSBackedStateStoreProvider, 
RocksDBConf,
+  RocksDBStateStoreProvider}
+import org.apache.spark.sql.internal.SQLConf
+
+/**
+ * Tests the configuration defaults a Real-Time Mode query applies at query 
start.
+ *
+ * These are not engine-wide defaults because they are only the right choice 
for a low-latency,
+ * long-running batch. Each is applied only when the user has not set it, so 
an explicit choice
+ * always wins -- both directions are asserted here, since a defaulting block 
that silently
+ * overrode a user's setting would be worse than having no default at all.
+ */
+class StreamRealTimeModeDefaultConfsSuite extends StreamRealTimeModeSuiteBase {
+
+  import testImplicits._
+
+  private val changelogKey =
+    
s"${RocksDBConf.ROCKSDB_SQL_CONF_NAME_PREFIX}.changelogCheckpointing.enabled"
+
+  /** Runs a trivial RTM query to completion and returns the effective conf 
values afterwards. */
+  private def runRealTimeQueryAndReadConfs(keys: Seq[String]): Map[String, 
Option[String]] = {
+    val inputData = LowLatencyMemoryStream[Int]
+    val mapped = inputData.toDS().map(_ + 1)
+    var observed: Map[String, Option[String]] = Map.empty
+
+    testStream(mapped, OutputMode.Update, Map.empty, new 
ContinuousMemorySink())(
+      AddData(inputData, 1, 2, 3),
+      StartStream(),
+      CheckAnswerWithTimeout(60000, 2, 3, 4),
+      Execute { q =>
+        // Read from sparkSessionForStream, the isolated CLONE the batches 
actually run with
+        // (StreamExecution.sparkSessionForStream). The defaults are applied 
there, not to the
+        // caller's session, so reading `q.sparkSession` would observe the 
unmodified original.
+        val conf = q.sparkSessionForStream.conf
+        observed = keys.map(k => k -> conf.getOption(k)).toMap
+      },
+      StopStream
+    )
+    observed
+  }
+
+  test("a Real-Time Mode query defaults to checkpoint format v2 with the 
RocksDB state store") {
+    val keys = Seq(
+      SQLConf.STATE_STORE_CHECKPOINT_FORMAT_VERSION.key,
+      SQLConf.STATE_STORE_PROVIDER_CLASS.key)
+    val observed = runRealTimeQueryAndReadConfs(keys)
+
+    
assert(observed(SQLConf.STATE_STORE_CHECKPOINT_FORMAT_VERSION.key).contains("2"),
+      "Real-Time Mode should default the state-store checkpoint format to v2, 
got " +
+        observed(SQLConf.STATE_STORE_CHECKPOINT_FORMAT_VERSION.key))
+    assert(observed(SQLConf.STATE_STORE_PROVIDER_CLASS.key)
+        .contains(classOf[RocksDBStateStoreProvider].getName),
+      "Real-Time Mode should default the state-store provider to RocksDB, got 
" +
+        observed(SQLConf.STATE_STORE_PROVIDER_CLASS.key))
+  }
+
+  test("the checkpoint-format and provider defaults are applied 
independently") {
+    // The two are defaulted by independent guards (matching the runtime): 
pinning one does not
+    // suppress the other. Pin only the version (to a compatible v2, since an 
incompatible explicit
+    // config is rejected up front by the pre-flight) and confirm the provider 
is still defaulted.
+    withSQLConf(SQLConf.STATE_STORE_CHECKPOINT_FORMAT_VERSION.key -> "2") {
+      val observed = runRealTimeQueryAndReadConfs(
+        Seq(SQLConf.STATE_STORE_PROVIDER_CLASS.key,
+          SQLConf.STATE_STORE_CHECKPOINT_FORMAT_VERSION.key))
+      
assert(observed(SQLConf.STATE_STORE_CHECKPOINT_FORMAT_VERSION.key).contains("2"),
+        "the pinned version must be left alone, got " +
+          observed(SQLConf.STATE_STORE_CHECKPOINT_FORMAT_VERSION.key))
+      assert(observed(SQLConf.STATE_STORE_PROVIDER_CLASS.key)
+          .contains(classOf[RocksDBStateStoreProvider].getName),
+        "the provider default is independent of the version, so it must still 
be RocksDB, got " +
+          observed(SQLConf.STATE_STORE_PROVIDER_CLASS.key))
+    }
+  }
+
+  test("an explicit checkpoint format version is not overridden by Real-Time 
Mode") {
+    // The defaulting guard respects an explicit version rather than forcing 
2. v2 is used here
+    // (not v1) because an explicit v1 is rejected up front by the pre-flight 
(covered below).
+    withSQLConf(SQLConf.STATE_STORE_CHECKPOINT_FORMAT_VERSION.key -> "2") {
+      val observed = runRealTimeQueryAndReadConfs(
+        Seq(SQLConf.STATE_STORE_CHECKPOINT_FORMAT_VERSION.key))
+      
assert(observed(SQLConf.STATE_STORE_CHECKPOINT_FORMAT_VERSION.key).contains("2"),
+        "an explicitly configured checkpoint format version must be left 
alone, got " +
+          observed(SQLConf.STATE_STORE_CHECKPOINT_FORMAT_VERSION.key))
+    }
+  }
+
+  test("an explicit incompatible config is rejected up front for a Real-Time 
Mode query") {
+    // The pre-flight in StreamingQueryManager rejects explicit 
RTM-incompatible session configs
+    // before the query is built: checkpoint format below v2, a non-RocksDB 
provider, and
+    // sortBeforeRepartition=true. Each is reported in one 
SQL_CONFIGURATION_NOT_SUPPORTED error.
+    def assertRejected(confs: (String, String)*): Unit = {
+      withSQLConf(confs: _*) {
+        val inputData = LowLatencyMemoryStream[Int]
+        val e = intercept[SparkIllegalArgumentException] {
+          testStream(inputData.toDS(), OutputMode.Update, Map.empty, new 
ContinuousMemorySink())(
+            StartStream())
+        }
+        checkError(e, condition = 
"STREAMING_REAL_TIME_MODE.SQL_CONFIGURATION_NOT_SUPPORTED",
+          parameters = e.getMessageParameters.asScala.toMap)
+      }
+    }
+    assertRejected(SQLConf.STATE_STORE_CHECKPOINT_FORMAT_VERSION.key -> "1")
+    assertRejected(
+      SQLConf.STATE_STORE_PROVIDER_CLASS.key -> 
classOf[HDFSBackedStateStoreProvider].getName)
+    assertRejected(SQLConf.SORT_BEFORE_REPARTITION.key -> "true")
+  }
+
+  test("the checkpoint-v1 escape hatch bypasses the pre-flight version check") 
{
+    // With the escape hatch on, an explicit v1 config is permitted through 
the pre-flight (the
+    // existing-v1-checkpoint fail-fast in initializeExecution is covered 
separately below).
+    withSQLConf(
+      SQLConf.STREAMING_REAL_TIME_MODE_DANGEROUSLY_ALLOW_CHECKPOINT_V1.key -> 
"true",
+      SQLConf.STATE_STORE_CHECKPOINT_FORMAT_VERSION.key -> "1") {
+      val inputData = LowLatencyMemoryStream[Int]
+      testStream(inputData.toDS(), OutputMode.Update, Map.empty, new 
ContinuousMemorySink())(
+        AddData(inputData, 1),
+        StartStream(),
+        CheckAnswerWithTimeout(60000, 1),
+        StopStream
+      )
+    }
+  }
+
+  test("a Real-Time Mode query defaults changelog checkpointing on with 
RocksDB") {
+    withSQLConf(
+      SQLConf.STATE_STORE_PROVIDER_CLASS.key -> 
classOf[RocksDBStateStoreProvider].getName) {
+      val observed = runRealTimeQueryAndReadConfs(Seq(changelogKey))
+      assert(observed(changelogKey).contains("true"),
+        s"Real-Time Mode should default $changelogKey to true, got 
${observed(changelogKey)}")
+    }
+  }
+
+  test("an explicit changelog checkpointing setting is not overridden by 
Real-Time Mode") {
+    // The RocksDB provider must be in force, otherwise the `usingRocksDb` 
gate skips the block and
+    // the guard under test is never reached -- the assertion would hold 
vacuously.
+    withSQLConf(
+      SQLConf.STATE_STORE_PROVIDER_CLASS.key -> 
classOf[RocksDBStateStoreProvider].getName,
+      changelogKey -> "false") {
+      val observed = runRealTimeQueryAndReadConfs(Seq(changelogKey))
+      assert(observed(changelogKey).contains("false"),
+        s"an explicitly configured $changelogKey must be left alone, got 
${observed(changelogKey)}")
+    }
+  }
+
+  test("switching an existing v1 checkpoint to Real-Time Mode fails fast") {
+    // An existing v1 checkpoint carries no explicit config, so the pre-flight 
cannot see it; the
+    // initializeExecution fail-fast catches it instead, from the resolved 
commit log version.
+    // Resolution keeps an existing checkpoint at the version it was created 
with, so rather than
+    // silently running at v1 (where a re-executed batch can reuse the failed 
batch's state file
+    // names and lose data) the query is rejected at start. The rejection is 
unconditional -- a
+    // plain (stateless) query is rejected too.
+    withTempDir { checkpointDir =>
+      val inputData = LowLatencyMemoryStream[Int]
+      val mapped = inputData.toDS().map(_ + 1)
+      testStream(mapped, OutputMode.Update, Map.empty, new 
ContinuousMemorySink())(
+        AddData(inputData, 1),
+        // Phase 1: a micro-batch trigger, which writes a v1 commit log.
+        StartStream(
+          trigger = Trigger.ProcessingTime("1 second"),
+          checkpointLocation = checkpointDir.getAbsolutePath),
+        WaitUntilCurrentBatchProcessed,
+        StopStream,
+        AddData(inputData, 2),
+        // Phase 2: the same checkpoint under the Real-Time trigger must be 
rejected.
+        StartStream(checkpointLocation = checkpointDir.getAbsolutePath),
+        ExpectFailure[SparkIllegalArgumentException] { e =>
+          checkError(
+            e.asInstanceOf[SparkThrowable],
+            condition = 
"STREAMING_REAL_TIME_MODE.CHECKPOINT_FORMAT_V1_NOT_SUPPORTED",
+            parameters = Map(
+              "config" -> 
SQLConf.STREAMING_REAL_TIME_MODE_DANGEROUSLY_ALLOW_CHECKPOINT_V1.key))
+        }
+      )
+    }
+  }
+
+  test("the escape hatch allows Real-Time Mode on an existing v1 checkpoint") {
+    withSQLConf(
+      SQLConf.STREAMING_REAL_TIME_MODE_DANGEROUSLY_ALLOW_CHECKPOINT_V1.key -> 
"true") {
+      withTempDir { checkpointDir =>
+        val inputData = LowLatencyMemoryStream[Int]
+        val mapped = inputData.toDS().map(_ + 1)
+        testStream(mapped, OutputMode.Update, Map.empty, new 
ContinuousMemorySink())(
+          AddData(inputData, 1),
+          StartStream(
+            trigger = Trigger.ProcessingTime("1 second"),
+            checkpointLocation = checkpointDir.getAbsolutePath),
+          WaitUntilCurrentBatchProcessed,
+          StopStream,
+          AddData(inputData, 2),
+          StartStream(checkpointLocation = checkpointDir.getAbsolutePath),
+          // The sink accumulates across both phases, so batch 1's row is 
still present.
+          CheckAnswerWithTimeout(60000, 2, 3),
+          StopStream
+        )
+      }
+    }
+  }
+
+  test("a fresh Real-Time Mode checkpoint is not rejected") {
+    // A fresh checkpoint takes v2 from the Real-Time Mode defaults, so its 
resolved commit log is
+    // v2 and the rejection does not apply. Only a v1 commit log -- an 
existing v1 checkpoint, or an
+    // explicit v1 config without the escape hatch -- is rejected.
+    withTempDir { checkpointDir =>
+      val inputData = LowLatencyMemoryStream[Int]
+      testStream(inputData.toDS(), OutputMode.Update, Map.empty, new 
ContinuousMemorySink())(
+        AddData(inputData, 1, 2),
+        StartStream(checkpointLocation = checkpointDir.getAbsolutePath),
+        CheckAnswerWithTimeout(60000, 1, 2),
+        StopStream
+      )
+    }
+  }
+
+}
diff --git 
a/sql/core/src/test/scala/org/apache/spark/sql/streaming/StreamRealTimeModeSuite.scala
 
b/sql/core/src/test/scala/org/apache/spark/sql/streaming/StreamRealTimeModeSuite.scala
index 689d89e19e00..52b05b6f4fb5 100644
--- 
a/sql/core/src/test/scala/org/apache/spark/sql/streaming/StreamRealTimeModeSuite.scala
+++ 
b/sql/core/src/test/scala/org/apache/spark/sql/streaming/StreamRealTimeModeSuite.scala
@@ -22,6 +22,7 @@ import java.util.concurrent.{ConcurrentHashMap, TimeUnit}
 import java.util.concurrent.atomic.{AtomicInteger, AtomicReference}
 
 import scala.concurrent.duration.Duration
+import scala.jdk.CollectionConverters._
 
 import org.scalatest.concurrent.PatienceConfiguration.Timeout
 
@@ -695,10 +696,11 @@ class StreamRealTimeModeWithManualClockSuite extends 
StreamRealTimeModeManualClo
     }
   }
 
-  test("pipelined shuffle: an explicit sortBeforeRepartition=true does not 
stall the query") {
+  test("pipelined shuffle: an explicit sortBeforeRepartition=true is 
rejected") {
     // The deterministic local sort before a round-robin repartition never 
drains an unbounded
-    // stream, so Real-Time Mode overrides this config even when it is set 
explicitly -- honouring
-    // it would hang the query forever. See MicroBatchExecution.
+    // stream, so honouring sortBeforeRepartition=true would hang a Real-Time 
Mode query forever.
+    // Rather than silently override it, the pre-flight in 
StreamingQueryManager rejects the
+    // explicit value up front. See 
StreamingQueryManager.throwIfConfsAreRealTimeModeIncompatible.
     withSQLConf(
       SQLConf.SHUFFLE_PARTITIONS.key -> "2",
       SQLConf.SORT_BEFORE_REPARTITION.key -> "true") {
@@ -707,12 +709,14 @@ class StreamRealTimeModeWithManualClockSuite extends 
StreamRealTimeModeManualClo
         .repartition(4)
         .dropDuplicates("key")
         .select($"key")
-      testStream(result, OutputMode.Update, Map.empty, new 
ContinuousMemorySink())(
-        AddData(inputData, ("a", 1), ("b", 1), ("c", 1), ("a", 2)),
-        StartStream(),
-        CheckAnswerWithTimeout(60000, "a", "b", "c"),
-        StopStream
-      )
+      val e = intercept[SparkIllegalArgumentException] {
+        testStream(result, OutputMode.Update, Map.empty, new 
ContinuousMemorySink())(
+          AddData(inputData, ("a", 1), ("b", 1), ("c", 1), ("a", 2)),
+          StartStream()
+        )
+      }
+      checkError(e, condition = 
"STREAMING_REAL_TIME_MODE.SQL_CONFIGURATION_NOT_SUPPORTED",
+        parameters = e.getMessageParameters.asScala.toMap)
     }
   }
 
diff --git 
a/sql/core/src/test/scala/org/apache/spark/sql/streaming/test/StreamingSinkEvolutionSuite.scala
 
b/sql/core/src/test/scala/org/apache/spark/sql/streaming/test/StreamingSinkEvolutionSuite.scala
index 46de58fad333..42a2edd016b9 100644
--- 
a/sql/core/src/test/scala/org/apache/spark/sql/streaming/test/StreamingSinkEvolutionSuite.scala
+++ 
b/sql/core/src/test/scala/org/apache/spark/sql/streaming/test/StreamingSinkEvolutionSuite.scala
@@ -23,8 +23,9 @@ import org.apache.spark.{SparkException, 
SparkIllegalArgumentException}
 import org.apache.spark.sql._
 import org.apache.spark.sql.execution.streaming.checkpointing.{CommitLog, 
CommitMetadataV3}
 import org.apache.spark.sql.execution.streaming.runtime.MemoryStream
+import 
org.apache.spark.sql.execution.streaming.state.HDFSBackedStateStoreProvider
 import org.apache.spark.sql.internal.SQLConf
-import org.apache.spark.sql.streaming.{StreamTest, Trigger}
+import org.apache.spark.sql.streaming.{StreamingQuery, StreamTest, Trigger}
 import org.apache.spark.util.Utils
 
 /**
@@ -313,6 +314,40 @@ class StreamingSinkEvolutionSuite extends StreamTest with 
BeforeAndAfterEach {
     assert(v3.sinkMetadataMap.size === 1)
   }
 
+  testWithSinkEvolution("restart V3 commit log with state checkpoint format 
V1") {
+    val checkpointDir = newMetadataDir
+    val input = MemoryStream[Int]
+
+    withSQLConf(
+        SQLConf.STATE_STORE_CHECKPOINT_FORMAT_VERSION.key -> "1",
+        SQLConf.STATE_STORE_PROVIDER_CLASS.key ->
+          classOf[HDFSBackedStateStoreProvider].getName) {
+      def startQuery(): StreamingQuery = input.toDF()
+        .groupBy("value")
+        .count()
+        .writeStream
+        .format("noop")
+        .outputMode("update")
+        .name("stateful_sink")
+        .option("checkpointLocation", checkpointDir)
+        .start()
+
+      input.addData(1)
+      val firstRun = startQuery()
+      firstRun.processAllAvailable()
+      firstRun.stop()
+
+      input.addData(2)
+      val restarted = startQuery()
+      restarted.processAllAvailable()
+      restarted.stop()
+    }
+
+    val commitLog = new CommitLog(spark, s"$checkpointDir/commits", readOnly = 
true)
+    val v3 = commitLog.getLatest().get._2.asInstanceOf[CommitMetadataV3]
+    assert(v3.stateUniqueIds.isEmpty)
+  }
+
   // ==============
   // Helper Methods
   // ==============


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to