Skip to content

perf(jni): bound completed scan outputs with background prefetch - #11198

Merged
robert3005 merged 3 commits into
vortex-data:developfrom
quantcast:cwirt/upstream-jni-scan-prefetch
Nov 5, 2026
Merged

robert3005 merged 3 commits into
vortex-data:developfrom
quantcast:cwirt/upstream-jni-scan-prefetch

Conversation

@ChristopherWirt

Copy link
Copy Markdown
Contributor

Summary

A JNI scan hands batches to Java through block_on_stream_thread_safe. That bridge queues one completed batch per CPU core, but each JNI stream has only one consumer. On wide or nested Spark scans those queued Arrow batches add up, so peak memory grows with the host's core count.

This adds a single-consumer bridge that keeps one completed batch ahead of Java, and uses it for JNI scans.

Result: local Spark measurements, medians of macOS peak physical footprint:

Workload Runs Peak GiB, before → after Change
Flat-layout read 3 1.46 → 1.05 −28%
List-layout read 2 3.25 → 2.22 −32%
Ordered Vortex rewrite 2 1.86 → 1.43 −23%
  • Batch sizes, compression and decode concurrency don't change. The rewrite's output was identical in size and row count.
  • Timing was too noisy on a shared host to claim a speedup or rule out a regression. Only one Spark task with four native workers was measured.

Changes

  • vortex-io: CurrentThreadRuntime::block_on_stream_prefetch, returning a PrefetchIterator.
    • It drives the stream in a background task, so native work still overlaps Java consumption.
    • It keeps at most one completed item ahead of the consumer, and skips entering the executor when an item is already waiting.
    • Dropping it cancels the producer. A producer panic is re-raised at end of stream.
  • JNI: NativePartition.scanArrow uses it instead of block_on_stream_thread_safe. Other callers, including DuckDB, are unchanged.

Notes for reviewers

  • Once a batch is queued, the producer stops pulling the next one until Java takes it. Conversions already in flight keep running, but with a single native worker there is little overlap.
  • A native panic during a scan is still re-raised inside the Arrow C stream callback, which aborts the JVM. This PR doesn't change that.

Tests

  • vortex-io: the one-item-ahead bound, and propagation of a driver panic.

On the fork (quantcast#78, same change), the vortex-io change is identical. The fork's JNI side goes through a scan_iterator helper from #11127 and has a JNI test for it. develop doesn't have that helper yet, so this branch changes the block_on_stream_thread_safe call directly and leaves out the JNI test. Only git diff --check has been run on the port.

API Changes

  • New public CurrentThreadRuntime::block_on_stream_prefetch and PrefetchIterator in vortex-io. Additive only.

AI assistance

Disclosed per the AI policy: AI tools helped write and review this change. Claude Code prepared this upstream port, issue and PR description at my request. I am the responsible contributor.

🤖 Generated with Claude Code

Adds `CurrentThreadRuntime::block_on_stream_prefetch`, a single-consumer
bridge that drives a stream in a background task and keeps at most one
completed item ahead of the consumer. JNI scans use it instead of
`block_on_stream_thread_safe`, whose per-core output queue retained one
finished Arrow batch per core ahead of Java.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Signed-off-by: Christopher Wirt <[email protected]>
@robert3005 robert3005 added the changelog/feature A new feature label Nov 4, 2026
@codspeed

codspeed Bot commented Nov 4, 2026 •

Copy link
Copy Markdown

Merging this PR will not alter performance

2 warnings

⚠️ Walltime results may be unreliable

Some benchmarks used the Walltime instrument outside CodSpeed Macro Runners, which can make the results inconsistent.

Learn more about Macro Runners

See affected benchmarks

⚠️ 1 benchmark is dominated by system calls

System calls cannot be simulated consistently and are left out of the measure. The reported value understates the real cost. Switch to the Walltime instrument to measure them.

Learn more about system calls

See affected benchmarks

✅ 1770 untouched benchmarks
⏩ 47 skipped benchmarks1


Comparing quantcast:cwirt/upstream-jni-scan-prefetch (ac2a8ea) with develop (d8a4952)

Open in CodSpeed

Footnotes

  1. 47 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports. ↩

@robert3005
robert3005 enabled auto-merge (squash) November 5, 2026 00:52
auto-merge was automatically disabled November 5, 2026 09:09

Head branch was pushed to by a user without write access

@robert3005
robert3005 enabled auto-merge (squash) November 5, 2026 14:09
@robert3005
robert3005 merged commit b9d7594 into vortex-data:develop Nov 5, 2026
69 checks passed
ChristopherWirt added a commit to quantcast/vortex that referenced this pull request Nov 9, 2026
Resolve the vortex-jni scan.rs conflict with vortex-data#11198: feed the
split-concurrency lookahead into block_on_stream_prefetch, and drop
scan_iterator, whose only job was to avoid the per-core queue that
the prefetch bridge already removes.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Signed-off-by: Christopher Wirt <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

changelog/feature A new feature

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants