Skip to content

PERF/API: Specify datasource size to avoid HEAD requests for S3 endpoints - #22739

Merged
rapids-bot[bot] merged 35 commits into
rapidsai:mainfrom
TomAugspurger:tom/cudf-sourceinfo-size
Jun 28, 2026
Merged

PERF/API: Specify datasource size to avoid HEAD requests for S3 endpoints#22739
rapids-bot[bot] merged 35 commits into
rapidsai:mainfrom
TomAugspurger:tom/cudf-sourceinfo-size

Conversation

@TomAugspurger

@TomAugspurger TomAugspurger commented Jun 1, 2026

Copy link
Copy Markdown
Contributor

Description

Currently, reading, say, parquet metadata or data from a remote source with kvikio requires several HTTP requests. See #22667 (comment) and rapidsai/kvikio#974 for more, but a couple of these HTTP requests are HEAD requests used to get the size of the file.

This updates libcudf to provide a way to bypass those HEAD requests when the file size is known in advance (#22734 is an example case where we already know the file sizes thanks to a list operation). To do this, we expand SourceInfo to accept a (list of) simple plc.io.types.FilepathSource(url, size=content_length) objects.

For example, read_parquet_metadata both without and with file sizes:

image

and read_parquet:

image

Note the lack of kvikio::RemoteHandle::open calls in the kvikio domain under the with-size annotations. These came from the following script:

Details
import boto3
import nvtx
import pylibcudf as plc

bucket = "rapids-tpch"
key = "scale-10/nation/part.0.parquet"
url = f"s3://{bucket}/{key}"

with nvtx.annotate("boto3-head_object"):
    response = boto3.client("s3").head_object(Bucket=bucket, Key=key)
    content_length = response["ContentLength"]

sources = plc.io.types.SourceInfo([url])
sources_with_size = plc.io.types.SourceInfo(
    [plc.io.types.FilepathSource(url, size=content_length)]
)


with nvtx.annotate("warmup"):
    plc.io.parquet_metadata.read_parquet_metadata(sources)


with nvtx.annotate("read_parquet_metadata"):
    with nvtx.annotate("no-size"):
        _ = plc.io.parquet_metadata.read_parquet_metadata(sources)

    with nvtx.annotate("with-size"):
        _ = plc.io.parquet_metadata.read_parquet_metadata(sources_with_size)

with nvtx.annotate("read_parquet_footers"):
    with nvtx.annotate("no-size"):
        footers = plc.io.parquet_metadata.read_parquet_footers(sources)

    with nvtx.annotate("with-size"):
        footers_with_size = plc.io.parquet_metadata.read_parquet_footers(sources_with_size)

options = plc.io.parquet.ParquetReaderOptions.builder(sources).build()
options_with_size = plc.io.parquet.ParquetReaderOptions.builder(
    sources_with_size
).build()

with nvtx.annotate("read_parquet"):
    with nvtx.annotate("no-size"):
        table = plc.io.parquet.read_parquet(options)

    with nvtx.annotate("with-size"):
        table = plc.io.parquet.read_parquet(options_with_size)

with nvtx.annotate("read_parquet-with-footers"):
    with nvtx.annotate("no-size"):
        table = plc.io.parquet.read_parquet(options, parquet_metadatas=footers)

    with nvtx.annotate("with-size"):
        table = plc.io.parquet.read_parquet(
            options_with_size, parquet_metadatas=footers_with_size
        )

print("done")

</details>

This is a necessary precursor to #22734.

Closes #22740

…ints

This updates libcudf and its usage of kvikio to avoid unnecessary HEAD
requests in KvikIO to "open" a remote S3 file.
@copy-pr-bot

copy-pr-bot Bot commented Jun 1, 2026

Copy link
Copy Markdown

Auto-sync is disabled for draft pull requests in this repository. Workflows must be run manually.

Contributors can view more details about this message here.

@github-actions github-actions Bot added libcudf Affects libcudf (C++/CUDA) code. Python Affects Python cuDF API. CMake CMake build issue pylibcudf Issues specific to the pylibcudf package labels Jun 1, 2026
@GPUtester GPUtester moved this to In Progress in cuDF Python Jun 1, 2026
Comment thread cpp/include/cudf/io/datasource.hpp Outdated
Comment thread cpp/include/cudf/io/datasource.hpp
Comment thread cpp/include/cudf/io/types.hpp
Comment thread cpp/include/cudf/io/types.hpp
Comment thread cpp/src/io/utilities/datasource.cpp Outdated
Comment thread cpp/include/cudf/io/types.hpp
@TomAugspurger TomAugspurger added improvement Improvement / enhancement to an existing function non-breaking Non-breaking change labels Jun 1, 2026
@TomAugspurger
TomAugspurger marked this pull request as ready for review June 1, 2026 19:48
@TomAugspurger
TomAugspurger requested review from a team as code owners June 1, 2026 19:48
@TomAugspurger TomAugspurger changed the title [WIP]: PERF/API: Specify datasource size to avoid HEAD requests for S3 endpoints PERF/API: Specify datasource size to avoid HEAD requests for S3 endpoints Jun 1, 2026
@coderabbitai

coderabbitai Bot commented Jun 1, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds optional per-path known file sizes across C++ types, datasource creation, remote handle opening, Python bindings, tests, docs, and CI packaging so callers can supply file size metadata and avoid HEAD requests when available.

Changes

I/O Known File Size Support

Layer / File(s) Summary
C++ type contract for filepath sources
cpp/include/cudf/io/types.hpp, cpp/include/cudf/io/datasource.hpp
filepath_source struct stores path and optional file size; source_info adds _filepath_sources, constructors to populate from std::vector<std::string> and std::vector<filepath_source>, rebuild_filepaths(), and filepath_sources() accessor; datasource::create signature extended with std::optional<std::size_t> known_size.
C++ datasource factory and remote I/O
cpp/src/io/utilities/datasource.cpp
datasource::create accepts known_size; adds infer_remote_endpoint_type and open_remote_handle to open kvikio handles with an optional known size; remote_file_source updated to accept and use known_size, including local-path rerouting to remote endpoints.
C++ source construction wiring
cpp/src/io/functions.cpp
make_datasources iterates over info.filepath_sources() and passes each source's optional size into datasource::create in both parallel and sequential creation paths.
C++ test coverage for filepath sources
cpp/tests/CMakeLists.txt, cpp/tests/io/filepath_source_test.cpp
Registers FILEPATH_SOURCE_TEST; adds tests validating source_info construction from strings and filepath_source vectors, size preservation, and an end-to-end Parquet read confirming propagation of known sizes.
Cython/C++ bindings for source metadata
python/pylibcudf/pylibcudf/libcudf/io/types.pxd
Adds filepath_source cppclass and source_info overload/accessor bindings so higher-level Cython can construct and query filepath sources with sizes.
Python API classes and SourceInfo integration
python/pylibcudf/pylibcudf/io/types.pxd, python/pylibcudf/pylibcudf/io/types.pyx, python/pylibcudf/pylibcudf/io/types.pyi, python/pylibcudf/pylibcudf/io/__init__.py
Adds high-level FilepathSource class with path and optional size; updates SourceInfo.__init__ to accept homogeneous FilepathSource lists, validate entries, and pass sizes into libcudf filepath_source vector; exports FilepathSource from the package.
Python export, tests, and user documentation
docs/cudf/source/cudf/io/io.md, python/pylibcudf/tests/io/test_source_sink_info.py
Documents HEAD-request behavior and how to suppress HEADs by supplying FilepathSource(size=...); adds Python tests for local/remote FilepathSource usage and mixed-type rejection.
CI packaging constraint update
ci/build_wheel_cudf_streaming.sh
Also download matching libcudf wheels during wheel packaging and add file-based libcudf constraint entries to ${PIP_CONSTRAINT} to pin libcudf used by cudf_streaming builds.

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related issues

  • #22753: Adds per-file known-size support which directly enables avoiding redundant HTTP HEAD requests by forwarding known sizes to kvikio.

Possibly related PRs

  • rapidsai/cudf#22760: Related CI packaging/wheel constraint work affecting how wheels are downloaded and constrained during builds.

Suggested labels

cuIO

Suggested reviewers

  • bdice
  • vuule
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 39.13% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The PR title 'PERF/API: Specify datasource size to avoid HEAD requests for S3 endpoints' directly and clearly summarizes the main change: adding the ability to specify datasource sizes to avoid HTTP HEAD requests.
Description check ✅ Passed The PR description comprehensively explains the motivation, implementation, and performance benefits of allowing callers to provide known file sizes via FilepathSource to bypass HEAD requests.
Linked Issues check ✅ Passed The PR fully addresses the objective in linked issue #22740: introducing FilepathSource API and forwarding size information through SourceInfo to kvikio to skip HEAD requests when file sizes are known.
Out of Scope Changes check ✅ Passed All changes are directly scoped to the objective: C++ header/implementation updates for datasource factory and source_info structures, Python bindings, comprehensive tests, documentation, and CI build script changes are all necessary for implementing the FilepathSource feature.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (2)
cpp/tests/io/filepath_source_test.cpp (1)

49-73: 💤 Low value

Consider cleaning up the temporary test file.

The test writes to std::filesystem::temp_directory_path() / "filepath_source_test.parquet" but doesn't explicitly remove it afterward. While this won't cause test failures, it could accumulate test artifacts over repeated runs.

🧹 Suggested cleanup

Add cleanup after the test (or use RAII):

   auto const result = cudf::io::read_parquet(read_opts);
   CUDF_TEST_EXPECT_COLUMNS_EQUAL(col, result.tbl->get_column(0).view());
+
+  std::filesystem::remove(filepath);
 }

Alternatively, use a scoped temporary:

auto const temp_dir = cudf::test::get_temp_filepath("filepath_source_test.parquet");

if such a helper exists in the test utilities.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cpp/tests/io/filepath_source_test.cpp` around lines 49 - 73, The test
FilepathSourceTest::KnownSizePlumbsThroughMakeDatasources leaves a temp file at
the path stored in the local variable filepath; ensure the file is removed after
the test by deleting filepath (or use an RAII/temp-file helper such as
get_temp_filepath if available) so the parquet artifact does not accumulate—add
cleanup at the end of the TEST_F or replace filepath with a scoped temp helper
when constructing cudf::io::parquet_writer_options and the sources vector.
docs/cudf/source/cudf/io/io.md (1)

139-141: 💤 Low value

Clarify the incorrect-size wording.

As phrased, it reads as if supplying an incorrect size is what suppresses HEAD requests. Any supplied size suppresses HEAD; an incorrect one is what breaks reads.

📝 Suggested reword
-Providing an incorrect size avoids the extra HEAD requests but will break footer reads and other
-operations that depend on the true file length. Plain string paths in `SourceInfo` preserve the
-previous behavior (size queried via KvikIO).
+Supplying a `size` always suppresses the extra HEAD requests, but the value must be correct: an
+incorrect size will break footer reads and other operations that depend on the true file length.
+Plain string paths in `SourceInfo` preserve the previous behavior (size queried via KvikIO).
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/cudf/source/cudf/io/io.md` around lines 139 - 141, Rewrite the sentence
in the docs explaining size behavior so it clearly states that providing any
size in SourceInfo (not specifically an incorrect size) suppresses HEAD
requests, and that supplying an incorrect size will break footer reads and
operations that depend on the true file length; update the paragraph mentioning
"Plain string paths in `SourceInfo` preserve the previous behavior (size queried
via KvikIO)" to follow this clarified wording so readers understand: any
provided size disables HEAD, but an incorrect size causes failures.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@python/pylibcudf/pylibcudf/libcudf/io/types.pxd`:
- Around line 128-146: The Cython binding declares source_info(const
vector[filepath_source] &sources) but the C++ API is explicit
source_info(std::vector<filepath_source> sources); update the pxd to match the
C++ ctor by changing the declaration of source_info with filepath_source vector
to take the vector by value (vector[filepath_source] sources) with the same
exception specifier (except +libcudf_exception_handler), or alternatively add an
overload matching the C++ signature; ensure the symbol names source_info and
filepath_source and the filepath_sources() accessor remain unchanged.

---

Nitpick comments:
In `@cpp/tests/io/filepath_source_test.cpp`:
- Around line 49-73: The test
FilepathSourceTest::KnownSizePlumbsThroughMakeDatasources leaves a temp file at
the path stored in the local variable filepath; ensure the file is removed after
the test by deleting filepath (or use an RAII/temp-file helper such as
get_temp_filepath if available) so the parquet artifact does not accumulate—add
cleanup at the end of the TEST_F or replace filepath with a scoped temp helper
when constructing cudf::io::parquet_writer_options and the sources vector.

In `@docs/cudf/source/cudf/io/io.md`:
- Around line 139-141: Rewrite the sentence in the docs explaining size behavior
so it clearly states that providing any size in SourceInfo (not specifically an
incorrect size) suppresses HEAD requests, and that supplying an incorrect size
will break footer reads and operations that depend on the true file length;
update the paragraph mentioning "Plain string paths in `SourceInfo` preserve the
previous behavior (size queried via KvikIO)" to follow this clarified wording so
readers understand: any provided size disables HEAD, but an incorrect size
causes failures.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 0cc67ef9-2151-4635-b9d9-7038a50a3f55

📥 Commits

Reviewing files that changed from the base of the PR and between 63bfe29 and 72c757e.

📒 Files selected for processing (13)
  • cpp/include/cudf/io/datasource.hpp
  • cpp/include/cudf/io/types.hpp
  • cpp/src/io/functions.cpp
  • cpp/src/io/utilities/datasource.cpp
  • cpp/tests/CMakeLists.txt
  • cpp/tests/io/filepath_source_test.cpp
  • docs/cudf/source/cudf/io/io.md
  • python/pylibcudf/pylibcudf/io/__init__.py
  • python/pylibcudf/pylibcudf/io/types.pxd
  • python/pylibcudf/pylibcudf/io/types.pyi
  • python/pylibcudf/pylibcudf/io/types.pyx
  • python/pylibcudf/pylibcudf/libcudf/io/types.pxd
  • python/pylibcudf/tests/io/test_source_sink_info.py

Comment thread python/pylibcudf/pylibcudf/libcudf/io/types.pxd
Comment thread cpp/tests/io/filepath_source_test.cpp
Comment thread cpp/src/io/utilities/datasource.cpp Outdated
kvikio::RemoteEndpointType infer_remote_endpoint_type(std::string const& url)
{
if (kvikio::S3Endpoint::is_url_valid(url)) { return kvikio::RemoteEndpointType::S3; }
if (kvikio::S3PublicEndpoint::is_url_valid(url)) { return kvikio::RemoteEndpointType::S3_PUBLIC; }

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.

Here a URL from public S3 would still be inferred as private S3.
The challenge for S3 is that private and public endpoints follow the same URL pattern (S3PublicEndpoint::is_url_valid's implementation is the same with S3Endpoint::is_url_valid), and I think it is not quite possible to infer the type from URL alone without network I/O.
In KvikIO's open() method, given a S3-like URL (that is valid for both private and public S3), we first try to create a private S3 endpoint and access its file size, and in case of an exception, we fall back to the public S3 path.

@TomAugspurger TomAugspurger Jun 23, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Would it be fair to say that there aren't really public and private URLs? Just URLs that happen to be anonymously accessible, and URLs that happen to require authorization, so they can't be disambiguated just by looking at the URL?

I think something like rapidsai/kvikio#951 would solve this pretty cleanly: you define a chain of credentials to attempt when making some HTTP request. So long as anonymous access (no credentials) is included in the chain then things will succeed.

For now, I think I'll remove the S3PublicEndpoint case, since IIUC it should be unreachable right now.

* When `size` is set for a remote URL, the IO backend may skip querying the remote server for file
* size at open time.
*/
struct filepath_source {

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.

How about the name filepath_info?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Hmm I was hoping for some kind of convention like a "source" is a thing that goes into a SourceInfo. Does that make sense?

If we change it to filepath_info, I'll want to change the python class to class FilepathInfo, which is a bit too close to SourceInfo for my taste.

@TomAugspurger

This comment was marked as outdated.

rapids-bot Bot pushed a commit to rapidsai/kvikio that referenced this pull request Jun 24, 2026
In rapidsai/cudf#22739, cudf / cudf-polars needed to infer the remote endpoint type kvikio would use for some URL, *without* making the HTTP request that'd come from `RemoteFile::open` with the default `AUTO` mode.

To do this, I've added a new public `infer_remote_endpoint_type` method, along with a python binding.

Authors:
  - Tom Augspurger (https://github.com/TomAugspurger)

Approvers:
  - Mads R. B. Kristensen (https://github.com/madsbk)
  - Tianyu Liu (https://github.com/kingcrimsontianyu)

URL: #982
@TomAugspurger

This comment was marked as outdated.

@TomAugspurger

Copy link
Copy Markdown
Contributor Author

I think there might still be some kind of build issue between here and rapidsmpf. https://github.com/rapidsai/cudf/actions/runs/28105062059/job/83225009485?pr=22739 is failure in tests/streaming/test_scan.py::test_scan_parquet_use_rapidsmpf_native[spmd-small]' .

----------------------------- Captured stderr call -----------------------------
[15a7370c9905:264948:0:277965] Caught signal 11 (Segmentation fault: address not mapped to object at address 0x100010110)
==== backtrace (tid: 277965) ====
 0  /__w/cudf/cudf/venv_polars_1.41/lib/python3.14/site-packages/libucx/lib/libucs.so(ucs_handle_error+0x294) [0x7c1c821b5b14]
 1  /__w/cudf/cudf/venv_polars_1.41/lib/python3.14/site-packages/libucx/lib/libucs.so(+0x34cca) [0x7c1c821b5cca]
 2  /__w/cudf/cudf/venv_polars_1.41/lib/python3.14/site-packages/libucx/lib/libucs.so(+0x34f7e) [0x7c1c821b5f7e]
 3  /lib/x86_64-linux-gnu/libc.so.6(+0x45330) [0x7c1f07719330]
 4  /__w/cudf/cudf/venv_polars_1.41/lib/python3.14/site-packages/libcudf_streaming/lib64/libcudf_streaming.so(_ZN4cudf2io22parquet_reader_optionsD1Ev+0xf8) [0x7c1c8040a058]
 5  /__w/cudf/cudf/venv_polars_1.41/lib/python3.14/site-packages/libcudf_streaming/lib64/libcudf_streaming.so(+0x5296c) [0x7c1c8040596c]
 6  /__w/cudf/cudf/venv_polars_1.41/lib/python3.14/site-packages/rapidsmpf/streaming/core/actor.abi3.so(+0x1b5ce) [0x7c1c801eb5ce]
 7  /__w/cudf/cudf/venv_polars_1.41/lib/python3.14/site-packages/rapidsmpf/streaming/core/actor.abi3.so(_ZNSt6vectorIN4coro6detail13when_all_taskIvEESaIS3_EED1Ev+0x32) [0x7c1c801ee002]
 8  /__w/cudf/cudf/venv_polars_1.41/lib/python3.14/site-packages/rapidsmpf/streaming/core/actor.abi3.so(+0xcca7) [0x7c1c801dcca7]
 9  /__w/cudf/cudf/venv_polars_1.41/lib/python3.14/site-packages/rapidsmpf/streaming/core/actor.abi3.so(+0x1b545) [0x7c1c801eb545]
10  /__w/cudf/cudf/venv_polars_1.41/lib/python3.14/site-packages/librapidsmpf/lib64/librapidsmpf.so(+0x15fd23) [0x7c1c8195fd23]
11  /__w/cudf/cudf/venv_polars_1.41/lib/python3.14/site-packages/librapidsmpf/lib64/librapidsmpf.so(+0x1616af) [0x7c1c819616af]
12  /__w/cudf/cudf/venv_polars_1.41/lib/python3.14/site-packages/librapidsmpf/lib64/librapidsmpf.so(_ZN4coro11thread_pool8executorEm+0xdc) [0x7c1c8199166c]
13  /lib/x86_64-linux-gnu/libstdc++.so.6(+0xecdb4) [0x7c1f0326edb4]
14  /lib/x86_64-linux-gnu/libc.so.6(+0x9caa4) [0x7c1f07770aa4]
15  /lib/x86_64-linux-gnu/libc.so.6(__clone+0x44) [0x7c1f077fda64]

@TomAugspurger

Copy link
Copy Markdown
Contributor Author

b375237 fixed the build issue. Our libcudf_streaming wheel build needs to use the libcudf wheel from this PR, not nightly.

Comment thread cpp/src/io/utilities/datasource.cpp

@kingcrimsontianyu kingcrimsontianyu 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.

Lgtm!
Suggested adding a comment documenting the current limitation.

@TomAugspurger

Copy link
Copy Markdown
Contributor Author

/merge

@rapids-bot
rapids-bot Bot merged commit 4d2410a into rapidsai:main Jun 28, 2026
138 checks passed
@TomAugspurger
TomAugspurger deleted the tom/cudf-sourceinfo-size branch June 28, 2026 19:31
@github-project-automation github-project-automation Bot moved this from In Progress to Done in cuDF Python Jun 28, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CMake CMake build issue cudf.pandas Issues specific to cudf.pandas improvement Improvement / enhancement to an existing function libcudf Affects libcudf (C++/CUDA) code. non-breaking Non-breaking change pylibcudf Issues specific to the pylibcudf package Python Affects Python cuDF API.

Projects

Archived in project

Development

Successfully merging this pull request may close these issues.

[PERF]: Avoid redundant HTTP HEAD requests in libcudf / kvikio for remote files

6 participants