[PERF]: Improve the performance of plc.io.parquet.read_parquet with prefetched parquet file metadata. - #23558
[PERF]: Improve the performance of plc.io.parquet.read_parquet with prefetched parquet file metadata.#23558TomAugspurger wants to merge 31 commits into
plc.io.parquet.read_parquet with prefetched parquet file metadata.#23558Conversation
|
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. |
|
/ok to test 39cc045 |
|
/ok to test b4d6a87 |
This changes the ownership model of FileMetadata to own the footers via unique_ptr and move them (under nogil) in the pylibcudf wrapper. Additionally, we avoid an unnecessary copy in get_parquet_metadatas().
b4d6a87 to
1ac2985
Compare
|
/ok to test 1ac2985 |
|
/ok to test af347e8 |
When page indexes are present, cloning the parquet footer FileMetaData object can be expensive. This slows down `read_parquet` when the user provides a prefetched metadata object. This PR adds a new `read_page_indexes` parameter to `read_parquet_footers` that controls whether page indexes are materialized. The default is `True` matching the existing behavior.
3d8de45 to
b3775f5
Compare
|
/ok to test f5525b5 |
read_parquet with prefetched parquet file metadata.
read_parquet with prefetched parquet file metadata.plc.io.parquet.read_parquet with prefetched parquet file metadata.
|
|
||
| timer.start(); | ||
| auto const metadatas = cudf::io::read_parquet_footers(sources); | ||
| auto const metadatas = cudf::io::read_parquet_footers(sources, write_page_index); |
There was a problem hiding this comment.
I'm not sure this change is needed, but it shouldn't hurt.
There was a problem hiding this comment.
This is currently a no-op (you're telling the benchmark to read page indexes only if they are written), but we probably want to benchmark a path where the indexes are present but ignored (write_page_index==true, read_page_indexes==false) at some point…
There was a problem hiding this comment.
I don't think we should stop reading page indexes. So I removed that commit. This means that we're not quite at parity on-prem with prefetch metdata turned off vs on--although we are closer than we were before this PR. For that reason, I'm only switching it on by default for cloud. And I've relaxed the garuntee that the metadata will be prefetched and cached if the setting is turned on to handle the case where some scans are reading from remote sources and others are not.
I actually think what may align the on-prem results is when metadata prefetching happens concurrently with scan execution (reading bytes)--see #23131.
There was a problem hiding this comment.
#23131 would overlap the footer read with other operations, but the clone happens on the scan critical path, so cost remains an issue. One complementary approach could be to minimize what gets cloned: the reader only mutates the schema (repetition_type promotion and apply_arrow_schema()), so row-group and page-index data could in principle be shared. This would be a larger undertaking, and definitely out of scope for this PR. Worth filing a separate feature request to track, or noting this in #23131?
|
/ok to test 0053fb4 |
|
These might overlap conceptually with #23546. |
|
/ok to test 93bccd7 |
|
/ok to test 8b7563a |
| def version(self): | ||
| """Get the file format version.""" | ||
| return self.c_obj.version | ||
| return dereference(self.c_obj).version |
There was a problem hiding this comment.
Doesn't __new__ bypass __init__? Wouldn't FileMetaData.__new__(FileMetaData) work outside of from_libcudf?
|
|
||
| timer.start(); | ||
| auto const metadatas = cudf::io::read_parquet_footers(sources); | ||
| auto const metadatas = cudf::io::read_parquet_footers(sources, write_page_index); |
There was a problem hiding this comment.
#23131 would overlap the footer read with other operations, but the clone happens on the scan critical path, so cost remains an issue. One complementary approach could be to minimize what gets cloned: the reader only mutates the schema (repetition_type promotion and apply_arrow_schema()), so row-group and page-index data could in principle be shared. This would be a larger undertaking, and definitely out of scope for this PR. Worth filing a separate feature request to track, or noting this in #23131?
|
/ok to test b545203 |

Description
We've observed a performance penalty from enabling metadata prefetching in cudf-polars for data in local storage. We'd like to enable prefetching by default, since it's crucial for good performance with high-latency remote storage systems (S3). Also, you'd naively expect prefetching to help locally since you'd do less work (you avoid re-parsing the footer when reading different row groups out of the same file multiple times).
Over simplifying things, prefetching slowed things down because it's (surprisingly?) expensive to copy a
FileMetaDataobject. This pain was compounded in cudf-polars because we run concurrentread_parquets in a thread pool, and the GIL was held for these expensive copies.(Note: why are we copying in the first place? IIUC, it's because the parquet reader currently mutates something on the object in
read_parquet, so it needs an ownedFileMetaData.)This PR moves FileMetaData footers into Python without holding the GIL. This lets concurrent prefetches happen more... concurrently. See https://github.com/user-attachments/assets/c0f34117-8483-413a-a3de-e101a0093c12 for an nsys profile screenshot showing GIL contention.
After this fix, prefetching is still a clear win on S3 but there's still a ~16% penalty on NVMe. Rather than holding off entirely, this PR enables prefetching by default only for remote URIs (s3://, gs://, etc.). And queries that mix remote and local reads will only prefetch for the remote reads if metedata prefetching is unset (default).
Benchmarks (SF100, all 22 TPC-H queries, 256 kvikio threads):