mkincaid commented on code in PR #56584:
URL: https://github.com/apache/spark/pull/56584#discussion_r3454908288


##########
mllib/src/main/scala/org/apache/spark/ml/Model.scala:
##########
@@ -57,5 +57,19 @@ abstract class Model[M <: Model[M]] extends Transformer { 
self =>
    * 4, For 3-rd extension, if external languages are used, it is recommended 
to override
    * this method and return a proper size.
    */
-  private[spark] def estimatedSize: Long = SizeEstimator.estimate(self)
+  private[spark] def estimatedSize: Long = {
+    // SPARK-57521: Temporarily clear the parent reference during size 
estimation.
+    // After fit(), the parent estimator may retain indirect references to the 
SparkSession
+    // (via closures or query plan state from DataFrame operations executed 
during fit).
+    // SizeEstimator traverses the entire reachable object graph, causing it 
to count
+    // shared SparkSession state as part of every model's size.
+    // The parent is @transient (not persisted) and is not needed for 
transform() or save().
+    val savedParent = parent
+    parent = null

Review Comment:
   Hi @uros-b, thanks for the quick review and input. I pushed a change that 
adds `synchronized` so that we wouldn't have two concurrent `estimatedSize` 
calls from here. However, I'm realizing this doesn't address the second part of 
your comment (a concurrent reader from elsewhere would still observe parent == 
null).
   
   As I looked into this further, the truly non-mutating approaches I came up 
with were:
    - Create a `copy` of the `Model` with empty `parent`, then size that. But 
this depends on the implementation of the `copy` method which is model-specific.
    - Make the `Model` object `Cloneable`, then `clone()`, clear `parent`, and 
size. But changing an interface of `Model` itself seems less conservative and 
beyond the scope I was intending for this original fix.
    - Serialize and deserialize the object before estimating its size. Since 
the `parent` is `@transient` it would be gone after the round trip. This seems 
conceptually appealing (it seems like, in principle, the data the model keeps 
and serializes is the state we care about sizing) but not sure if it might be 
expensive for large models.
    - Target the fix elsewhere, e.g., perhaps `SizeEstimator` itself should 
skip walking through `SparkSession` objects (the same way as there are existing 
exclusions there for `ClassLoader` and `scala.reflect`). This also seems less 
conservative since other users of `SizeEstimator` might not want the behavior 
to change.
   
   Or I may be missing something easier/cleaner. It is probably pretty obvious 
that I'm new to this code base, so I want to be thoughtful about design and get 
more input before proceeding. Appreciate your patience with me and looking 
forward to your thoughts :)



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


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

Reply via email to