mbutrovich opened a new issue, #23978:
URL: https://github.com/apache/datafusion/issues/23978

   ### Describe the bug
   
   apache/datafusion#22857 (backported to branch-54 as #23088, released in 
54.1.0) reordered the Parquet opener's state machine so the initial metadata 
load requests `PageIndexPolicy::Skip`, and the page index is loaded later, only 
if row-group statistics can't already prove the surviving row groups are fully 
matched.
   
   When that later load does run, it goes through the free function 
`load_page_index` in `datafusion/datasource-parquet/src/opener/mod.rs`:
   
   ```rust
   async fn load_page_index<T: AsyncFileReader>(
       reader_metadata: ArrowReaderMetadata,
       input: &mut T,
       options: ArrowReaderOptions,
   ) -> Result<ArrowReaderMetadata> {
       ...
       let mut reader = ParquetMetaDataReader::new_with_metadata(m)
           .with_page_index_policy(PageIndexPolicy::Optional);
       reader.load_page_index(input).await?;
       ...
   }
   ```
   
   This calls `ParquetMetaDataReader::load_page_index`, which reads directly 
off the `AsyncFileReader`'s byte-range methods. It never goes through 
`ParquetFileReaderFactory::get_metadata` / `DFParquetMetadata::fetch_metadata`, 
so the result is never written back into the `FileMetadataCache` that the 
initial (Skip-policy) metadata load populated.
   
   For files where the skip heuristic never fires, this makes every open of the 
same file pay for a fresh, uncached page-index fetch, for as many times as the 
file is opened (once per row-group split / partition). Before #22857, 
`CachedParquetFileReaderFactory` (then documented as "always loads the entire 
metadata, including page index, even if not required by the current query") 
loaded and cached both in one request. After #22857, the equivalent case (skip 
doesn't fire) now costs one cached footer fetch plus one uncached page-index 
fetch, repeated on every open.
   
   ### To Reproduce
   
   This surfaced in Apache DataFusion Comet (apache/datafusion-comet#3978) on a 
TPC-DS q88-style query: three `IS NOT NULL` predicates on non-null foreign keys 
against `store_sales`, scanned across 10,237 files and 1,824 partitions.
   
   Comparing DataFusion 54.0.0 (pre-#22857) to 54.1.0 (post-backport) on the 
same query and data, cumulative `CometNativeScan` metrics for `store_sales` 
moved like this (summed across all task instances in the query's physical plan):
   
   | metric | 54.0.0 | 54.1.0 |
   | --- | --- | --- |
   | Wall clock time elapsed for file opening | 79.5 min | 149.8 min (+88%) |
   | Wall clock time elapsed for data decompression + decoding | 57.0 min | 
77.6 min (+36%) |
   | Total time reading and parsing footer metadata | 79.0 min | 89.1 min 
(+13%) |
   | Number of bytes scanned | 3.4 GiB | 3.5 GiB (+3%) |
   
   Bytes scanned barely moved (ruling out "loading more data"), while 
file-opening wall clock nearly doubled: a request-count/latency signature, not 
a data-volume signature, consistent with a second, uncached fetch being added 
per open.
   
   A minimal repro (no cluster) would be: a Parquet file with a page index, a 
`IS NOT NULL` predicate on a column whose row groups don't carry `null_count` 
statistics (or use a writer that omits it), opened twice through 
`CachedParquetFileReaderFactory` sharing one `FileMetadataCache`. Assert that 
the second open's `AsyncFileReader::get_metadata` does not re-issue a range 
read for the page index. On 54.1.0 (or current main) it does; it also does 
after the first open in the 54.0.0 case, but there the initial factory call 
already fetched and cached it, so nothing further happens.
   
   ### Expected behavior
   
   Once the opener determines the page index is needed and loads it via 
`load_page_index`, the result should be merged back into the same 
`FileMetadataCache` entry that the initial (Skip-policy) load populated, so a 
second open of the same file for a different row-group range gets a cache hit 
instead of repeating the fetch. The skip optimization from #22857 should still 
avoid the load entirely when it can; this is about not silently losing caching 
in the case where it can't.
   
   ### Additional context
   
   - apache/datafusion#22795: the original design doc for the skip optimization.
   - apache/datafusion#22857 / branch-54 backport #23088: introduced the 
reordering and the bypass.
   - apache/datafusion-comet#3978: where this was diagnosed downstream. Comet's 
workaround (forcing eager, always-cached page-index loading via a Comet-owned 
`ParquetFileReaderFactory`, giving up the skip's benefit to restore caching) is 
not a fix for this issue, just a way to unblock Comet while this is open 
upstream.
   - Relevant code: `load_page_index` (module-level free function) and 
`RowGroupsPrunedParquetOpen::load_page_index` in 
`datafusion/datasource-parquet/src/opener/mod.rs`; 
`DFParquetMetadata::fetch_metadata` / `CachedParquetFileReaderFactory` in 
`datafusion/datasource-parquet/src/metadata.rs` and `reader.rs`.


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to