fix(parquet): preserve explicit mask with sparse pages#10288
Conversation
|
@alamb 👋Hi, This PR is now ready for review and updated with the latest main. |
|
Ok, thank ou @hhhizzz -- I will try. It just takes me a qhile to review non trivial PRs like this (not just the code, but also the approach). |
Thanks, and no rush at all. Please take whatever time works for you. I completely understand that reviewing the overall approach of a non-trivial change takes significantly more time than just reviewing the code. I’ve been focusing on this area recently, and I added a few diagrams to the PR description in the hope that they make the design and execution flow easier to understand. |
Fully resolves the sparse-page failure mode for mask-backed Parquet row selection: explicit
RowSelectionPolicy::Masknow remains correct when page pruning loads only discontiguous parts of a row group, without failing or falling back to selectors.Related issues
Autopolicy behavior is related to [Parquet] Adaptively to pick between RowSelection and Mask filter representation #8846 and Filter pushdown selectivity threshold #9591. This PR supportsAutoresolving to either mask-backed or selector-backed execution, but intentionally does not implement the broader adaptive strategy changes discussed there.Problem and fix
Mask-backed execution previously assumed that every row covered by the absolute mask was available for decoding. Page pruning breaks this assumption: only pages containing selected rows may be loaded, leaving discontiguous ranges inside a row group. Applying the original mask as though the complete row group were present could attempt to decode a page that was never fetched and fail with invalid page offsets.
Previous handling avoided some failures by switching to selector-backed execution, which meant explicit
RowSelectionPolicy::Maskwas not reliable for sparse pages. This PR removes the underlying assumption and fully resolves the failure mode while preserving mask semantics.Mask-backed execution now:
BatchReadPlan::DecodeAndFilterplans carrying each decoded range and its matchingMaskSlice.The mask is not converted into selectors. For example, Page A is processed as
read_records(8) -> consume_batch() -> filter_record_batch(10110101).skip_records(8)only advances across the unloaded Page B gap; Page B is never decoded.For multi-column projections, Parquet leaves can have different page boundaries because each column chunk is encoded and paginated independently. A resulting Arrow batch needs every projected leaf for the same rows, so using the union of loaded ranges would still ask at least one leaf to read an unloaded page. The safe decode ranges are therefore the intersection across all projected leaves.
As a result:
RowSelectionPolicy::Maskremains mask-backed with sparse pages;Autocan safely resolve to either mask-backed or selector-backed execution.No public APIs are changed.
Concepts introduced
RowSelectionPolicy: the user-facingSelectors,Mask, orAutopreference.RowSelectionStrategy: the concrete execution strategy resolved from that preference and the selection characteristics.LoadedRowRanges: absolute row-group ranges backed by loaded pages for every projected leaf.LoadedRowRangeCursor: monotonic traversal of loaded ranges without rescanning previously visited fragments.BatchReadPlan: an explicit physical contract:DecodeUpTo,DecodeExact, orDecodeAndFilter.RowGroupReadCursor: ownership ofArrayReaderexecution and absolute row-group position.These boundaries separate policy resolution, batch planning, decoder position, cardinality requirements, decoding, and post-decode filtering.
Testing
Coverage includes:
Automask execution with sparse loaded pages;Autoresolving to selectors;Validated with:
cargo fmt --check git diff --check cargo test -p parquet --all-features cargo clippy -p parquet --all-features --all-targets -- -D warnings