Summary
RAPIDS 26.06 regressed remote Parquet scan performance when reads used the Hadoop-backed RapidsInputFile.readVectored fallback. The regression was measured on S3A with spark.rapids.perfio.s3.enabled=false, but the same fallback is also used by GCS and other Hadoop-backed filesystems without an optimized readVectored override.
The fallback moved from the old Parquet copy loop, which used parquet.read.allocation.size with an 8 MiB default, to RapidsInputFile default readVectored, which delegated to HostMemoryBuffer.copyFromStream with a fixed 128 KiB internal copy chunk.
Initial Reproduction
The following were the initial single-run diagnostic measurements on NDS SF3000 query28, EMR on EKS, Spark 4.0.0, and six g6.4xlarge executors:
| case |
query28 |
scan buffer time |
| 26.04.2 baseline |
60-65 s |
approximately 1.2-1.4k s |
| 26.06.0 original |
420.938 s |
17112.416 s |
| 26.06.0 + restored Hadoop fallback |
72.400 s |
1552.405 s |
restored fallback + parquet.read.allocation.size=131072 |
434.112 s |
17655.652 s |
These measurements established that forcing a 128 KiB copy size was sufficient to reproduce the severe regression. They were not repeated or interleaved, so the 72.4 s fixed result versus the 60-65 s baseline range should not be interpreted as evidence of a stable residual 10% regression.
Follow-up Controlled Validation
The final implementation from NVIDIA/cudf-spark-jni#4765 and #15164 was compared with RAPIDS 26.04.2 using the same SparkAAS base image and resource settings. Five full NDS runs per side were interleaved as test1, baseline1, test2, baseline2, ... to reduce time-dependent environmental drift.
With PerfIO disabled, full-query-set times were:
| pair |
26.06 + fixes |
26.04.2 |
delta |
| 1 |
1034 s |
1066 s |
-3.00% |
| 2 |
1024 s |
1026 s |
-0.19% |
| 3 |
1029 s |
991 s |
+3.83% |
| 4 |
1006 s |
1000 s |
+0.60% |
| 5 |
975 s |
992 s |
-1.71% |
The median paired delta was -0.19%. Mean times were 1013.6 s for 26.06 with the fixes and 1015.0 s for 26.04.2 (-0.14%). Independently computed medians were 1024 s and 1000 s (+2.40%), but they came from different pairs and did not agree with the paired median or mean.
Query28 itself remained noisy and changed direction across pairs:
| pair |
26.06 + fixes |
26.04.2 |
delta |
| 1 |
47.570 s |
54.815 s |
-13.22% |
| 2 |
49.354 s |
46.972 s |
+5.07% |
| 3 |
52.417 s |
46.772 s |
+12.07% |
| 4 |
45.573 s |
46.999 s |
-3.03% |
| 5 |
46.991 s |
44.880 s |
+4.70% |
The independently computed query28 medians were 47.570 s and 46.972 s (+1.27%). The pair deltas ranged from -13.22% to +12.07%, so these runs did not reproduce a stable residual 10% regression.
As a control, the same full test was run five times per side with PerfIO enabled:
| pair |
26.06 + fixes |
26.04.2 |
delta |
| 1 |
750 s |
756 s |
-0.79% |
| 2 |
767 s |
763 s |
+0.52% |
| 3 |
771 s |
782 s |
-1.41% |
| 4 |
778 s |
778 s |
0.00% |
| 5 |
766 s |
797 s |
-3.89% |
The median paired delta was -0.79%, and mean times were 766.4 s for 26.06 with the fixes and 775.2 s for 26.04.2 (-1.14%).
These results do not prove that every workload is regression-free, but they do not reproduce the initially suspected residual regression after the fix.
Code Path
Before #14674, GpuParquetScan.copyRemoteBlocksData used:
PerfIO.readToHostMemory(...).getOrElse { inputFile.open(); copyDataRange(...) } for HadoopFileIO
copyDataRange used parquet.read.allocation.size, defaulting to 8 MiB
After #14674, the fallback path used:
inputFile.readVectored(...)
RapidsInputFile default readVectored
HostMemoryBuffer.copyFromStream(...)
- a fixed 128 KiB internal byte array
Fix
The fix has two layers and is being maintained on both release/26.06 and main:
At the JNI layer, the default RapidsInputFile.readVectored fallback uses a bounded byte-buffer loop and exposes a caller-supplied-buffer helper. At the cudf-spark layer, HadoopInputFile.readVectored uses that helper with parquet.read.allocation.size, preserving Hadoop fallback configurability while keeping the #14674 inputFile.readVectored abstraction.
Optimized S3InputFile and IcebergS3InputFile implementations continue to override readVectored and use PerfIO/range-copy paths when enabled.
Summary
RAPIDS 26.06 regressed remote Parquet scan performance when reads used the Hadoop-backed
RapidsInputFile.readVectoredfallback. The regression was measured on S3A withspark.rapids.perfio.s3.enabled=false, but the same fallback is also used by GCS and other Hadoop-backed filesystems without an optimizedreadVectoredoverride.The fallback moved from the old Parquet copy loop, which used
parquet.read.allocation.sizewith an 8 MiB default, toRapidsInputFiledefaultreadVectored, which delegated toHostMemoryBuffer.copyFromStreamwith a fixed 128 KiB internal copy chunk.Initial Reproduction
The following were the initial single-run diagnostic measurements on NDS SF3000 query28, EMR on EKS, Spark 4.0.0, and six
g6.4xlargeexecutors:parquet.read.allocation.size=131072These measurements established that forcing a 128 KiB copy size was sufficient to reproduce the severe regression. They were not repeated or interleaved, so the 72.4 s fixed result versus the 60-65 s baseline range should not be interpreted as evidence of a stable residual 10% regression.
Follow-up Controlled Validation
The final implementation from NVIDIA/cudf-spark-jni#4765 and #15164 was compared with RAPIDS 26.04.2 using the same SparkAAS base image and resource settings. Five full NDS runs per side were interleaved as
test1, baseline1, test2, baseline2, ...to reduce time-dependent environmental drift.With PerfIO disabled, full-query-set times were:
The median paired delta was -0.19%. Mean times were 1013.6 s for 26.06 with the fixes and 1015.0 s for 26.04.2 (-0.14%). Independently computed medians were 1024 s and 1000 s (+2.40%), but they came from different pairs and did not agree with the paired median or mean.
Query28 itself remained noisy and changed direction across pairs:
The independently computed query28 medians were 47.570 s and 46.972 s (+1.27%). The pair deltas ranged from -13.22% to +12.07%, so these runs did not reproduce a stable residual 10% regression.
As a control, the same full test was run five times per side with PerfIO enabled:
The median paired delta was -0.79%, and mean times were 766.4 s for 26.06 with the fixes and 775.2 s for 26.04.2 (-1.14%).
These results do not prove that every workload is regression-free, but they do not reproduce the initially suspected residual regression after the fix.
Code Path
Before #14674,
GpuParquetScan.copyRemoteBlocksDataused:PerfIO.readToHostMemory(...).getOrElse { inputFile.open(); copyDataRange(...) }forHadoopFileIOcopyDataRangeusedparquet.read.allocation.size, defaulting to 8 MiBAfter #14674, the fallback path used:
inputFile.readVectored(...)RapidsInputFiledefaultreadVectoredHostMemoryBuffer.copyFromStream(...)Fix
The fix has two layers and is being maintained on both
release/26.06andmain:release/26.06release/26.06mainmainAt the JNI layer, the default
RapidsInputFile.readVectoredfallback uses a bounded byte-buffer loop and exposes a caller-supplied-buffer helper. At the cudf-spark layer,HadoopInputFile.readVectoreduses that helper withparquet.read.allocation.size, preserving Hadoop fallback configurability while keeping the #14674inputFile.readVectoredabstraction.Optimized
S3InputFileandIcebergS3InputFileimplementations continue to overridereadVectoredand use PerfIO/range-copy paths when enabled.