Skip to content

[feature](runtime filter) Support single-column runtime filter bucket pruning#65837

Open
HappenLee wants to merge 1 commit into
apache:masterfrom
HappenLee:feature/runtime-filter-bucket-pruning
Open

[feature](runtime filter) Support single-column runtime filter bucket pruning#65837
HappenLee wants to merge 1 commit into
apache:masterfrom
HappenLee:feature/runtime-filter-bucket-pruning

Conversation

@HappenLee

Copy link
Copy Markdown
Contributor

What problem does this PR solve?

Issue Number: None

Related PR: None

Problem Summary: Runtime filters could prune table partitions but still created and scheduled scanners for every hash-distributed bucket. Add FE eligibility metadata for direct targets on a single HASH distribution column and use exact IN values in BE to compute Doris CRC bucket indexes. Initial and late filters skip nonmatching tablet scanners. Composite distribution and non-invertible Bloom filters conservatively fall back.

Release note

Support runtime-filter bucket pruning for exact filters on single-column HASH-distributed OLAP scans. It can be disabled with enable_runtime_filter_bucket_prune.

Check List (For Author)

  • Test: Unit Test and Regression test
    • FE unit test: RuntimeFilterBucketPruneClassifierTest (6 tests)
    • BE unit test: RuntimeFilterBucketPrunerTest (5 tests)
    • Regression test: query_p0/runtime_filter/rf_bucket_pruning
    • Build: ./build.sh --fe and ./build.sh --be -j 48
    • Static analysis: run-clang-tidy.sh on modified C++ translation units
  • Behavior changed: Yes. Eligible runtime filters skip nonmatching hash buckets.
  • Does this need documentation: No

… pruning

### What problem does this PR solve?

Issue Number: None

Related PR: None

Problem Summary: Runtime filters could prune table partitions but still created and scheduled scanners for every hash-distributed bucket. Add FE eligibility metadata for direct targets on a single HASH distribution column and use exact IN values in BE to compute Doris CRC bucket indexes. Initial and late filters skip nonmatching tablet scanners. Composite distribution and non-invertible Bloom filters conservatively fall back.

### Release note

Support runtime-filter bucket pruning for exact filters on single-column HASH-distributed OLAP scans. It can be disabled with enable_runtime_filter_bucket_prune.

### Check List (For Author)

- Test: Unit Test and Regression test
    - FE unit test: RuntimeFilterBucketPruneClassifierTest (6 tests)
    - BE unit test: RuntimeFilterBucketPrunerTest (5 tests)
    - Regression test: query_p0/runtime_filter/rf_bucket_pruning
    - Build: ./build.sh --fe and ./build.sh --be -j 48
    - Static analysis: run-clang-tidy.sh on modified C++ translation units
- Behavior changed: Yes. Eligible runtime filters skip nonmatching hash buckets.
- Does this need documentation: No
@hello-stephen

Copy link
Copy Markdown
Contributor

Thank you for your contribution to Apache Doris.
Don't know what should be done next? See How to process your PR.

Please clearly describe your PR:

  1. What problem was fixed (it's best to include specific error reporting information). How it was fixed.
  2. Which behaviors were modified. What was the previous behavior, what is it now, why was it modified, and what possible impacts might there be.
  3. What features were added. Why was this function added?
  4. Which code was refactored and why was this part of the code refactored?
  5. Which functions were optimized and what is the difference before and after the optimization?

@HappenLee

Copy link
Copy Markdown
Contributor Author

/review

@github-actions

Copy link
Copy Markdown
Contributor

Codex automated review failed and did not complete.

Error: You've hit your usage limit. Visit https://chatgpt.com/codex/settings/usage to purchase more credits or try again at Jul 25th, 2026 6:03 AM.
Workflow run: https://github.com/apache/doris/actions/runs/29809112468

Please inspect the workflow logs and rerun the review after the underlying issue is resolved.

@morrySnow morrySnow left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for this PR! The implementation is well-structured and integrates cleanly with the existing runtime-filter infrastructure. Below is a summary of my overall findings; detailed inline comments follow.

Overview: This PR adds the ability for exact IN runtime filters on single-column HASH-distributed OLAP tables to skip scanning tablets whose bucket index doesn't match any filter value. The FE classifier determines eligibility, and the BE pruner materializes filter values into hashes and computes which buckets to skip. The feature is gated behind enable_runtime_filter_bucket_prune (default true).

Strengths:

  • Clean separation between FE eligibility classification and BE pruning logic
  • Good test coverage (FE unit tests, BE unit tests, regression test)
  • Proper concurrency handling with shared_mutex
  • Conservatively falls back when prerequisites aren't met (Bloom, composite hash, etc.)
  • Efficient: caches selected buckets by bucket_num to avoid recomputation

Key issues to address:

  1. Asymmetric column comparison in sameColumn() — uses different name methods for target vs distribution columns, which could miss pruning opportunities
  2. NULL handling in materialize_hashes — inserting a default value for NULL adds a spurious hash that prevents optimal pruning
  3. A few minor style/convention items noted inline

@morrySnow morrySnow left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Inline comments on specific code locations (see the overall review for summary).

&& targetUniqueId == distributionUniqueId) {
return true;
}
return targetColumn.tryGetBaseColumnName().equalsIgnoreCase(distributionColumn.getName());

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Issue: Asymmetric column-name comparison may miss pruning opportunities.

The comparison uses targetColumn.tryGetBaseColumnName() on the target side but distributionColumn.getName() on the distribution side. These methods can return different representations of the same column name:

  • tryGetBaseColumnName() may strip expression-manipulation prefixes/suffixes.
  • getName() returns the raw column name from the catalog definition.

If the target column's name was rewritten (e.g., with a qualifier prefix) while the distribution column's raw name doesn't match the stripped version, this comparison could incorrectly return false, disabling bucket pruning for an eligible scan.

Suggestion: Use the same name-retrieval method on both sides, or compare by getUniqueId() directly as the primary check, falling back to name comparison only when unique IDs are unset.

if (is_string_type(primitive_type)) {
const auto* string_value = reinterpret_cast<const StringRef*>(value);
column->insert_data(string_value->data, string_value->size);
} else {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: insert_data(value, 0) relies on an implicit convention.

For non-string fixed-length types (INT, BIGINT, etc.), the second argument 0 is passed to insert_data(). ColumnVector<T>::insert_data ignores the length parameter (marked /*length*/), reading exactly sizeof(T) bytes via unaligned_load. This is correct but the convention is not obvious to a reader.

Suggestion: Add a brief comment:

// length=0 is unused for fixed-length types; ColumnVector::insert_data ignores it.
column->insert_data(reinterpret_cast<const char*>(value), 0);

}
iter->next();
}
if (hybrid_set->contain_null() && data_type->is_nullable()) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Issue: Inserting a default value for NULL adds a spurious hash that reduces pruning effectiveness.

When hybrid_set->contain_null() is true, column->insert_default() adds a default value whose hash is computed and included in the selected-bucket set. Since NULL IN (...) evaluates to NULL (unknown), not true, no bucket should be kept open solely for the NULL element in the IN list. Adding the default value's hash may keep an extra bucket open that could otherwise be pruned.

While this is safe (it is conservative — it only fails to prune a prunable bucket, never incorrectly prunes a needed bucket), it represents a missed pruning opportunity.

Suggestion: Either:

  1. Skip the insert_default() call entirely — NULL does not match any row, so its hash should not prevent any bucket from being pruned.
  2. Or add a comment explaining that including the NULL hash is intentionally conservative and why.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants