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

pan3793 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 7659eb8b0883 [SPARK-59024][SQL] Use the physical plan id as the cached 
name for anonymous cached tables
7659eb8b0883 is described below

commit 7659eb8b0883482a2bbcf4e53891f8d9e601cd69
Author: Cheng Pan <[email protected]>
AuthorDate: Mon Aug 31 09:33:06 2026 +0800

    [SPARK-59024][SQL] Use the physical plan id as the cached name for 
anonymous cached tables
    
    ### What changes were proposed in this pull request?
    
    Add an internal SQL config `spark.sql.dataframeCache.planIdName.enabled` 
(default `false`). When it is true and the cached table has no name, 
`CachedRDDBuilder` uses the physical plan id, e.g. `CachedRDD (plan_id=42)`, as 
the cached name instead of the abbreviated plan tree string. `cachedName` is 
also made a `lazy val`, so the name is only computed when it is actually needed:
    
    ```scala
    lazy val cachedName: String = tableName.map(n => s"In-memory table 
$n").getOrElse {
      if 
(cachedPlan.conf.getConf(SQLConf.DATAFRAME_CACHE_PLAN_ID_NAME_ENABLED)) {
        s"CachedRDD (plan_id=${cachedPlan.id})"
      } else {
        Utils.abbreviate(cachedPlan.toString, 1024)
      }
    }
    ```
    
    ### Why are the changes needed?
    
    For anonymous cached tables, the cached name was built from the plan's tree 
string (`cachedPlan.toString`, abbreviated to 1024 chars) at `CachedRDDBuilder` 
construction time, even for caches that are never materialized. Rendering the 
plan tree string can be expensive for large plans, and the name is only used 
for display.
    
    This is another spot, besides the SQL event plan description addressed in 
SPARK-59023, that hurts the same customer job: it constructs a huge plan whose 
`treeString` exceeds 280,000 lines, and rendering the plan tree string takes 
minutes per iteration and contributes to driver OOM.
    
    ### Does this PR introduce _any_ user-facing change?
    
    The new config is internal and defaults to `false`. One nuance: because 
`cachedName` is now evaluated lazily, the name of anonymous caches is rendered 
at materialization time, so for adaptive plans the Storage tab shows a name 
derived from the final AQE plan instead of the pre-execution plan. No other 
behavior changes.
    
    ### How was this patch tested?
    
    New unit tests in `InMemoryRelationSuite`:
    
    - `SPARK-59024: plan id cached name for anonymous cached tables` -- 
verifies the `CachedRDD (plan_id=<id>)` format, that caches of the same plan 
share the name, that distinct plans get distinct names, that named tables keep 
the `In-memory table <name>` name, and that the abbreviated plan tree string is 
kept when the config is disabled
    - `SPARK-59024: anonymous cached name is not rendered before 
materialization` -- verifies the plan tree string is not rendered at cache 
construction
    
    ### Was this patch authored or co-authored using generative AI tooling?
    
    Generated-by: Qwen3.8 Max
    
    Closes #58314 from pan3793/SPARK-59024.
    
    Authored-by: Cheng Pan <[email protected]>
    Signed-off-by: Cheng Pan <[email protected]>
    (cherry picked from commit af0d5c3abd19ba4bc244da174d3df257d582f970)
    Signed-off-by: Cheng Pan <[email protected]>
---
 .../org/apache/spark/sql/internal/SQLConf.scala    | 12 +++++
 .../sql/execution/columnar/InMemoryRelation.scala  | 11 +++-
 .../execution/columnar/InMemoryRelationSuite.scala | 58 +++++++++++++++++++++-
 3 files changed, 77 insertions(+), 4 deletions(-)

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 9f92028ea8a2..3bdeae6ee7a5 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
@@ -2647,6 +2647,18 @@ object SQLConf {
     .enumConf(classOf[Level])
     .createWithDefault(Level.TRACE)
 
+  val DATAFRAME_CACHE_PLAN_ID_NAME_ENABLED =
+    buildConf("spark.sql.dataframeCache.planIdName.enabled")
+      .internal()
+      .doc("When true and the cached table has no name, use the physical plan 
id, e.g. " +
+        "'CachedRDD (plan_id=42)', as the cached name instead of the 
abbreviated plan tree " +
+        "string. Rendering the plan tree string can be expensive for large 
plans. The name " +
+        "is resolved when the cache is first materialized.")
+      .version("4.4.0")
+      .withBindingPolicy(ConfigBindingPolicy.NOT_APPLICABLE)
+      .booleanConf
+      .createWithDefault(false)
+
   val DROP_TABLE_VIEW_ENABLED =
     buildConf("spark.sql.dropTableOnView.enabled")
       .doc("When true, DROP TABLE command will work on VIEW as well.")
diff --git 
a/sql/core/src/main/scala/org/apache/spark/sql/execution/columnar/InMemoryRelation.scala
 
b/sql/core/src/main/scala/org/apache/spark/sql/execution/columnar/InMemoryRelation.scala
index 584eaa9ecbc8..3de3e41e110b 100644
--- 
a/sql/core/src/main/scala/org/apache/spark/sql/execution/columnar/InMemoryRelation.scala
+++ 
b/sql/core/src/main/scala/org/apache/spark/sql/execution/columnar/InMemoryRelation.scala
@@ -289,8 +289,15 @@ case class CachedRDDBuilder(
   // late updates from making a rebuilt cache appear complete.
   private var partitionStats = newPartitionStats()
 
-  val cachedName = tableName.map(n => s"In-memory table $n")
-    .getOrElse(Utils.abbreviate(cachedPlan.toString, 1024))
+  // Resolved on first access (cache materialization for anonymous caches). 
For adaptive plans,
+  // the name reflects the final plan.
+  lazy val cachedName: String = tableName.map(n => s"In-memory table 
$n").getOrElse {
+    if (cachedPlan.conf.getConf(SQLConf.DATAFRAME_CACHE_PLAN_ID_NAME_ENABLED)) 
{
+      s"CachedRDD (plan_id=${cachedPlan.id})"
+    } else {
+      Utils.abbreviate(cachedPlan.toString, 1024)
+    }
+  }
 
   val supportsColumnarInput: Boolean = {
     cachedPlan.supportsColumnar &&
diff --git 
a/sql/core/src/test/scala/org/apache/spark/sql/execution/columnar/InMemoryRelationSuite.scala
 
b/sql/core/src/test/scala/org/apache/spark/sql/execution/columnar/InMemoryRelationSuite.scala
index 6a9c8964e4db..706e81d8588c 100644
--- 
a/sql/core/src/test/scala/org/apache/spark/sql/execution/columnar/InMemoryRelationSuite.scala
+++ 
b/sql/core/src/test/scala/org/apache/spark/sql/execution/columnar/InMemoryRelationSuite.scala
@@ -17,13 +17,19 @@
 
 package org.apache.spark.sql.execution.columnar
 
+import java.util.concurrent.atomic.AtomicInteger
+
 import org.apache.spark.SparkFunSuite
-import org.apache.spark.sql.catalyst.expressions.AttributeSet
-import org.apache.spark.sql.execution.SparkPlan
+import org.apache.spark.rdd.RDD
+import org.apache.spark.sql.catalyst.InternalRow
+import org.apache.spark.sql.catalyst.expressions.{Attribute, AttributeSet}
+import org.apache.spark.sql.execution.{LeafExecNode, SparkPlan}
 import org.apache.spark.sql.execution.adaptive.AdaptiveSparkPlanHelper
 import org.apache.spark.sql.functions.expr
+import org.apache.spark.sql.internal.SQLConf
 import org.apache.spark.sql.test.SharedSparkSessionBase
 import org.apache.spark.storage.StorageLevel
+import org.apache.spark.util.Utils
 
 class InMemoryRelationSuite extends SparkFunSuite
   with SharedSparkSessionBase with AdaptiveSparkPlanHelper {
@@ -51,6 +57,38 @@ class InMemoryRelationSuite extends SparkFunSuite
     assert(r1.sameResult(r2))
   }
 
+  test("SPARK-59024: plan id cached name for anonymous cached tables") {
+    val d = spark.range(1)
+    withSQLConf(SQLConf.DATAFRAME_CACHE_PLAN_ID_NAME_ENABLED.key -> "true") {
+      val r1 = InMemoryRelation(StorageLevel.MEMORY_ONLY, d.queryExecution, 
None)
+      // Caches of the same physical plan instance share the plan id.
+      val r1Again = InMemoryRelation(StorageLevel.MEMORY_ONLY, 
d.queryExecution, None)
+      val r2 = InMemoryRelation(StorageLevel.MEMORY_ONLY, 
spark.range(2).queryExecution, None)
+      assert(r1.cacheBuilder.cachedName.matches("CachedRDD 
\\(plan_id=\\d+\\)"))
+      assert(r1Again.cacheBuilder.cachedName == r1.cacheBuilder.cachedName)
+      assert(r1.cacheBuilder.cachedName != r2.cacheBuilder.cachedName)
+      // Named tables keep the usual name.
+      val r3 = InMemoryRelation(StorageLevel.MEMORY_ONLY, d.queryExecution, 
Some("t1"))
+      assert(r3.cacheBuilder.cachedName == "In-memory table t1")
+    }
+    // When disabled, the cached name keeps the abbreviated plan tree string.
+    withSQLConf(SQLConf.DATAFRAME_CACHE_PLAN_ID_NAME_ENABLED.key -> "false") {
+      val r4 = InMemoryRelation(StorageLevel.MEMORY_ONLY, d.queryExecution, 
None)
+      assert(r4.cacheBuilder.cachedName ==
+        Utils.abbreviate(r4.cacheBuilder.cachedPlan.toString, 1024))
+    }
+  }
+
+  test("SPARK-59024: anonymous cached name is not rendered before 
materialization") {
+    val plan = ToStringCountingPlan()
+    val relation = InMemoryRelation(new DefaultCachedBatchSerializer, 
StorageLevel.MEMORY_ONLY,
+      plan, None, spark.range(1).queryExecution.optimizedPlan)
+    assert(plan.toStringCount == 0)
+    // Forcing the name renders the tree string exactly once.
+    relation.cacheBuilder.cachedName
+    assert(plan.toStringCount == 1)
+  }
+
   test("SPARK-47177: Cached SQL plan do not display final AQE plan in explain 
string") {
     def findIMRInnerChild(p: SparkPlan): SparkPlan = {
       val tableCache = find(p) {
@@ -74,3 +112,19 @@ class InMemoryRelationSuite extends SparkFunSuite
       .contains("AdaptiveSparkPlan isFinalPlan=true"))
   }
 }
+
+case class ToStringCountingPlan() extends LeafExecNode {
+  private val _toStringCount = new AtomicInteger(0)
+
+  def toStringCount: Int = _toStringCount.get()
+
+  override def output: Seq[Attribute] = Seq.empty
+
+  override protected def doExecute(): RDD[InternalRow] =
+    throw new UnsupportedOperationException
+
+  override def toString: String = {
+    _toStringCount.incrementAndGet()
+    "ToStringCountingPlan"
+  }
+}


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

Reply via email to