fix(parquet): avoid seeking into the middle of a row group when prefetch and page index filtering are both enabled - #185
Conversation
zjw1111
left a comment
There was a problem hiding this comment.
Thanks for the detailed write-up and for fixing this — the root-cause analysis (prefetch coordinator vs. page-filtered read path disagreeing on the reader position) is very clear, and I appreciate that both new tests are verified to fail before the corresponding fix.
I reviewed the two changes and did not find blocking issues:
HandleReadResult()re-keying the batch to the read range that actually contains it looks correct. The "recurses at most once" reasoning holds: sinceglobal_row_ids[0]lies withinowner_range, the recursive call still hasslice_begin == 0but necessarilyslice_end >= 1, so it cannot produce a zero slice end again. Restricting the lookup toread_ranges_in_group_[reader_idx]preserves the "one read range served by exactly one queue" property, and themin_rangeretirement inNextBatchWithBitmap()keeps output ordering unchanged. Thenulloptfallback also stays safe because it only landsreaders_pos_on a row-group-aligned range start.NextPageFiltered()advancingnext_row_to_read_viaMapFilteredIndexToOriginalRowcorrectly skips holes inside a row group'sRowRangesand lands on the row group end once the filtered ranges are exhausted, matching the semantics used byNextFullyMatched()/SeekToRow().
One minor, non-blocking follow-up on the multi-reader path is left as an inline note. The known GenReadRanges() follow-up you called out seems like a reasonable separate change.
|
+1 |
zjw1111
left a comment
There was a problem hiding this comment.
Thanks for the updates — the comment wording now reads accurately, and bumping the prefetch parallelism to 2 nicely covers the multi-reader path I was wondering about (the surviving read ranges get dispatched across two sub readers, and a reader still crosses row group boundaries because its assigned ranges are non-contiguous). The core fix is unchanged and still looks good.
One small leftover inline note below.
…tch and page index filtering are both enabled (#185) * test: add test cases to show problem * fix: avoid seek to the middle of a RowGroup when reading a parquet with prefetch=on and page-inex-filter=on * test: update test cases * fix: partially-matched path do not push next_row_to_read * test: update test comments and enable multi-thread reading * style: update comments * clang-format
…tch and page index filtering are both enabled (#185) * test: add test cases to show problem * fix: avoid seek to the middle of a RowGroup when reading a parquet with prefetch=on and page-inex-filter=on * test: update test cases * fix: partially-matched path do not push next_row_to_read * test: update test comments and enable multi-thread reading * style: update comments * clang-format
Purpose
Reading a Parquet file with prefetch enabled and
parquet.read.enable-page-index-filterleft at itsdefault (
true) can fail with:The prefetch coordinator and the page-filtered read path disagree about what a reader position is:
PrefetchFileBatchReaderImpldrives its sub readers with read ranges fromParquetFileBatchReader::GenReadRanges(), which are row group aligned and cover all rowgroups, independent of any pushed down predicate.
row group rather than at its start, and the sub reader crosses into the next row group as soon as
the current one is exhausted, while the coordinator still points at the previous read range.
HandleReadResult()then computesslice_end == 0("fully out of range"), drops the batch andrecords that mid-row-group row as
readers_pos_, expecting to seek back to it later.FileReaderWrapper::SeekToRow()only accepts row group boundaries, so the followingEnsureReaderPosition()fails and the read is aborted. The drop-and-re-read strategy comes fromORC, whose
RowReader::seekToRow()accepts an arbitrary row; it cannot work for Parquet.Two independent defects are fixed:
1.
PrefetchFileBatchReaderImpl::HandleReadResult()no longer drops a batch that belongs to alater read range. It looks up the read range that actually contains the batch and re-keys the batch
to it, so no backward seek is needed at all. Rows that belong to another sub reader are still
dropped, which is safe because that reader produces them. The lookup is restricted to
read_ranges_in_group_[reader_idx], i.e. the ranges assigned to the producing reader, so theexisting "one read range is served by exactly one queue" property is preserved and output ordering is
unchanged. The recursion can happen at most once, because the first row id lies inside the range
found and therefore cannot produce a zero slice end again.
2.
FileReaderWrapper::NextPageFiltered()now advancesnext_row_to_read_. It used to leave thecursor at the row group start for the whole duration of a partially matched row group, so
GetNextRowToRead()reported a position the reader had long passed. The cursor now points at thenext row that survives filtering (mapped through the row group's
RowRanges), or at the row groupend once the filtered ranges are exhausted. This keeps the coordinator's read range in step with the
sub reader, so the "fully out of range" path above becomes a pure fallback instead of the common case.
Notes on behaviour:
read_ranges_cursor, and read rangesthat yield no data are still retired by the existing
min_rangerule inNextBatchWithBitmap().after a drop plus re-seek round trip, which removes one redundant row group decode.
Known follow-up, intentionally left out:
ParquetFileBatchReader::GenReadRanges()still reports everyrow group, including those already pruned by the predicate. Aligning it with the surviving target row
groups would avoid empty read ranges, but it also changes range dispatch and the adaptive prefetch
heuristic, so it deserves its own change.
Tests
Two new cases, each verified to fail before the corresponding fix:
WriteAndReadInteTest.TestAppendWithParquetPageIndexFilterAndPrefetch(test/inte) - to verify that pre-metioned problem is solved.FileReaderWrapperTest.PageFilteredAdvancesNextRowToRead(paimon-parquet-format-test) — to verify thatnext_row_to_read_is pushed forward.API and Format
No.
Documentation
No.
Generative AI tooling
Generated-by: GLM 5.2