Repository navigation
Conversation
A FilePruner builds its PruningPredicate when its file opens, and again whenever a dynamic filter in its predicate changes. A build snapshots the dynamic filters, simplifies the predicate and rewrites it in terms of statistics, which costs much more than evaluating it against one file's statistics. A scan of many small files under a TopK, which prunes most of them at open, spent most of its CPU building the same pruning predicate once a file. The FilePruners of a partition's files now share a PruningPredicateCache that keeps the last one built, with the predicate it was built from (after partition values are replaced with literals), the file schema, and the generation of the predicate's dynamic filters. A file whose predicate, schema and generation match reuses it. One entry is enough when files with the same partition values are next to each other, and it bounds the cache at one pruning predicate a partition. The predicate_creation_errors metric now counts a failed build once, rather than once for each file that would have built it again. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> (cherry picked from commit c8c3a7d)
Author
|
apologies -- the bot posted this without my permission :/ i do not vouch for this code, feel free to ignore |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
🤖 Written by Claude (an AI assistant) and posted from my account.
Which issue does this PR close?
Rationale for this change
A Parquet scan with a predicate builds a
PruningPredicatefor each file'sFilePrunerwhen it opens the file, and again whenever a dynamic filter in the predicate changes. A build snapshots the dynamic filters, simplifies the predicate and rewrites it in terms of statistics, which costs much more than evaluating the result against one file's statistics. Files with the same partition values build the same pruning predicate, so a scan of many small files does that work once a file.It shows most under a
TopKover many files that carry statistics: the first files fill the TopK, and its dynamic filter then prunes most of the rest at open, each after building the pruning predicate from scratch. In a service of ours whose query reads ~350PartitionedFiles (row ranges of Parquet files, each with min/max statistics for the sort column, in one partition) under a newest-firstTopK,FilePruner::should_prunewas 45% of a query's CPU: 31% building the pruning predicate, 13% evaluating it.What changes are included in this PR?
PruningPredicateCacheindatafusion-pruning: one slot holding the last pruning predicate built, with the predicate it was built from (after partition values are replaced with literals), the file schema, and the predicate'ssnapshot_generation. Clones share the slot.FilePruner::with_cache, additive:should_prunetakes the pruning predicate from the cache, and builds it only when the predicate, the schema or the generation differs.ParquetMorselizerkeeps one cache per partition and gives it to each file's pruner.For review:
predicate_creation_errorsnow counts a failed build once, not once for each file that would have built it again.FilePruner, dynamic filters included, so the two don't overlap.What is the testing strategy for this PR?
file_pruner::tests::files_with_the_same_predicate_share_its_pruning_predicate: under aDynamicFilterPhysicalExpr, a second file reuses the first's pruning predicate; afterupdate, the next check builds a new one, which the files after it reuse; each file is pruned or kept as before.file_pruner::tests::files_with_different_predicates_build_their_own.opener::test::test_prune_on_partition_values_of_each_file_of_a_scan: one morselizer opens files with different partition values, interleaved, under a dynamic filter, and each is pruned or kept by its own partition value.Benchmark:
parquet_pruning_setup_cache/same_schema_files(128 files, static predicate), main (5f21582) against this branch, four runs each taking turns on a laptop, LTO off: median 11.1 ms → 10.1 ms (−9%), with about ±10% between runs.Not known:
SELECT id FROM t ORDER BY id LIMIT 10over the same files) was within noise. I haven't checked whether its files reach theFilePruner, or are skipped before it.Are there any user-facing changes?
PruningPredicateCacheandFilePruner::with_cacheare new public API indatafusion-pruning. Nothing breaks.