viirya commented on code in PR #59582:
URL: https://github.com/apache/spark/pull/59582#discussion_r4471391219


##########
sql/core/src/test/scala/org/apache/spark/sql/execution/python/ArrowColumnarPythonUDFSuite.scala:
##########
@@ -130,6 +158,203 @@ class ArrowColumnarPythonUDFSuite extends 
SharedSparkSession {
     }
   }
 
+  test("SPARK-60325: Arrow-backed source that reuses its vectors: Arrow 
columnar path not taken") {
+    assume(shouldTestPandasUDFs)
+    withSQLConf(SQLConf.ARROW_PYSPARK_EXECUTION_ENABLED.key -> "true") {
+      val pandasUDF = TestScalarPandasUDF(
+        name = "arrow_reuse_udf", returnType = Some(StringType))
+      registerTestUDF(pandasUDF, spark)
+
+      // The Arrow columnar path would keep the pass-through columns of a 
batch until its result
+      // comes back from Python. The reader refills its vectors for the next 
batch and closes them
+      // once it is closed, as soon as it is exhausted, so they are not 
retainable, and the
+      // batches take the row queue path. With two batches, the reader is 
closed before their
+      // results come back. With many, it refills its vectors with the next 
batches before then.
+      // The test UDF casts its input to string: on a string column, the cast 
is removed, so the
+      // inputs are column references.
+      Seq(200, 20000).foreach { numRows =>
+        val result = readArrowSource(numRows = numRows, reuseVectors = true, 
batchSize = 100)
+          .selectExpr("id", "name", "value", "data", "arrow_reuse_udf(name) as 
udf_name")
+        val arrowExec = 
singleArrowEvalPythonExec(result.queryExecution.executedPlan)
+        assert(!takesArrowColumnarPath(arrowExec))

Review Comment:
   Done in cafd4cf047c: the restored test is merged into the reusing-reader 
test, which runs with columnar input on and off, and its comment says only that 
the default reads the scan as rows.



##########
sql/core/src/main/scala/org/apache/spark/sql/execution/python/ColumnarArrowEvalPythonEvaluatorFactory.scala:
##########
@@ -200,15 +221,22 @@ private[python] class 
ColumnarArrowEvalPythonEvaluatorFactory(
         outputTypes: Seq[DataType],
         columnIndices: Array[Int]): Iterator[ColumnarBatch] = {
 
+      // In pipelined mode the batches are added in the writer thread and

Review Comment:
   Done in cafd4cf047c: this comment now says that the queue is polled by the 
consumer of the output, possibly on another thread, and the class doc no longer 
says that the pass-through columns are released on the task thread.



##########
sql/core/src/main/scala/org/apache/spark/sql/execution/python/ColumnarArrowEvalPythonEvaluatorFactory.scala:
##########
@@ -262,7 +290,7 @@ private[python] class 
ColumnarArrowEvalPythonEvaluatorFactory(
         childOutput, childOutput)
 
       val resultIter = if (inputColumnIndices.isDefined) {
-        // Path 2: simple column refs, non-Arrow vectors.
+        // Path 2: simple column refs, columns that cannot be held.
         val bufferedIter = new WrittenBatchReleasingIterator(inputIter, { 
batch =>
           batch.rowIterator().asScala.foreach { row =>
             queue.add(unsafeProj(row).copy())

Review Comment:
   Agreed. It is pre-existing, so I'll fix it separately, together with the 
other issues of the row queue path.



##########
sql/catalyst/src/main/java/org/apache/spark/sql/vectorized/ColumnVector.java:
##########
@@ -84,6 +84,33 @@ public void closeIfFreeable() {
     close();
   }
 
+  /**
+   * Returns whether a consumer may keep this column vector after it asks the 
producer of its batch
+   * for the next batch, until the consumer releases it with {@link 
#closeIfFreeable()}. By default,
+   * a column vector may be reused or released by its producer once the next 
batch is requested, so
+   * a consumer has to copy what it needs past that point.
+   * <p>
+   * A producer that returns new vectors for every batch can override this to 
return true, which
+   * promises that:
+   * <ul>
+   *   <li>the producer neither overwrites nor frees this vector when the next 
batch is requested,
+   *   or when the producer is closed, e.g. a {@code PartitionReader} once it 
is exhausted;</li>
+   *   <li>{@link #closeIfFreeable()} frees this vector, and may be called 
from another thread
+   *   than the one that requested its batch;</li>
+   *   <li>the producer frees the vectors that the consumer has not released 
when the task
+   *   completes, e.g. when the consumer stops early.</li>
+   * </ul>
+   * A consumer that keeps such vectors does not account for their memory, and 
cannot spill them.
+   * <p>
+   * This applies to the top-level columns of a batch: the children of a 
struct, array or map
+   * column share the lifetime of that column.
+   *
+   * @since 4.4.0
+   */
+  public boolean isRetainable() {

Review Comment:
   Keeping it internal would leave Path 1 unreachable for every source outside 
Spark, so enabling the conf could only send Arrow batches to the row queue 
path. The opt-in is what lets an Arrow-backed connector that returns new 
vectors for every batch use the path that SPARK-56350 added for it. 
`ColumnVector` is `@Evolving`, so the method can still change, but I'd like 
HyukjinKwon's opinion before it ships. I also changed L103 in cafd4cf047c as 
you suggest: "A consumer may keep such vectors without accounting for their 
memory or spilling them."



-- 
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