Support for multiple columns and index granularity > 1 in top-K threshold filtering optimization#93545
Conversation
|
Workflow [PR], commit [1fcb0d9] Summary: ❌
|
|
Clean CI run at |
There was a problem hiding this comment.
Pull request overview
This PR extends the top-K threshold filtering optimization to support two new scenarios: 1) multiple columns in the ORDER BY clause (using the first column's value for threshold determination), and 2) min-max indexes with granularity > 1 (by replicating index values across corresponding table granules and handling ties appropriately).
Changes:
- Extended
TopKFilterInfoto track the number of sort columns - Modified index granule handling to support index granularity > 1 by replicating min-max values across table granules
- Added tie-handling logic to ensure correct results when using the first column of multi-column sorts or granularity > 1
- Updated threshold comparison operators from
>=/<=to>/<to properly handle tie scenarios
Reviewed changes
Copilot reviewed 11 out of 11 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
src/Processors/TopKThresholdTracker.h |
Changed threshold comparison operators to strict inequalities for tie handling |
src/Processors/QueryPlan/ReadFromMergeTree.h |
Added num_sort_columns field to TopKFilterInfo |
src/Processors/QueryPlan/ReadFromMergeTree.cpp |
Removed granularity==1 requirement and enhanced logging |
src/Processors/QueryPlan/Optimizations/optimizeTopK.cpp |
Removed single-column restriction, added column validation, and passed num_sort_columns |
src/Storages/MergeTree/MergeTreeIndexMinMax.h |
Added parameters for index granularity and tie handling |
src/Storages/MergeTree/MergeTreeIndexMinMax.cpp |
Implemented granule replication and tie-handling logic with template specialization |
src/Storages/MergeTree/MergeTreeDataSelectExecutor.cpp |
Integrated new parameters and tie-handling flag |
tests/queries/0_stateless/03711_top_k_by_skip_index_negative.sql |
Test cases verifying optimization doesn't apply to invalid scenarios |
tests/queries/0_stateless/03711_top_k_by_skip_index_negative.reference |
Expected output for negative test cases |
tests/queries/0_stateless/03711_top_k_by_skip_index_handle_ties.sql |
Test cases for multi-column ORDER BY and granularity > 1 |
tests/queries/0_stateless/03711_top_k_by_skip_index_handle_ties.reference |
Expected output for tie-handling test cases |
| if (store_map) | ||
| granules_map.emplace(granule_num, granules.size() - 1); | ||
| /// If index granularity is not 1, we insert the same value as the min | ||
| /// or max for all the corresponding granules. For our top-K purpose,this |
There was a problem hiding this comment.
Missing space after comma in 'purpose,this'.
| /// or max for all the corresponding granules. For our top-K purpose,this | |
| /// or max for all the corresponding granules. For our top-K purpose, this |
| else | ||
| { | ||
| LOG_TRACE(getLogger("optimizeTopK"), "Could not resolve column alias {}", sort_column_name); | ||
| LOG_DEBUG(getLogger("optimizeTopK"), "Could not resolve column alias {} {}", sort_column_name, column_node->type); |
There was a problem hiding this comment.
The error message includes column_node->type which may not be a string. This could produce unclear output if the type doesn't have a good string representation. Consider using a more descriptive format or converting the type to a readable string.
| LOG_DEBUG(getLogger("optimizeTopK"), "Could not resolve column alias {} {}", sort_column_name, column_node->type); | |
| LOG_DEBUG(getLogger("optimizeTopK"), "Could not resolve column alias {} {}", sort_column_name, static_cast<int>(column_node->type)); |
There was a problem hiding this comment.
Poco logger handles enum
| auto min_granules = n * index_granularity; | ||
| auto threshold = queue.top(); | ||
| for (size_t i = 0; i < min_granules && !queue.empty(); ++i) |
There was a problem hiding this comment.
The variable name min_granules is ambiguous. It represents the minimum number of granules to select before tie-handling, not a minimum granule identifier. Consider renaming to min_granules_to_select or target_granule_count for clarity.
| auto min_granules = n * index_granularity; | |
| auto threshold = queue.top(); | |
| for (size_t i = 0; i < min_granules && !queue.empty(); ++i) | |
| auto target_granule_count = n * index_granularity; | |
| auto threshold = queue.top(); | |
| for (size_t i = 0; i < target_granule_count && !queue.empty(); ++i) |
| auto min_granules = n * index_granularity; | ||
| auto threshold = queue.top(); | ||
| for (size_t i = 0; i < min_granules && !queue.empty(); ++i) |
There was a problem hiding this comment.
The variable name min_granules is ambiguous. It represents the minimum number of granules to select before tie-handling, not a minimum granule identifier. Consider renaming to min_granules_to_select or target_granule_count for clarity.
| auto min_granules = n * index_granularity; | |
| auto threshold = queue.top(); | |
| for (size_t i = 0; i < min_granules && !queue.empty(); ++i) | |
| auto min_granules_to_select = n * index_granularity; | |
| auto threshold = queue.top(); | |
| for (size_t i = 0; i < min_granules_to_select && !queue.empty(); ++i) |
|
Full green ci at |
LLVM Coverage Report
PR changed lines: PR changed-lines coverage: 72.22% (156/216) |
|
Failure in |
728da43
…i-column ORDER BY with dynamic filtering in top-K tests PR ClickHouse#93545 added support for multiple columns and index granularity > 1 in top-K threshold filtering, but the existing tests had two gaps: 1. `03711_top_k_by_skip_index_handle_ties`: only tested multi-column `ORDER BY` with `GRANULARITY 4`. Added single-column `ORDER BY v1` queries using the same table to cover the single-column + `GRANULARITY > 1` path. 2. `03711_top_k_by_dynamic_filter`: only tested single-column `ORDER BY` with `use_top_k_dynamic_filtering=1`. Added a second table `tab2` with tied `v1` values (10 distinct values) to cover multi-column `ORDER BY` with the dynamic filtering path. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Remove `skip_index->index.granularity != 1` guard: skip indexes with granularity > 1 are now supported (ref ClickHouse#93545). - Trim test tags to just `no-fasttest`; 10000 rows is fine for sanitizers. Made-with: Cursor
See #91645
Details
The support for index granularity > 1 : If the min-max index granule is
[50, 88]and a single index granule covers 8 table granules, then use[50, 88]as the min-max value for all the 8 table granules. When shortlisting table granules forLIMIT <n>, we need to shortlist the top8 x ntable granules and also dohandle_tiesprocessing (next point)To support
ORDER BY a,b- We will use the value ofafor threshold filtering. And the key change is how we compare to threshold - we need to send all rows withavalue>=the threshold (forORDER BY a DESC, b). Note the '=', that ensures that optimization is correct.Changelog category (leave one):
Changelog entry (a [user-readable short description](
ORDER BYclause. the first column's value is used to determine the threshold. 2)minmaxindex with index granularity > 1Note
Medium Risk
Changes core MergeTree top-K pruning logic and threshold comparisons, which can affect query correctness (ties) and performance (more granules selected) across ORDER BY/LIMIT queries.
Overview
Top-K
ORDER BY … LIMIToptimization is broadened and made tie-safe. The optimizer now allows multipleORDER BYcolumns (using the first column for pruning) and passesnum_sort_columnsthroughTopKFilterInfoso the executor can decide when it must handle ties.Minmax-based top-K selection now works with skip-index granularity > 1 by expanding index granules to underlying table marks and, when required, selecting
k * granularitymarks plus all marks tied at the threshold. Threshold filtering semantics were tightened to be inclusive (<=/>=) via__topKFilterand strictness changes inTopKThresholdTracker, and the optimization is disabled forLIMIT 0.Adds stateless tests covering multi-column ordering, coarser index granularity, and negative cases where the optimization must not apply.
Written by Cursor Bugbot for commit 1fcb0d9. This will update automatically on new commits. Configure here.