Skip to content

Add PageIndexBuilder and PageIndexProvider for Parquet page indexes - #10842

Merged
etseidl merged 86 commits into
apache:mainfrom
etseidl:page_index_provider
Sep 8, 2026
Merged

Add PageIndexBuilder and PageIndexProvider for Parquet page indexes#10842
etseidl merged 86 commits into
apache:mainfrom
etseidl:page_index_provider

Conversation

@etseidl

@etseidl etseidl commented Aug 25, 2026

Copy link
Copy Markdown
Contributor

Which issue does this PR close?

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 PageIndexProvider trait to allow more performant implementations.

What changes are included in this PR?

Adds PageIndexBuilder, PageIndexProvider, implements PageIndexProvider for PageIndex, and adds RowGroupPageIndex as a helper to support fetching page indexes for a specific row group (replaces the X_index_for_rowgroup() functions on PageIndex).

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.

@etseidl

etseidl commented Aug 29, 2026

Copy link
Copy Markdown
Contributor Author

Thanks @alamb. The biggest change from your last look is the example is more compelling now.

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

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):

  1. Writer drops custom page indexes
  2. memory accounting on the trait
  3. doc cleanups

Comment thread parquet/examples/custom_page_index.rs Outdated
//! - 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

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.

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 🤔

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.

done in 0caa8b4

Comment thread parquet/examples/custom_page_index.rs Outdated
use std::sync::Arc;
use tempfile::TempDir;

//////////////////////////////////////////////

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.

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

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.

done in 0caa8b4

Comment thread parquet/examples/custom_page_index.rs
Comment thread parquet/examples/custom_page_index.rs
Comment thread parquet/examples/custom_page_index.rs

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.

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>()

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.

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)

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.

done in 690907c

need to file issue

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.

Comment thread parquet/src/file/metadata/mod.rs Outdated
// 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()));

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.

this clone deep copies the page index -- I think it should be something mor elike std::mem::size_of::<PageIndex>() + page_index.heap_size()

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.

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.

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.

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.

Comment thread parquet/src/file/metadata/mod.rs Outdated
#[cfg(not(feature = "encryption"))]
let encryption_size = 0usize;

// We can only determine the heap size for PageIndex. Custom providers are

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.

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)

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.

added TODO in d562697

need to file an issue for this

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.

Comment thread parquet/src/file/metadata/page_index.rs Outdated
/// [`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

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.

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

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.

attempted fix in 39c5828

@etseidl

etseidl commented Sep 2, 2026

Copy link
Copy Markdown
Contributor Author

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.

Comment thread parquet/src/file/metadata/mod.rs
@bharath-techie

Copy link
Copy Markdown

Thanks for the changes @etseidl

@alamb alamb 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 @etseidl -- I went through this one again

I filed some follow on tickets

And then I pushed some commits to:

  1. Update the TODO comments with links to issues
  2. 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!

Comment thread parquet/src/file/metadata/mod.rs Outdated
#[cfg(not(feature = "encryption"))]
let encryption_size = 0usize;

// We can only determine the heap size for PageIndex. Custom providers are

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.

@etseidl

etseidl commented Sep 8, 2026

Copy link
Copy Markdown
Contributor Author

Thanks @alamb, your changes look good. I'll merge once CI finishes.

@etseidl
etseidl merged commit 7438071 into apache:main Sep 8, 2026
32 checks passed
@etseidl
etseidl deleted the page_index_provider branch September 8, 2026 22:44
@adriangb

adriangb commented Sep 8, 2026

Copy link
Copy Markdown
Contributor

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)?

@alamb

alamb commented Sep 9, 2026

Copy link
Copy Markdown
Contributor

We did it!

@etseidl

etseidl commented Sep 9, 2026

Copy link
Copy Markdown
Contributor Author

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)?

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

api-change Changes to the arrow API next-major-release the PR has API changes and it waiting on the next major version parquet Changes to the parquet crate

Projects

None yet

Development

Successfully merging this pull request may close these issues.

parquet: PageIndex cannot be constructed outside the crate

4 participants