feat(parquet): multi-column lexicographic stats reorder for TopK sort pushdown#23888
Draft
zhuqi-lucas wants to merge 1 commit into
Draft
feat(parquet): multi-column lexicographic stats reorder for TopK sort pushdown#23888zhuqi-lucas wants to merge 1 commit into
zhuqi-lucas wants to merge 1 commit into
Conversation
… pushdown Implements apache#22198 P0: PreparedAccessPlan::reorder_by_statistics and ParquetSource::reorder_files previously keyed only off the leading sort column's min statistic. When the leading key ties across row groups or files (e.g. ORDER BY low_cardinality_col, ts LIMIT k), the reorder was a no-op, the scan read row groups in disk order, and the TopK dynamic filter never tightened enough for the runtime RG pruner to fire. Both layers now sort lexicographically over the longest plain-Column prefix of the sort order, by per-column min with per-column directions expressed relative to the leading key (the leading key stays normalized ASC at the RG level; direction is applied by the later reverse() as before). The included end-to-end test shows the effect: leading key tied across 5 row groups, secondary key clustered but stored in adversarial DESC disk order — before this change row_groups_pruned_dynamic_filter=0 and all 500 rows decode; after it, the scan reads the best RG first and prunes the other 4.
zhuqi-lucas
marked this pull request as draft
July 25, 2026 15:48
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #23888 +/- ##
========================================
Coverage 80.65% 80.66%
========================================
Files 1092 1092
Lines 371106 371300 +194
Branches 371106 371300 +194
========================================
+ Hits 299323 299508 +185
- Misses 53937 53943 +6
- Partials 17846 17849 +3 ☔ 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.
Which issue does this PR close?
Rationale for this change
PreparedAccessPlan::reorder_by_statistics(row-group level) andParquetSource::reorder_files(file level) key only off the leading sort column'sminstatistic. When the leading key ties across row groups or files — theORDER BY low_cardinality_col, ts LIMIT kshape — the reorder degenerates to a no-op, the scan reads data in disk order, and the TopK dynamic filter's threshold improves monotonically without ever proving a later row group unwinnable. Result: zero runtime RG pruning and a full-file decode even though per-column statistics contain everything needed to read the best row group first.The end-to-end test in this PR demonstrates the failure concretely on current main: leading key tied across 5 row groups, secondary key clustered but stored in adversarial DESC disk order →
row_groups_pruned_dynamic_filter=0, all 500 rows decoded. With this change the scan reads the best RG first and prunes the other 4.Note the pruning machinery itself already handles multi-column lexicographic dynamic filters (the leading disjunct of
a < x OR (a = x AND b < y)prunes onmin(a)stats — verified by the other new e2e test, which passes on main unmodified). The gap was purely in the reorder layers feeding it.What changes are included in this PR?
Both reorder layers now sort lexicographically over the longest plain-
Columnprefix of the sort order, using per-columnminstatistics:access_plan.rs): switch fromsort_to_indicesover the leading column's mins tolexsort_to_indicesover per-column min arrays. The leading key stays normalized ASC (direction still applied by the downstreamreverse(), unchanged); secondary keys sort by their direction relative to the leading key, with null placement pre-flipped when the reverse is coming so the post-reverse order matches the request. The prefix walk stops at the first non-Column/ not-in-file-schema expression (leading-key-only graceful skips unchanged).sort.rs):extract_topk_sort_infoextended from the single leading column to the plain-Columnprefix, comparator compares tuple-wise with per-column direction; missing-stats files still sort last per column.Behavior for single-column sort orders is unchanged.
Are these changes tested?
reorder_by_statistics: secondary tie-break, relative secondary direction (a ASC, b DESC), DESC/DESC normalization throughreverse(), and prefix-stop on a non-Columnsecondary expression.dynamic_row_group_pruning.rs:dynamic_row_group_pruningintegration tests, andtopk.slt/sort_pushdown.slt/dynamic_row_group_pruning.slt/dynamic_filter_pushdown_config.sltall pass.topk_tpch(sort-tpch-l 100) shows no regressions (all 11 queries within noise; lineitem's secondary keys are not clustered when the leading key ties, so no wins expected on that suite — the win case is theORDER BY low_card, tsshape shown in the e2e test).Are there any user-facing changes?
No API changes. Queries with multi-column
ORDER BY ... LIMITover parquet whose leading sort key ties across row groups / files can now skip row groups at runtime instead of decoding the whole file.