[core] Support file index and predicate push down in DataEvolutionSplitRead#8839
[core] Support file index and predicate push down in DataEvolutionSplitRead#8839wombatu-kun wants to merge 5 commits into
Conversation
…itRead DataEvolutionSplitRead.withFilter was a no-op, so data evolution tables never used their file indexes on read: AppendOnlyFileStoreScan evaluates the embedded index in filterByStats(ManifestEntry), but DataEvolutionFileStoreScan overrides that method and only checks row id ranges. Bloom, bitmap and bsi indexes were written for data evolution files and never read back. Format level push down did not reach the reader either, because the FormatReaderMapping.Builder was constructed without filters. Push down is now applied where it can not interfere with the column merging. A file read without merging gets the full treatment: the file index can skip it, a bitmap index result narrows the read to the matching positions, and the filters reach the format reader. A merged group only uses the file index to skip the whole group, since DataEvolutionFileReader zips its readers positionally and dropping rows in one of them would break the alignment. The existing enabledFilterPushDown flag of FormatReaderMapping.Builder already draws exactly that line, so no extra branching is needed for the format level part. Only plain data files may prove that a merged group can be skipped. mergeRangesAndSort guarantees they all span the row id range of the whole group, while a blob or vector-store file only covers a sub range and proves nothing about the other rows. Filters on row tracking fields are never pushed down. _ROW_ID and _SEQUENCE_NUMBER are assigned from the manifest entry rather than read from the file, and data evolution may reassign row ids, so a physical copy in the file can be stale. Dropping them also keeps filter devolution working, as it resolves predicate fields against the table schema, which has no system fields. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…on file Follow-up to the file index push down. A column missing from a data evolution file is not null, its values live in another file of the same row id range, so a predicate on it must not be pushed down. Formats such as Parquet read a column absent from the file schema as all null and drop every row, which silently lost rows once the scan pruned the group down to the file that does not carry the column. The single file path now pushes only the filters the file can answer, computed from its writeCols against the table fields. Its reader mappings move to a dedicated cache keyed by writeCols, so they can not collide with the merge path cache, which keys by the projected read field names and pushes no filters. New tests: a filter on a column absent from the read file returns all rows across parquet, orc and avro, and a bitmap selection composes correctly with a deletion vector. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
JingsongLi
left a comment
There was a problem hiding this comment.
skipByFileIndexiterates through all files in a merge group; if any old file’s index is determined to be a mismatch, the entire group is skipped.- However, the same field may have been overwritten by an updated file, rendering the old file effectively obsolete.
- New local regression: Old
f1=a*, newf1=c*; queryf1=‘c050’; expected 100 rows, actual 0 rows; consistently reproducible.
I suggest you use ChatGPT and set it to the highest review level; as far as I recall, your PR has already contained several serious errors. This is dangerous for the community.
|
Fixed in 40f9115: skipByFileIndex now evaluates each file's index only over the columns it is the newest writer of (matching the winner selection in evolutionStats and createUnionReader). Added regression test testMergedGroupKeptWhenFilterColumnOverwritten reproduces the report (old f1=a*, new f1=c*, query f1='c050' -> 100 rows). |
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
JingsongLi
left a comment
There was a problem hiding this comment.
It is better to introduce Table tests to validate reading with filter.
…path Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
Done d3c0e6f. Each scenario now also reads the whole plan through |
Purpose
Resolves the TODO in
DataEvolutionSplitRead.withFilter, which was a no-op: Support File index push down (all conditions) and Predicate push down (only if no column merge).As a result, data evolution tables never used their file indexes on read:
AppendOnlyFileStoreScanevaluates the embedded index infilterByStats(ManifestEntry), butDataEvolutionFileStoreScanoverrides that method and only checks row id ranges. Bloom, bitmap and bsi indexes were written for data evolution files and never read back. Format level push down did not reach the reader either, since theFormatReaderMapping.Builderwas built withfilters = null.Push down is now applied wherever it can not interfere with the column merging:
BitmapIndexResultnarrows the read to the matching positions, and the filters reach the format reader, mirroringRawFileSplitRead.DataEvolutionFileReaderzips its inner readers positionally, so dropping rows in one of them would break the column alignment. Skipping the group is sound because a logical row takes each column from exactly one file.The existing
enabledFilterPushDownflag ofFormatReaderMapping.Builderalready draws that line (the merge path callsbuild(..., false)), so passingfiltersinto the builder needs no extra branching.Three safety points:
mergeRangesAndSortassertsareAllRangesSame(dataFiles), while a blob or vector-store file covers only a sub range and proves nothing about the other rows._ROW_IDand_SEQUENCE_NUMBERare never pushed down: they are assigned from the manifest entry, and data evolution may reassign row ids, so a physical copy in the file can be stale.writeCols, and the single file reader mappings use a dedicated cache keyed bywriteColsso they can not collide with the merge path cache.Semantics are unchanged:
withFilteris a hint, and this only drops rows proven not to match.Benchmark (throwaway, not in the diff): 200k rows in 40 row id groups, ids interleaved so min/max stats prune nothing, bloom filter, point filter. 39 of 40 groups skipped.
Left for a follow up: row level bitmap selection inside a merged group, which needs the bitmap translated into global row id ranges and fed through the existing
rowRangesmechanism.Tests
New
DataEvolutionFileIndexTest(11 tests). Readers are created directly from the splits withoutexecuteFilter(), so an empty result proves the reader itself pruned the rows; filter values sit inside the column min/max range so the scan does not drop the split first.Covered: a single file group skipped by a standalone
.indexfile, a merged group skipped by an embedded index, column alignment preserved in a merged group, exact bitmap index selection, a merged group skipped afterRENAME COLUMN(the filter has to be devolved by field id), a filter on a column absent from the read file returns all rows across parquet, orc and avro, a bitmap selection composed with a deletion vector,file-index.read.enabled = false, and a filter mixing_ROW_IDwith a data column.