perf: use compact pruning for large string IN lists - #24526
Open
sunchao wants to merge 2 commits into
Open
Conversation
sunchao
marked this pull request as ready for review
August 20, 2026 16:25
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## main #24526 +/- ##
==========================================
+ Coverage 81.27% 81.32% +0.04%
==========================================
Files 1116 1118 +2
Lines 395017 396357 +1340
Branches 395017 396357 +1340
==========================================
+ Hits 321055 322324 +1269
- Misses 55166 55188 +22
- Partials 18796 18845 +49 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
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.
Why are the changes needed?
Which issue does this PR close?
Related to #8668 and #8609; follows #24074. This PR is stacked on #24525, which must merge first.
Rationale for this change
Queries often select a sparse set of string identifiers. Parquet min/max statistics can make these queries much cheaper by ruling out row groups or pages that cannot contain any requested identifier. For example, this query asks for 21 IDs, spaced ten apart:
A row group whose values fall between
id003andid007cannot contain a match. The default pruning limit is 20, so this list is not eligible for theINmin/max rewrite unless the caller raises the limit. #24074 made that limit configurable. With a raised limit, DataFusion can already reject this row group, but it does so by constructing a growing expression tree resembling:For hundreds or thousands of identifiers, building and evaluating that tree can become expensive in its own right. Replacing the list with one enclosing range,
[id000, id200], would be cheaper, but would lose the gaps: that broad range overlaps[id003, id007]even though none of the requested IDs is present there.The aim is to keep the useful pruning precision of the existing per-value checks while making large lists cheaper to represent and evaluate. In the included local microbenchmark, evaluating 1,024 values against 4,096 intervals falls from 68.8 ms to 0.198 ms. This measures pruning work only, not end-to-end query speedup.
What changes were proposed in this PR?
What changes are included in this PR?
Eligible large string lists are stored as a sorted, deduplicated set of values inside the pruning predicate, rather than expanded into one comparison branch per value. For each inclusive statistics interval, DataFusion finds the first requested value at or after the interval's minimum, then checks whether that value is also at or before its maximum.
In the example, the first requested ID at or after
id003isid010. Sinceid010 > id007, the row group can be skipped. An interval such as[id019, id021]must be kept because it could containid020. This takes a binary search per interval after sorting the values once per constructed predicate, and the expression tree no longer grows with the number of IDs.The result remains a conservative pruning decision. An overlapping interval means only that a match is possible; the original
INexpression still performs exact row filtering. The original literal information also remains available to other pruning mechanisms, including Bloom filters. Missing or inverted bounds cannot prove that data is safe to skip.The existing limit continues to control eligibility. Its default remains 20, setting it to zero disables the
INmin/max rewrite, and lists beyond the configured cap remain ineligible. Only eligible positive, non-null literal string lists larger than 20 take the compact path.NOT IN, NULL-containing lists, and unsupported expressions keep their existing handling. Page-index pruning now receives the same configured cap as row-group pruning, so raising the limit can benefit both.The dependency on #24525 matters for correctness: an interval search is only meaningful when the stored bounds use the same comparison order as the query. That companion PR handles legacy or unrecognized Parquet byte-array ordering. It must land before the newly enabled large-list page-pruning path here.
Are there any user-facing changes?
Users who raise
datafusion.execution.parquet.max_in_list_sizeget cheaper min/max pruning for eligible large string lists, and page pruning now honors that setting. The configuration default, exact query results, and existing public APIs are unchanged. This is a focused optimization for literal string lists, not a general rewrite of every largeINexpression.How was this PR tested?
Are these changes tested?
The pruning-crate suite passed 93 tests. The standalone Parquet regressions use lists of 20, 21, 256, and 1,024 values and check both exact query results and scan/pruning metrics. They cover gaps inside the list's enclosing range, row-group pruning, page-only pruning, and the default and zero-cap controls.
Two correctness regressions exercise the less obvious interactions. A direct physical-source test uses
NOT IN (..., NULL), row-filter pushdown, andLIMIT 1, so logical optimizer folding cannot hide an incorrect decision to bypass filtering. A real-file test combines this PR with #24525 and verifies that compact page pruning cannot lose a matching row when the footer's ordering is missing or unknown. Against unchanged Apachef1f0449a, the positive row-group/page tests fail as expected; theNOT IN (..., NULL)control passes.The benchmark compares the actual raised-cap
INpath on Apachef1f0449aand this patch, using separate build directories and checking that both return the same nontrivial pruning results. Local results on an Apple M5 Max (18 CPUs, 128 GiB), Rust 1.97.0,release-nonlto, 20 samples:A balanced explicit OR tree is included as another comparison: at 1,024 values it takes 8.03 ms to evaluate the same intervals. These measurements isolate pruning overhead; no end-to-end workload improvement is claimed.
Formatting, all-targets/all-features Clippy with warnings denied, and
./dev/rust_lint.shpassed on the combined stack. The extended workspace run passed 10,674 Rust tests, with eight ignored, and all 503 SQL-logic files.Validation commands