Skip to content

feat[cuda]: add direct I/O for local file scans#8898

Draft
onursatici wants to merge 1 commit into
developfrom
os/o-direct
Draft

feat[cuda]: add direct I/O for local file scans#8898
onursatici wants to merge 1 commit into
developfrom
os/o-direct

Conversation

@onursatici

Copy link
Copy Markdown
Contributor

No description provided.

Keep footer and zone-map reads buffered while data-plane segments are read into pooled pinned buffers with Linux direct I/O. Expose the mode through both CUDA open options and a backwards-compatible FFI scan options entry point.

Signed-off-by: Onur Satici <onur@spiraldb.com>
@github-actions

github-actions Bot commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

Polar Signals Profiling Results

Latest Run

Status Commit Job Attempt Link
🟢 Done d18b83b 1 Explore Profiling Data

Powered by Polar Signals Cloud

@github-actions

Copy link
Copy Markdown
Contributor

Benchmarks: Vortex queries 📖

Verdict: No clear signal (low confidence)
Attributed Vortex impact: +0.7%
Engines: DataFusion No clear signal (+8.4%, medium confidence) · DuckDB No clear signal (-6.5%, low confidence)
Vortex (geomean): 0.946x ➖
Parquet (geomean): 0.943x ➖
Shifts: Parquet (control) -5.7% · Median polish -5.1%

How to read Verdict and Engines
  • Verdict: Overall PR-level signal after subtracting baseline drift estimated from Parquet control rows. It can be Likely improvement, Likely regression, or No clear signal.
  • Engines: Per-engine attribution. DataFusion is compared against DataFusion/Parquet controls; DuckDB is compared against DuckDB/Parquet controls. This answers whether each engine improved or regressed independently.
  • Confidence: Based on directional consistency, share of rows above the noise floor, and control-run noise.

datafusion / vortex-file-compressed (0.975x ➖, 0↑ 0↓)
name PR d18b83b (ns) base 037b1a8 (ns) ratio (PR/base)
vortex_q00/datafusion:vortex-file-compressed 9648006 10016685 0.96
vortex_q01/datafusion:vortex-file-compressed 6156432 6241940 0.99
datafusion / parquet (0.899x ✅, 1↑ 0↓)
name PR d18b83b (ns) base 037b1a8 (ns) ratio (PR/base)
vortex_q00/datafusion:parquet 19968543 21628324 0.92
vortex_q01/datafusion:parquet 🚀 4583246 5231454 0.88
duckdb / vortex-file-compressed (0.924x ➖, 0↑ 0↓)
name PR d18b83b (ns) base 037b1a8 (ns) ratio (PR/base)
vortex_q00/duckdb:vortex-file-compressed 9846385 10577988 0.93
vortex_q01/duckdb:vortex-file-compressed 6041981 6588883 0.92
duckdb / parquet (0.988x ➖, 0↑ 0↓)
name PR d18b83b (ns) base 037b1a8 (ns) ratio (PR/base)
vortex_q00/duckdb:parquet 23285300 23594326 0.99
vortex_q01/duckdb:parquet 9450923 9549441 0.99

No file size changes detected.

@0ax1
0ax1 self-requested a review July 22, 2026 15:58
@0ax1

0ax1 commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

@claude review

@github-actions

github-actions Bot commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

Claude finished @0ax1's task in 3m 25s —— View job


Claude PR Review — Direct I/O for local CUDA file scans

  • Gather context (read changed files)
  • Review FFI direct I/O changes (ffi/src/lib.rs, vortex_cuda.h)
  • Review pooled_read_at.rs direct I/O implementation
  • Review file.rs / pinned.rs changes
  • Post review feedback

Overall this is a clean, careful implementation. The alignment math is correct, the FFI surface is additive and ABI-safe, error handling avoids panics on the I/O path, and the Linux/non-Linux split is handled consistently. Below are findings ordered by importance — none are blocking, mostly hardening and questions.


Correctness — looks solid

I traced the alignment path end-to-end and it holds up:

  • direct_io_range widens [offset, offset+length) down to the aligned read_offset, up to a block-aligned read_length, and returns the prefix..requested_end slice. All arithmetic is overflow-checked. The unit tests cover the representative cases.
  • read_direct_at always passes an offset-aligned buffer length (buffer[filled..] with filled proven aligned) at an aligned file offset, so every pread satisfies O_DIRECT constraints. The short-read-past-EOF case terminates correctly because required_bytes ≤ file_size − read_offset for any valid request. The mid-stream unaligned-short-read guard is a nice defensive touch.
  • Memory alignment is guaranteed in practice: alloc_pinned (cudaHostAlloc) returns page-aligned memory (4096), which satisfies stx_dio_mem_align (typically 512/4096). The runtime address check backstops this.

1. Device-buffer element alignment after .slice(prefix..) — worth a comment

read_byte_range returns BufferHandle::new_device(...).slice(requested_range). In the buffered path the data starts at the (page-aligned) device allocation base; in the direct path it starts at base + prefix, where prefix = offset % offset_align.

This preserves the segment's natural alignment only if the on-disk segment alignment A is a power-of-two divisor of the block alignment (then prefix is a multiple of A, and base + prefix stays A-aligned). That invariant holds for Vortex today, but it's load-bearing and non-obvious. Consider a short comment at the .slice(...) call noting that the widened read preserves segment alignment because segment offsets divide the DIO block size. If any consumer ever assumes a stronger alignment than the segment's, this would silently break.

2. Hard failure when dio_mem_align > 4096

If a device reports a memory alignment larger than the pinned allocation's page alignment, every data-plane read fails with InvalidInput and the scan is dead in the water — a confusing failure mode for the caller who just set direct_io = true. This is rare, but since the goal of the statx/DIOALIGN probe is graceful degradation, it'd be more robust to either fall back to buffered reads (or return a clear "device requires N-byte alignment, unsupported" error at open_direct time rather than per-read). At minimum, worth documenting the assumption that pinned buffers are page-aligned.

3. Fallback alignment may be insufficient on old kernels

When statx doesn't report DIOALIGN (kernels < 6.1), the code falls back to FALLBACK_DIRECT_IO_ALIGNMENT = 4096. On a device whose true logical block is larger than 4096 this fallback would produce EINVAL on read. 4096 is a reasonable default and the common case, but a one-line comment explaining why 4096 (page size, covers virtually all real devices) would help future readers weigh the tradeoff.

4. Small inefficiency: full-read_length transfer for tiny reads

Each direct read transfers up to ~2 × alignment − 1 extra bytes to the GPU (the widened prefix + tail) and the device buffer retains the full read_length allocation even though only the sub-slice is used. Immaterial for large data segments (the intended target), but if read_byte_range is ever called for many small ranges under direct I/O, this amplifies both H2D traffic and resident device memory. Not worth changing now — just flagging the characteristic.

5. Test coverage

The pure direct_io_range logic is unit-tested, but read_direct_at (the short-read/alignment-guard loop) and the widen→read→truncate→slice round-trip are not exercised. A full integration test needs Linux + CUDA + an O_DIRECT-capable FS, which is hard in CI — understandable. If feasible, a host-only test of the loop logic (factoring the read loop to take a read_at closure over a plain File opened without O_DIRECT) would cover the trickiest untested code cheaply.

Minor / nits

  • direct_io_range: the first offset.checked_add(length_u64)? result is discarded; the subsequent prefix + length and + alignment - 1 checks already catch overflow, so this line is redundant (harmless).
  • read_byte_range: for length == 0 with an unaligned offset the direct path reads a full aligned block just to return an empty slice. Edge case, likely never hit, but a length == 0 early return would avoid it.
  • The non-Linux vortex_ensure!(!direct_io, ...) in read_byte_range is unreachable because open_direct already bails on non-Linux — fine as defense-in-depth.

Style / repo conventions

  • New public APIs (with_direct_io, open_direct, vx_cuda_scan_options, the new FFI fn) all carry doc comments ✅ and FFI functions have # Safety sections ✅.
  • Tests return VortexResult<()> and use ? ✅, and live in a tests module ✅.

I could not run cargo clippy/cargo +nightly fmt --check -p vortex-cuda in this review environment (the crate requires the CUDA toolchain to build). Please make sure those pass locally before merge, per CLAUDE.md.
· branch os/o-direct

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.

2 participants