Skip to content

[SPARK-58390][SQL] Emit row counts without deserializing Arrow payloads for empty-projection cache reads - #57583

Open
viirya wants to merge 1 commit into
apache:masterfrom
viirya:arrow-cache-empty-projection
Open

[SPARK-58390][SQL] Emit row counts without deserializing Arrow payloads for empty-projection cache reads#57583
viirya wants to merge 1 commit into
apache:masterfrom
viirya:arrow-cache-empty-projection

Conversation

@viirya

@viirya viirya commented Jul 27, 2026

Copy link
Copy Markdown
Member

What changes were proposed in this pull request?

Short-circuit ArrowCachedBatchSerializer.convertCachedBatchToInternalRow when the projection is empty: emit numRows reused 0-field UnsafeRows per cached batch, without deserializing or decompressing the batch's Arrow payload. The row count is already recorded on ArrowCachedBatch.

Also fixes the ArrowCachedBatch scaladoc, which listed the per-column statistics as (upperBound, lowerBound, ...) while both write paths produce the ColumnStats.collectedStatistics order (lowerBound, upperBound, nullCount, count, sizeInBytes); the code was consistently lower-first everywhere, only the doc was wrong.

Why are the changes needed?

An empty projection (e.g. a count aggregate through the row-based reader, spark.sql.inMemoryColumnarStorage.enableVectorizedReader=false) selects no columns, yet the reader still paid full IPC deserialization and decompression for every cached batch just to iterate its rows. That cost is pure waste: the answer is a stored integer.

Does this PR introduce any user-facing change?

No. The Arrow cache serializer (SPARK-57268) is unreleased, and the change is performance-only; results are identical.

How was this patch tested?

Two new tests in ArrowCachedBatchSerializerSuite:

  • empty projection emits row counts without deserializing the Arrow payload: hands the reader a cached batch whose Arrow payload is garbage bytes. The empty projection returns the correct number of empty rows purely from numRows (fails before this change with IllegalArgumentException: capacity < 0 from the IPC reader, proving the payload used to be deserialized), while a projection that actually needs the payload still fails on the same batch, pinning that only the empty-projection case skips the read.
  • count aggregate over the cached relation with the row-based reader: end-to-end count(*) over a cached relation spanning many small Arrow batches with the vectorized reader disabled, plus a sum over the same cached data verifying projecting reads still decode the payload correctly.

Full ArrowCachedBatchSerializerSuite (73 tests) and ArrowCachedBatchKryoRegistrationSuite pass.

Was this patch authored or co-authored using generative AI tooling?

Yes, this pull request and its description were written by Claude Code.

…ds for empty-projection cache reads

An empty projection over an Arrow-cached relation (e.g. a count
aggregate through the row-based reader) selects no columns, yet
convertCachedBatchToInternalRow still deserialized and decompressed
every cached batch's Arrow payload just to iterate its rows. The row
count is already recorded on the cached batch, so short-circuit the
empty-projection case to emit that many reused 0-field UnsafeRows
without touching the payload.

Also fix the ArrowCachedBatch scaladoc, which listed the per-column
statistics as (upperBound, lowerBound, ...) while both write paths
produce the ColumnStats.collectedStatistics order
(lowerBound, upperBound, nullCount, count, sizeInBytes).

Co-authored-by: Claude Code
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant