parthchandra opened a new issue, #7277:
URL: https://github.com/apache/datafusion-comet/issues/7277

   Part of #7267.
   
   ## Problem
   
   Comet's `ArrowCachedBatchSerializer` stores cached tables as Arrow batches. 
Reads
   emit one batch per cached batch (`ProjectedBatch`, 
`ArrowCachedBatchSerializer.scala:612-653`)
   and `CometInMemoryTableScanExec` (`:177`) feeds them to the native plan 
through
   `wrapColumnarBatchRDD` and `ScanExec`. Nothing on that path slices them.
   
   The batch size is fixed when the cache is written, and three write paths 
exist:
   
   - **Row input** (`ArrowCachedBatchSerializer.scala:664`): Spark's
     `conf.columnBatchSize`, i.e. `spark.sql.inMemoryColumnarStorage.batchSize`,
     default 10000.
   - **AQE cache build** (Spark 4.0+, `CometCacheBuildRule.scala:63`): builds 
the cache
     with `CometSparkToColumnarExec(child, Some(conf.columnBatchSize))`, also 
10000.
   - **Columnar input** (`ArrowCachedBatchSerializer.scala:519-530`): keeps the 
batch
     size of the plan that wrote it, i.e. the `spark.comet.batchSize` in effect 
then.
   
   None of them follows the current `spark.comet.batchSize`, so lowering it has 
no
   effect on queries over a cached table.
   
   ## How it fails
   
   The row writers fail on the JVM before native code is involved:
   `ArrowWriters.scala:589-616` (`setSafe`, `reserveData` / `checkDataEnd`) 
throw
   `OversizedAllocationException` once one column of one batch passes 2^31-1 
bytes. At
   10000 rows that is an average above ~215 KB per value. Slicing on read can't 
help
   that case.
   
   ## Proposed fix
   
   - Write: use `min(spark.sql.inMemoryColumnarStorage.batchSize, 
spark.comet.batchSize)`
     in `convertInternalRowToCachedBatch` and in `CometCacheBuildRule`. This is
     required, not optional.
   - Read: slice any cached batch larger than the current 
`spark.comet.batchSize`. Do it
     in native `ScanExec` with `RecordBatch::slice`, which is zero-copy. 
Slicing on the
     Java side (`VectorSchemaRoot.slice` / `splitAndTransfer`) shares the data 
buffer
     but copies and rebases offsets, and can copy validity.
   
   ## Tests
   
   - Scala test: cache a table with `spark.comet.batchSize` smaller than the 
cache batch
     size, then check the batches the native side receives are no bigger than 
the
     batch size. Cover the row, AQE (Spark 4.0+) and columnar write paths.
   - SQL-file test: cache a table with a binary column of values a few hundred 
KB in
     size, filter it, and compare with Spark.
   
   ## Notes
   
   - Related: #6834 (cache audit epic) and #6950 / PR #6988, which merge small 
cached
     batches on the columnar path up to 
`spark.sql.inMemoryColumnarStorage.batchSize`.
     That makes the columnar path follow the same 10000-row default, so the 
write-side
     `min()` here should apply to it as well.
   
   - Changing the write-side size changes cache layout and the number of cached
     batches. Benchmark cache build and scan before and after.
   


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