Skip to content

Use configured copy buffer for Hadoop vectored reads - #15164

Merged
binmahone merged 2 commits into
NVIDIA:release/26.06from
binmahone:fix-readvectored-hadoop-fallback
Jul 9, 2026
Merged

Use configured copy buffer for Hadoop vectored reads#15164
binmahone merged 2 commits into
NVIDIA:release/26.06from
binmahone:fix-readvectored-hadoop-fallback

Conversation

@binmahone

@binmahone binmahone commented Jun 29, 2026

Copy link
Copy Markdown
Collaborator

Fixes #15163.

Description

When an optimized input-file implementation is not selected, remote Parquet reads use HadoopInputFile and the generic RapidsInputFile.readVectored fallback. Before NVIDIA/cudf-spark-jni#4765, that fallback used HostMemoryBuffer.copyFromStream with a 128 KiB internal copy chunk, while the pre-NVIDIA/cudf-spark#14674 Hadoop copy loop used parquet.read.allocation.size with an 8 MiB default.

This PR overrides HadoopInputFile.readVectored to use the caller-supplied-buffer helper from NVIDIA/cudf-spark-jni#4765:

  • read the copy size from parquet.read.allocation.size
  • default to the JNI fallback size of 8 MiB
  • allocate a temporary buffer for each readVectored call
  • avoid a retained shared buffer or ThreadLocal state
  • leave GpuParquetScan on the inputFile.readVectored abstraction introduced by optimize iceberg read #14674

The measured regression was on the S3A fallback with PerfIO disabled. The same HadoopInputFile fallback is also used by GCS and other Hadoop-backed filesystems that do not provide their own optimized readVectored implementation. When S3 PerfIO is enabled, S3InputFile.readVectored continues to use the optimized PerfIO path and bypasses this fallback.

Requires the JNI change from NVIDIA/cudf-spark-jni#4765, merged as f5c95d88e16846a66a61870441e73c84863e706c. The release/26.06 base branch selects JNI 26.06.1-SNAPSHOT through #15228.

Testing

The JNI change (901ca4f570cb843c814121a7698ba8b3d5896fbf) passed all eight RapidsInputFileTest tests. After installing that JNI artifact locally, the cudf-spark change commit (ba4487c750ba73cf2f2281288ca319ad7f3752be) was packaged on an x86 host with:

mvn -B -pl sql-plugin -am -DskipTests \
  -Dspark-rapids-jni.version=26.06.1-SNAPSHOT package

Result: BUILD SUCCESS.

Performance Validation

Five interleaved full-NDS runs per configuration did not reproduce a stable performance regression after the fix, with either PerfIO disabled or enabled. See #15163 for the complete environment, per-run results, and analysis.

Checklists

Documentation

  • Updated for new or modified user-facing features or behaviors
  • No user-facing change

Testing

  • Added or modified tests to cover new code paths
  • Covered by existing tests
  • Not required

Performance

  • Tests ran and results are added in the PR description
  • Issue filed with a link in the PR description
  • Not required

res-life
res-life previously approved these changes Jun 29, 2026

@res-life res-life left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@binmahone
binmahone force-pushed the fix-readvectored-hadoop-fallback branch 3 times, most recently from f9ef769 to 887912f Compare June 30, 2026 08:19
@binmahone binmahone changed the title Use configured copy buffer for Hadoop vectored reads Reuse configured copy buffer for Hadoop fallback vectored reads Jun 30, 2026
@sameerz

sameerz commented Jun 30, 2026

Copy link
Copy Markdown
Collaborator

@binmahone please retarget to release/26.06 as this is a regression and we will need to release a hotfix.

In addition, please add performance results to the description of the PR.

@binmahone binmahone changed the title Reuse configured copy buffer for Hadoop fallback vectored reads Restore Hadoop fallback copy loop for Parquet ranges Jul 2, 2026
@binmahone binmahone changed the title Restore Hadoop fallback copy loop for Parquet ranges Use configured copy buffer for Hadoop vectored reads Jul 2, 2026
@binmahone
binmahone force-pushed the fix-readvectored-hadoop-fallback branch from be4c485 to d3283e8 Compare July 2, 2026 08:38
@binmahone
binmahone changed the base branch from main to release/26.06 July 2, 2026 13:04
Signed-off-by: Hongbin Ma (Mahone) <mahongbin@apache.org>
@binmahone
binmahone force-pushed the fix-readvectored-hadoop-fallback branch from d3283e8 to ba4487c Compare July 2, 2026 13:08
@binmahone

Copy link
Copy Markdown
Collaborator Author

Done. This PR targets release/26.06, and the description now includes the dependent JNI build verification, the five-pair PerfIO-disabled results, the PerfIO-enabled control results, and the required checklist.

The current GitHub compile failures are the expected dependency-order failure: CI resolves the published 26.06 JNI artifact, which does not yet contain DEFAULT_READ_VECTORED_COPY_BUFFER_SIZE or readVectoredUsingCopyBuffer. Packaging succeeds when the JNI PR snapshot is installed and selected.

@binmahone

Copy link
Copy Markdown
Collaborator Author

@gerashegalov Following up here on your caller-supplied-buffer suggestion in NVIDIA/cudf-spark-jni#4765: this PR does use RapidsInputFile.readVectoredUsingCopyBuffer(..., byte[]), but it intentionally allocates the byte[] for each HadoopInputFile.readVectored invocation rather than retaining it across invocations. This does not realize the cross-call allocation amortization you originally highlighted.

We considered retaining the buffer in HadoopInputFile, but RapidsInputFile requires implementations to be thread-safe. A shared mutable buffer would be unsafe for concurrent calls; synchronizing its use would serialize reads; and a ThreadLocal could retain up to the configured allocation size per thread. Truly amortizing the allocation therefore needs buffer ownership at a reader scope with a well-defined lifecycle, which is a broader change than this hotfix. The current implementation keeps the per-call lifetime while using the caller-supplied helper to honor parquet.read.allocation.size.

We validated this exact implementation against 26.04.2 with five interleaved full-NDS runs per configuration, both with PerfIO disabled and enabled. The results did not reproduce a stable performance regression after the fix. The complete environment and per-run results are in #15163.

@binmahone
binmahone marked this pull request as ready for review July 6, 2026 08:55
@binmahone
binmahone requested review from Copilot and gerashegalov and removed request for Copilot July 6, 2026 08:55
@greptile-apps

greptile-apps Bot commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR overrides readVectored in HadoopInputFile to use a caller-supplied copy buffer, fixing a performance regression on S3A and other Hadoop-backed filesystems when S3InputFile's optimized PerfIO path is not active.

  • Reads parquet.read.allocation.size (defaulting to RapidsInputFile.DEFAULT_READ_VECTORED_COPY_BUFFER_SIZE ≈ 8 MiB) at construction time and stores it as copyBufferSize, restoring the pre-regression copy chunk size that the Hadoop loop previously used.
  • readVectored allocates a fresh byte[] per call and delegates to the new JNI static helper RapidsInputFile.readVectoredUsingCopyBuffer, deliberately avoiding a retained shared buffer or ThreadLocal state.
  • The empty-ranges early return prevents an unnecessary stream open when there is nothing to read.

Confidence Score: 5/5

The change is a focused, well-scoped override that restores correct copy-chunk sizing for Hadoop-backed vectored reads with no resource management or logic concerns.

The override is small and self-contained: it reads a well-known Parquet config key at construction, guards against non-positive values, allocates a fresh byte array per call (intentionally avoiding shared state), and delegates to the new JNI static helper. The empty-ranges early return is correct. No GPU resources, no AutoCloseable ownership concerns on this side of the call, and no logic paths that deviate from the stated design.

No files require special attention; the only outstanding gate is the dependent JNI PR landing so the build can resolve the new constant and helper.

Important Files Changed

Filename Overview
sql-plugin/src/main/java/com/nvidia/spark/rapids/fileio/hadoop/HadoopInputFile.java Adds readVectored override that allocates a per-call copy buffer sized from parquet.read.allocation.size and delegates to the new JNI helper; adds copyBufferSize field with positive-value guard; removes unused FileStatus import.

Sequence Diagram

%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
    participant GpuParquetScan
    participant HadoopInputFile
    participant RapidsInputFile
    participant HadoopInputStream

    GpuParquetScan->>HadoopInputFile: readVectored(output, copyRanges)
    alt copyRanges is empty
        HadoopInputFile-->>GpuParquetScan: return
    else
        HadoopInputFile->>HadoopInputFile: new byte[copyBufferSize]
        HadoopInputFile->>RapidsInputFile: readVectoredUsingCopyBuffer(this, output, copyRanges, copyBuffer)
        RapidsInputFile->>HadoopInputFile: open()
        HadoopInputFile->>HadoopInputStream: new HadoopInputStream(fs.open(filePath))
        HadoopInputStream-->>RapidsInputFile: SeekableInputStream
        loop for each CopyRange
            RapidsInputFile->>HadoopInputStream: seek + read into copyBuffer
            RapidsInputFile->>RapidsInputFile: "copy copyBuffer -> HostMemoryBuffer(output)"
        end
        RapidsInputFile->>HadoopInputStream: close()
        RapidsInputFile-->>GpuParquetScan: done
    end
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
    participant GpuParquetScan
    participant HadoopInputFile
    participant RapidsInputFile
    participant HadoopInputStream

    GpuParquetScan->>HadoopInputFile: readVectored(output, copyRanges)
    alt copyRanges is empty
        HadoopInputFile-->>GpuParquetScan: return
    else
        HadoopInputFile->>HadoopInputFile: new byte[copyBufferSize]
        HadoopInputFile->>RapidsInputFile: readVectoredUsingCopyBuffer(this, output, copyRanges, copyBuffer)
        RapidsInputFile->>HadoopInputFile: open()
        HadoopInputFile->>HadoopInputStream: new HadoopInputStream(fs.open(filePath))
        HadoopInputStream-->>RapidsInputFile: SeekableInputStream
        loop for each CopyRange
            RapidsInputFile->>HadoopInputStream: seek + read into copyBuffer
            RapidsInputFile->>RapidsInputFile: "copy copyBuffer -> HostMemoryBuffer(output)"
        end
        RapidsInputFile->>HadoopInputStream: close()
        RapidsInputFile-->>GpuParquetScan: done
    end
Loading

Reviews (2): Last reviewed commit: "Merge branch 'release/26.06' into fix-re..." | Re-trigger Greptile

Comment on lines +48 to +50
int copyBufferSize = conf.getInt(PARQUET_READ_ALLOCATION_SIZE,
RapidsInputFile.DEFAULT_READ_VECTORED_COPY_BUFFER_SIZE);
return new HadoopInputFile(filePath, fs, copyBufferSize);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Dependency on unpublished JNI artifact

Both RapidsInputFile.DEFAULT_READ_VECTORED_COPY_BUFFER_SIZE (line 49) and RapidsInputFile.readVectoredUsingCopyBuffer (line 91) are added in NVIDIA/cudf-spark-jni#4765, which has not yet been published in the 26.06 artifact. The PR description notes the two cannot find symbol compile errors are expected until that JNI change ships. This PR cannot be merged independently and must be gated on the JNI PR landing first.

@gerashegalov gerashegalov left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

if (copyRanges.isEmpty()) {
return;
}
byte[] copyBuffer = new byte[copyBufferSize];

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Non-blocking for a follow-up: HadoopInputFile could allocate min(copyBufferSize, largest requested range) like the JNI default path does, instead of always allocating copyBufferSize. That keeps the current per-call lifetime and thread-safety properties, while avoiding an 8 MiB allocation for small footer/range reads.

gerashegalov pushed a commit to NVIDIA/cudf-spark-jni that referenced this pull request Jul 7, 2026
## Description

This updates the default `RapidsInputFile.readVectored` fallback to copy
through an explicit byte-buffer loop and write into the destination
`HostMemoryBuffer` with `setBytes`.

The default fallback does not cache a scratch buffer in the interface
implementation. It allocates a bounded buffer for each `readVectored`
call, using the smaller of the default 8 MiB limit and the largest
requested range. Callers with a well-defined lifecycle can use
`RapidsInputFile.readVectoredUsingCopyBuffer(..., byte[])` to provide a
caller-managed scratch buffer.

This avoids routing default fallback vectored reads through
`HostMemoryBuffer.copyFromStream`, whose internal copy chunk is 128 KiB.
Internal validation and stream-copy helpers live in a package-private
utility class so they do not expand the interface API surface beyond the
caller-supplied helper.

Related issue: NVIDIA/cudf-spark#15163.

The dependent NVIDIA/cudf-spark#15164 uses the helper in
`HadoopInputFile.readVectored`. It keeps the `GpuParquetScan`
`readVectored` abstraction introduced by NVIDIA/cudf-spark#14674 while
allowing Hadoop-backed fallback reads to honor
`parquet.read.allocation.size`.

## Testing

The current PR head (`901ca4f570cb843c814121a7698ba8b3d5896fbf`) was
tested with:

```text
mvn -B -Dmaven.antrun.skip=true -Dtest=RapidsInputFileTest \
  test-compile surefire:test@default-test
```

Result:

```text
Tests run: 8, Failures: 0, Errors: 0, Skipped: 0
BUILD SUCCESS
```

The JNI artifact was then installed locally and the dependent cudf-spark
head (`ba4487c750ba73cf2f2281288ca319ad7f3752be`) was packaged on an x86
host with:

```text
mvn -B -pl sql-plugin -am -DskipTests \
  -Dspark-rapids-jni.version=26.06.1-SNAPSHOT package
```

Result: `BUILD SUCCESS`.

## Performance Validation

End-to-end performance validation was performed with the dependent
NVIDIA/cudf-spark#15164. Five interleaved NDS runs did not reproduce a
stable regression after the fix. See NVIDIA/cudf-spark#15164 for the
complete results.

---------

Signed-off-by: Hongbin Ma (Mahone) <mahongbin@apache.org>
@binmahone

Copy link
Copy Markdown
Collaborator Author

build

2 similar comments
@binmahone

Copy link
Copy Markdown
Collaborator Author

build

@yinqingh

yinqingh commented Jul 8, 2026

Copy link
Copy Markdown
Collaborator

build

@binmahone

Copy link
Copy Markdown
Collaborator Author

build

1 similar comment
@yinqingh

yinqingh commented Jul 8, 2026

Copy link
Copy Markdown
Collaborator

build

Signed-off-by: Hongbin Ma <mahongbin@apache.org>
Copilot AI review requested due to automatic review settings July 8, 2026 13:28
@greptile-apps

greptile-apps Bot commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

Want your agent to iterate on Greptile's feedback? Try greploops.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR addresses a remote Parquet scan performance regression on Hadoop-backed filesystems (e.g., S3A fallback, GCS) by making the HadoopInputFile vectored-read fallback use a configurable copy buffer size (via parquet.read.allocation.size) and delegating the actual copy loop to the newer JNI helper introduced in the referenced JNI change.

Changes:

  • Adds a parquet.read.allocation.size-driven copy buffer size to HadoopInputFile creation (defaulting to the JNI-provided fallback size).
  • Overrides HadoopInputFile.readVectored to allocate a per-call byte buffer and use RapidsInputFile.readVectoredUsingCopyBuffer(...) for bounded buffered reads.

Comment on lines +56 to +58
if (copyBufferSize <= 0) {
throw new IllegalArgumentException(PARQUET_READ_ALLOCATION_SIZE + " must be positive");
}
Comment on lines +89 to +92
}
byte[] copyBuffer = new byte[copyBufferSize];
RapidsInputFile.readVectoredUsingCopyBuffer(this, output, copyRanges, copyBuffer);
}
@yinqingh

yinqingh commented Jul 8, 2026

Copy link
Copy Markdown
Collaborator

build

@sameerz sameerz added the performance A performance related task/issue label Jul 8, 2026
@binmahone
binmahone merged commit cbae849 into NVIDIA:release/26.06 Jul 9, 2026
49 of 51 checks passed
binmahone added a commit that referenced this pull request Jul 13, 2026
…5242)

Fixes #15163.

### Description

This is the `main` forward-port of #15164, which targeted
`release/26.06`.

When an optimized input-file implementation is not selected, remote
Parquet reads use `HadoopInputFile` and the generic
`RapidsInputFile.readVectored` fallback. Before
NVIDIA/cudf-spark-jni#4765, that fallback used
`HostMemoryBuffer.copyFromStream` with a 128 KiB internal copy chunk,
while the pre-#14674 Hadoop copy loop used
`parquet.read.allocation.size` with an 8 MiB default.

This PR overrides `HadoopInputFile.readVectored` to use the
caller-supplied-buffer helper from the JNI `main` forward-port,
NVIDIA/cudf-spark-jni#4808:

- read the copy size from `parquet.read.allocation.size`
- default to the JNI fallback size of 8 MiB
- allocate a temporary buffer for each `readVectored` call
- avoid retained shared buffers and `ThreadLocal` state
- leave `GpuParquetScan` on the `inputFile.readVectored` abstraction
introduced by #14674

The measured regression was on the S3A fallback with PerfIO disabled.
The same `HadoopInputFile` fallback is also used by GCS and other
Hadoop-backed filesystems that do not provide their own optimized
`readVectored` implementation. When S3 PerfIO is enabled,
`S3InputFile.readVectored` continues to use the optimized PerfIO path
and bypasses this fallback.

Requires NVIDIA/cudf-spark-jni#4808. The previous `release/26.06`
implementation is #15164 and NVIDIA/cudf-spark-jni#4765.

### Testing

The JNI `main` forward-port (`daca8177f626c4c48676beea5f16d36a974620fe`)
passed all eight `RapidsInputFileTest` tests. After installing that JNI
`26.08.0-SNAPSHOT` artifact locally, this commit
(`5ec2c7ffc1763d5b141e20066ab303e9c287e962`) was packaged on an x86 host
with:

```text
mvn -B -pl sql-plugin -am -DskipTests \
  -Dspark-rapids-jni.version=26.08.0-SNAPSHOT package
```

Result: `BUILD SUCCESS` for all four reactor modules.

Five interleaved full-NDS runs per configuration did not reproduce a
stable performance regression after the fix, with either PerfIO disabled
or enabled. See #15163 for the complete environment, per-run results,
and analysis.

### Checklists

Documentation
- [ ] Updated for new or modified user-facing features or behaviors
- [x] No user-facing change

Testing
- [ ] Added or modified tests to cover new code paths
- [ ] Covered by existing tests
- [x] Not required

Performance
- [ ] Tests ran and results are added in the PR description
- [x] Issue filed with a link in the PR description
- [ ] Not required

Signed-off-by: Hongbin Ma <mahongbin@apache.org>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

performance A performance related task/issue

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants