Add PageIndexBuilder and PageIndexProvider for Parquet page indexes - #10842
Conversation
|
Thanks @alamb. The biggest change from your last look is the example is more compelling now. |
alamb
left a comment
There was a problem hiding this comment.
Looks good to me -- the example is especially nice now I think
Thank you @etseidl
I think we should avoid the clone in heap_size before merging, but everything else can be done as a follow on (I can help file tickets if you like):
- Writer drops custom page indexes
- memory accounting on the trait
- doc cleanups
| //! - Uses nested HashMaps for efficient storage and lookup | ||
| //! - Implements all required PageIndexProvider trait methods | ||
| //! | ||
| //! This approach can significantly reduce memory usage when working with wide |
There was a problem hiding this comment.
Can we also claim it reduces metadata load time?
"This approach can significantly reduce memory usage and metadata load time when working with wide
tables and you only access a few columns".
Though maybe we should also explain when page level statistics are helpful (evaluating predicates (stats) or fetching specific ranges of rows (after predicates or index application). That might be too nuanced however 🤔
| use std::sync::Arc; | ||
| use tempfile::TempDir; | ||
|
|
||
| ////////////////////////////////////////////// |
There was a problem hiding this comment.
nit: my personal preference is to put helpers at the end so the example starts with the "punchline" and then people can refer to the details if they need. However I am not sure how important that is going forward with coding agents, etc
There was a problem hiding this comment.
We can finally have nice things -- a module that has a page index separated -- so nice!
| if let Some(page_index_arc) = self.metadata.page_index.as_ref() | ||
| && let Some(page_index) = page_index_arc | ||
| .as_any() | ||
| .downcast_ref::<crate::file::metadata::PageIndex>() |
There was a problem hiding this comment.
Downcast to PageIndex to access raw index structures for serialization
Claude points out that this means that the writer not write page indexes if the provider is a custom provider. I think we need to either explicitly call that out in docs, or (preferably) actually serialize the page indexes when sourced from a custom provider
We could document the limitation in this PR and then fix it in a follow on PR (I bet if we wrote up a ticket someone else would do it)
There was a problem hiding this comment.
| // out of scope. | ||
| let page_index_size = if let Some(page_index) = self.page_index.as_ref() { | ||
| if let Some(page_index) = page_index.as_any().downcast_ref::<PageIndex>() { | ||
| let page_index = Some(Arc::new(page_index.clone())); |
There was a problem hiding this comment.
this clone deep copies the page index -- I think it should be something mor elike std::mem::size_of::<PageIndex>() + page_index.heap_size()
There was a problem hiding this comment.
Yeah, I knew this was janky. I'm working through the correct thing to do here. I think this form was undercounting when the page index was Some anyway.
I want to push heap_size into the provider API now because I think adding a function to a public trait constitutes a breaking change. I'll see if I can get this right today.
There was a problem hiding this comment.
fixed in 3e382e5
I did some manual accounting. For an unpopulated index, the size should go from the size of an Option<PageIndex> 48 bytes to Option<Arc<...>> 16 bytes so a net -32 bytes.
For a populated index, we need to add in the heap size of the index (450 bytes), the size of the now heap allocated PageIndex (48 bytes), plus 16 bytes of heap allocated Arc overhead. This adds 64 bytes to the heap size, but we saved 32 above, so we should net increase 32 bytes for the test case.
| #[cfg(not(feature = "encryption"))] | ||
| let encryption_size = 0usize; | ||
|
|
||
| // We can only determine the heap size for PageIndex. Custom providers are |
There was a problem hiding this comment.
I think it is fine to not include heap size in the memory usage calculation in this PR, but we should file a follow on PR to add an API for a custom index provider to report its memory usage (as one of the main points of this PR is to have more efficient caching, for which we need to know how large the memory usage is)
There was a problem hiding this comment.
added TODO in d562697
need to file an issue for this
There was a problem hiding this comment.
| /// [`OffsetIndex`]: crate::file::page_index::offset_index::OffsetIndexMetaData | ||
| /// [`ColumnChunkMetaData`]: crate::file::metadata::ColumnChunkMetaData | ||
| pub trait PageIndexProvider: Send + Sync + std::fmt::Debug { | ||
| /// Returns `true` if offset index structures are present |
There was a problem hiding this comment.
It is probably also worth mentioning here that if has_offset_indexes should return false only of offset_index will always return false -- aka if this method reports false, the reader/writer won't even try to load any offset indexes
As written it is slightly unclear if it should report false of no indexes are currently loaded but some might be loaded in the future
…o page_index_provider
|
Thanks @alamb! I think I've addressed all of your comments, PTAL when you can. I still need to add issues for the serialization gap and memory accounting. I think the former is a pretty easy fix, but the latter will take some thought (we've gone down this rabbit hole before (e.g. #9138) Edit: I have the serialization ready to go once this merges. Non-breaking so it can wait for 60.1.0. |
|
Thanks for the changes @etseidl |
alamb
left a comment
There was a problem hiding this comment.
Thanks @etseidl -- I went through this one again
I filed some follow on tickets
And then I pushed some commits to:
- Update the TODO comments with links to issues
- Fix some other random small cleanups found while reviewing
I think we are ready now. Maybe you can take one last quick peek and then merge it in!
| #[cfg(not(feature = "encryption"))] | ||
| let encryption_size = 0usize; | ||
|
|
||
| // We can only determine the heap size for PageIndex. Custom providers are |
There was a problem hiding this comment.
|
Thanks @alamb, your changes look good. I'll merge once CI finishes. |
|
Amazing to see this land! @etseidl do you plan to use this to work on alternative storage / representation for page indexes (particularly for wide tables with sparse reads)? |
|
We did it! |
Thanks @adriangb. TBH I haven't really thought that far ahead. I was hoping to get more requirements from the Datafusion side before doing too much further engineering. I'm certainly interested in having other providers in the parquet crate. |
Which issue does this PR close?
PageIndexcannot be constructed outside the crate #10824.Rationale for this change
This grew out of a discussion in #10784 and relates to apache/datafusion#24288 (comment). This PR provides a new builder for creating page index structures, and also adds a new
PageIndexProvidertrait to allow more performant implementations.What changes are included in this PR?
Adds
PageIndexBuilder,PageIndexProvider, implementsPageIndexProviderforPageIndex, and addsRowGroupPageIndexas a helper to support fetching page indexes for a specific row group (replaces theX_index_for_rowgroup()functions onPageIndex).Are these changes tested?
Yes, should be covered by existing tests
Are there any user-facing changes?
Yes, this changes the public API for accessing page index information
Created with the aid of Claude Code, but I own the changes.