Skip to content

Prefetch parquet metadata for scan tasks - #22700

Merged
rapids-bot[bot] merged 81 commits into
rapidsai:mainfrom
TomAugspurger:tom/cudf-polars-prefetch-metadata
Jul 7, 2026
Merged

Prefetch parquet metadata for scan tasks#22700
rapids-bot[bot] merged 81 commits into
rapidsai:mainfrom
TomAugspurger:tom/cudf-polars-prefetch-metadata

Conversation

@TomAugspurger

@TomAugspurger TomAugspurger commented May 28, 2026

Copy link
Copy Markdown
Contributor

Description

To improve the performance of cudf-polars' remote IO, we'd like to prefetch all parquet metadata ahead of time, rather than inside a Scan task. This ensures we read metadata at most once per paths, and allows us to cleanly separate setup things like metadata reads from the actual job of reading data. This is also a necessary step for adopting the Hybrid Scan reader.

To accomplish this, we have a new stage of execution that prefetches parquet metadata for a given IR query plan. This occurs immediately after lowering. The FileMetadata for those paths is read and cached on a new field in IRExecutionContext. Once we get to the actual Scan.do_evaluate that's reading the parquet metadata, we access the pre-fetched parquet file metadata from the IRExecutionContext for our paths.

I've implemented this as a new option on ParquetOptions, mostly for ease of benchmarking. I suspect we'll want to enable it by default but I want to confirm that.

Closes #22667
Closes #22753

@copy-pr-bot

copy-pr-bot Bot commented May 28, 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 Python Affects Python cuDF API. cudf-polars Issues specific to cudf-polars labels May 28, 2026
@GPUtester GPUtester moved this to In Progress in cuDF Python May 28, 2026
Comment thread python/cudf_polars/cudf_polars/streaming/io.py
Add a parquet metadata prefetch cache in IR execution context and plumb it through in-memory and streaming scan paths to reuse footer metadata across related reads.
@TomAugspurger
TomAugspurger force-pushed the tom/cudf-polars-prefetch-metadata branch from 392b7f0 to 80d92b7 Compare May 29, 2026 21:22
@TomAugspurger TomAugspurger added improvement Improvement / enhancement to an existing function non-breaking Non-breaking change labels May 29, 2026
Comment thread python/cudf_polars/cudf_polars/dsl/ir.py Outdated
Comment thread python/cudf_polars/cudf_polars/dsl/ir.py Outdated
Comment thread python/cudf_polars/cudf_polars/dsl/ir.py Outdated
Comment thread python/cudf_polars/cudf_polars/dsl/ir.py Outdated
Comment thread python/cudf_polars/cudf_polars/dsl/ir.py Outdated
Comment thread python/cudf_polars/cudf_polars/utils/config.py
Comment thread python/cudf_polars/tests/test_scan.py Outdated
@TomAugspurger

TomAugspurger commented Jun 1, 2026

Copy link
Copy Markdown
Contributor Author

#22739 implements a change to (py)libcudf to accept a file size in addition to a path in SourceInfo.

I suspect that we can use that if we explicitly list all the file sizes (using something like kvikio.RemoteFile.open_s3_url(url).nbytes(), in parallel) just before doing the parallel parquet metadata collection. Then we should be able to pass that through to read_parquet_footers and each read_parquet call to bypass all the HEAD requests in kvikio. I think this PR should wait for #22739 and do the file size collection in cudf-polars.

Finally, #22734 would let us skip that explicit list file sizes call and just use the file sizes directly from polars. This will require changes to polars, so is not a blocker for this PR.

@TomAugspurger

TomAugspurger commented Jun 2, 2026

Copy link
Copy Markdown
Contributor Author

109ad43 demonstrates using the new FilepathSource from #22739. It seems to work as expected. This screenshot shows a worker thread doing 3 remote IO things:

  1. reading ParquetMetadata in stats collection
  2. reading parquet footers in the prefetching stage
  3. reading parquet data in the Scan task
image

Note that the Scan task doesn't have any (large) kvikio spans related to HEAD requests to "open" the file. Just the read_parquet_metadata and read_parquet_footers from stats and metadata collection (and I wonder if those can be combined...)

But for now, I've reverted that commit so this isn't blocked by #22739.

@Matt711
Matt711 self-requested a review June 3, 2026 15:09
rapids-bot Bot pushed a commit that referenced this pull request Jun 4, 2026
This refactors how we dynamically generate `Scan` nodes with the rapidsmpf streaming runtime.

Previously, these nodes were generated inside
`cudf_polars.streaming.actor_grpah.io.scan_node`. The lowered `ir` node would have a basic `Scan` (or maybe a `SplitScan` node) that was never actually executed. Instead, *after* we created the Actor responsible for that node, we'd generate new `Scan` nodes to do the work of reading some subset of the files / row groups.

This PR does the same thing (generate these `Scan`/`SplitScan` nodes) but it does it at lowering time, rather than inside the `Scan` task. The actual logic of generating these new nodes is unchanged. We should end up with exactly the same execution as before; we just make the `list[Scan | SplitScan]` earlier.

This is primarily motivated by awkwardness in using the dynamically generated Scan nodes in a couple spots:

1. prefetching parquet metadata(#22700)
2. Quent tracing

I've added a new `StreamingScan` IR node that just holds references to the `list[Scan]` nodes. This might be overkill for what we need, but it's at least consistent with how we do the main lowering, which I like.

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

Approvers:
  - Matthew Murray (https://github.com/Matt711)

URL: #22758
@TomAugspurger

Copy link
Copy Markdown
Contributor Author

/ok to test f3fa17f

@github-project-automation github-project-automation Bot moved this from In Progress to Done in cuDF Python Jun 4, 2026
Comment thread python/cudf_polars/cudf_polars/dsl/utils/replace.py Outdated
Comment thread python/cudf_polars/cudf_polars/dsl/utils/replace.py Outdated
Comment thread python/cudf_polars/cudf_polars/dsl/utils/io.py
Comment thread python/cudf_polars/cudf_polars/streaming/io.py Outdated
We treat this as an optimization, so exclude prefetched metadata from
hashing & equality of Scan-type nodes.

This makes it safe to mutate the Scan nodes after theyre' created by
lowering, which simplifies updating the IR graph after metadata have
been prefetched.

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

Thanks @TomAugspurger ! Pretty happy where this PR is at.

These are the next steps I'm aware of

  • Separate Scan class by file type #23063
  • Prefetch inside the streaming network (maybe do). Can you open an issue?
  • Turn on prefetching by default (this is a maybe I think) #22826
  • Use prefetched footers for hybrid scan in #22857
  • Reuse prefetched parquet metadata for decimal predicate pushdown #22940

Anything missing?

def _parquet_physical_types(
paths: list[str], columns: list[str] | None
) -> dict[str, plc.DataType]:
# This may not be able use prefetched metadata, since we don't (currently) have a Schema.

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.

nitpick: Can you make this a TODO and link the issue?

Comment thread python/cudf_polars/cudf_polars/dsl/utils/io.py Outdated
Comment thread python/cudf_polars/cudf_polars/dsl/ir.py Outdated
Comment thread python/cudf_polars/cudf_polars/streaming/io.py Outdated
@TomAugspurger

Copy link
Copy Markdown
Contributor Author

Prefetch inside the streaming network (maybe do). Can you open an issue?

I thought I had, but couldn't find it. #23131

Anything missing?

#22662 should have the others (like getting the object information (file size) from polars).

@TomAugspurger

Copy link
Copy Markdown
Contributor Author

/merge

@TomAugspurger
TomAugspurger requested a review from wence- July 7, 2026 11:44

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

One nitpick

with kvikio.RemoteFile.open(path) as remote_file:
sizes.append(remote_file.nbytes())
else:
sizes.append(None)

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 you make a size value of None.

Comment on lines +103 to +104
CachedParquetInfo(path, size, file_metadata)
for path, size, file_metadata in zip(paths, sizes, metadata, strict=True)

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.

But here you are claiming that size is an int. What gives?

@TomAugspurger TomAugspurger Jul 7, 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.

Whoops, I think Rick might have flagged this earlier too but I misread it.

It's fine for us to pass an int | None to FilepathSource, but CachedParquetInfo currently states that .size is an int.

I'll get this fixed.

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.

#23142 has the fix.

@rapids-bot
rapids-bot Bot merged commit a1d4b37 into rapidsai:main Jul 7, 2026
108 checks passed
@github-project-automation github-project-automation Bot moved this from In Progress to Done in cuDF Python Jul 7, 2026
@TomAugspurger
TomAugspurger deleted the tom/cudf-polars-prefetch-metadata branch July 7, 2026 14:48
TomAugspurger added a commit to TomAugspurger/pygdf that referenced this pull request Jul 7, 2026
In rapidsai#22700 we needed a way to get the
equivalent of `ParquetMetadata.columnchunk_metadata` from a list of parquet footers.

See the discussion at https://github.com/rapidsai/cudf/pull/22700/files/1d9e2147f8001a6e607da6f79c8125be5d1bb3da#diff-e5ac2ccddbef69c522213586ce23621cc87f3c21f123291c17a0d4dda628def8 for details.

This implements that in libcudf / pylibcudf, and we'll make use of it in rapidsai#22700.
TomAugspurger added a commit to TomAugspurger/pygdf that referenced this pull request Jul 8, 2026
In rapidsai#22700 we needed a way to get the
equivalent of `ParquetMetadata.columnchunk_metadata` from a list of parquet footers.

See the discussion at https://github.com/rapidsai/cudf/pull/22700/files/1d9e2147f8001a6e607da6f79c8125be5d1bb3da#diff-e5ac2ccddbef69c522213586ce23621cc87f3c21f123291c17a0d4dda628def8 for details.

This implements that in libcudf / pylibcudf, and we'll make use of it in rapidsai#22700.
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 cudf-polars Issues specific to cudf-polars improvement Improvement / enhancement to an existing function Java Affects Java cuDF API. libcudf Affects libcudf (C++/CUDA) code. non-breaking Non-breaking change Python Affects Python cuDF API.

Projects

Status: Done

5 participants