github-actions[bot] commented on code in PR #65837:
URL: https://github.com/apache/doris/pull/65837#discussion_r3776846781


##########
be/src/exec/operator/olap_scan_operator.cpp:
##########
@@ -827,6 +832,8 @@ Status 
OlapScanLocalState::_init_scanners(std::list<ScannerSPtr>* scanners) {
                                   p._olap_scan_node.is_preaggregation,
                                   read_row_binlog,
                                   resolve_binlog_scan_type(palo_scan_range),
+                                  palo_scan_range.bucket_seq,

Review Comment:
   [P2] Propagate bucket identity through `ParallelScannerBuilder` too. The 
ordinary factory forwards `bucket_seq`/`bucket_num` here, but the 
default-enabled parallel branch above hands the builder only tablets/read 
sources, and `_build_scanner()` leaves the new `OlapScanner::Params` fields at 
`(0, 0)`. A late RF updates the pruner under the real bucket count (for example 
8), while every split scanner then asks `is_bucket_pruned(0, 0)`, which can 
never match; ready-at-open filters still compact before builder construction, 
so the current tests mask this production path. Please carry each tablet's 
bucket metadata into every row-range/segment split and add a late-arrival test 
through the real parallel scanner factory.



##########
fe/fe-core/src/main/java/org/apache/doris/qe/runtime/ThriftPlansBuilder.java:
##########
@@ -117,6 +118,8 @@ public static Map<DistributedPlanWorker, 
TPipelineFragmentParamsList> plansToThr
 
         // we should set runtime predicate first, then we can use heap sort 
and to thrift
         setRuntimePredicateIfNeed(coordinatorContext.scanNodes);
+        setRuntimeFilterBucketPruneParametersIfNeeded(

Review Comment:
   [P2] Attach this metadata for the legacy Coordinator path too. With 
`enable_nereids_distribute_planner=false`, Nereids still creates the eligible 
runtime-filter descriptor, but `EnvFactory` selects `Coordinator`; this helper 
is only called from `ThriftPlansBuilder`, and the legacy serializer never calls 
`OlapScanNode.setRuntimeFilterBucketPruneParameters()`. It therefore sends 
every range without `bucket_seq`/`bucket_num`, so BE records 
`_has_rf_bucket_prune_metadata=false` and silently disables this feature even 
though `enable_runtime_filter_bucket_prune` is true. Please move the one-time 
annotation to a serializer-neutral point (or invoke it before legacy 
assignment/serialization) and add coverage with the distributed planner 
disabled that proves both fields arrive and the pruning counter is positive.



##########
be/src/exec/scan/scanner_scheduler.cpp:
##########
@@ -192,6 +199,15 @@ void 
ScannerScheduler::_scanner_scan(std::shared_ptr<ScannerContext> ctx,
                 }
             }
 
+            // A filter may become ready while prepare() is doing tablet 
setup. Apply it before
+            // open() so a newly pruned OLAP scanner never initializes its 
reader or eagerly reads.
+            if (!eos && !scanner->is_open()) {
+                append_late_arrival_runtime_filter();
+                if (scanner->is_pruned_by_runtime_filter()) {

Review Comment:
   [P2] Release prepared reader inputs when this check prunes the scanner. 
`OlapScanner::prepare()` has already created its `BlockReader`, cloned 
contexts, and retained `ReaderParams.rs_splits`/delete metadata; the normal 
prompt release is in `_open_impl()` after reader initialization, which this new 
EOS path intentionally skips. `OlapScanner::close()` does not clear that state, 
and `ScanLocalState::_scanners` owns every delegate until operator close, so 
many eliminated tablets can keep their rowset readers pinned for the remainder 
of a long scan. Please add an abandon/cleanup path for prepared-but-unopened 
scanners and extend the lifecycle test to assert both no reader init and no 
retained read-source splits.



##########
be/src/exec/runtime_filter/runtime_filter_bucket_pruner.cpp:
##########
@@ -0,0 +1,148 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+#include "exec/runtime_filter/runtime_filter_bucket_pruner.h"
+
+#include <gen_cpp/PlanNodes_types.h>
+
+#include <algorithm>
+#include <memory>
+#include <mutex>
+
+#include "exprs/hybrid_set.h"
+#include "exprs/runtime_filter_expr.h"
+#include "exprs/vexpr.h"
+#include "exprs/vexpr_context.h"
+#include "exprs/vslot_ref.h"
+
+namespace doris {
+
+Status RuntimeFilterBucketPruner::prune_by_runtime_filters(
+        const std::vector<std::unique_ptr<TPaloScanRange>>& ranges,
+        const VExprContextSPtrs& conjuncts, const 
std::vector<TRuntimeFilterDesc>& rf_descs,
+        int scan_node_id, int max_in_num, int64_t* newly_pruned_count) {
+    *newly_pruned_count = 0;
+    if (ranges.empty()) {
+        return Status::OK();
+    }
+
+    phmap::flat_hash_set<int> eligible_filter_ids;
+    for (const auto& desc : rf_descs) {
+        if (desc.__isset.bucket_pruning_target_ids &&
+            desc.bucket_pruning_target_ids.contains(scan_node_id)) {
+            eligible_filter_ids.insert(desc.filter_id);
+        }
+    }
+    if (eligible_filter_ids.empty()) {
+        return Status::OK();
+    }
+
+    for (const auto& conjunct_ctx : conjuncts) {
+        VExprSPtr root = conjunct_ctx->root();
+        if (!root->is_rf_wrapper()) {
+            continue;
+        }
+        auto* rf_expr = assert_cast<RuntimeFilterExpr*>(root.get());
+        if (!eligible_filter_ids.contains(rf_expr->filter_id())) {
+            continue;
+        }
+
+        VExprSPtr impl = root->get_impl();
+        DORIS_CHECK(impl != nullptr);
+        std::shared_ptr<HybridSetBase> hybrid_set = impl->get_set_func();
+        if (hybrid_set == nullptr) {
+            // IN_OR_BLOOM may become a Bloom filter at runtime. A Bloom filter
+            // cannot be inverted to a safe finite bucket set.
+            continue;
+        }
+        if (hybrid_set->size() > max_in_num) {
+            continue;
+        }
+
+        DORIS_CHECK_EQ(impl->children().size(), 1);
+        VExprSPtr target_expr = impl->children()[0];
+        DORIS_CHECK_EQ(target_expr->node_type(), TExprNodeType::SLOT_REF);
+
+        std::shared_ptr<const std::vector<uint32_t>> hashes =
+                rf_expr->get_bucket_prune_hashes(target_expr->data_type());
+        phmap::flat_hash_map<int32_t, phmap::flat_hash_set<int32_t>> 
new_selected_buckets_by_num;
+        for (const auto& range_ptr : ranges) {
+            DORIS_CHECK(range_ptr != nullptr);
+            const auto& range = *range_ptr;
+            DORIS_CHECK(range.__isset.bucket_seq);
+            DORIS_CHECK(range.__isset.bucket_num);
+            DORIS_CHECK_GT(range.bucket_num, 0);
+            DORIS_CHECK_GE(range.bucket_seq, 0);
+            DORIS_CHECK_LT(range.bucket_seq, range.bucket_num);
+
+            auto [selected_it, inserted] =
+                    new_selected_buckets_by_num.try_emplace(range.bucket_num);
+            if (inserted) {
+                auto& selected_buckets = selected_it->second;
+                selected_buckets.reserve(
+                        std::min(hashes->size(), 
static_cast<size_t>(range.bucket_num)));
+                for (uint32_t hash : *hashes) {
+                    selected_buckets.insert(
+                            static_cast<int32_t>(hash % 
static_cast<uint32_t>(range.bucket_num)));
+                }
+            }
+        }
+
+        std::unique_lock lock(_prune_mutex);
+        for (const auto& range_ptr : ranges) {
+            const auto& range = *range_ptr;
+            auto current_it = _selected_buckets_by_num.find(range.bucket_num);
+            bool was_selected = current_it == _selected_buckets_by_num.end() ||
+                                current_it->second.contains(range.bucket_seq);
+            if (was_selected &&
+                
!new_selected_buckets_by_num.at(range.bucket_num).contains(range.bucket_seq)) {
+                ++*newly_pruned_count;
+            }
+        }
+        for (auto& [bucket_num, new_selected_buckets] : 
new_selected_buckets_by_num) {
+            auto current_it = _selected_buckets_by_num.find(bucket_num);
+            if (current_it == _selected_buckets_by_num.end()) {
+                _selected_buckets_by_num.emplace(bucket_num, 
std::move(new_selected_buckets));
+            } else {
+                for (auto bucket_it = current_it->second.begin();
+                     bucket_it != current_it->second.end();) {
+                    if (!new_selected_buckets.contains(*bucket_it)) {
+                        bucket_it = current_it->second.erase(bucket_it);
+                    } else {
+                        ++bucket_it;
+                    }
+                }
+            }
+        }
+        _pruned_tablet_count += *newly_pruned_count;

Review Comment:
   [P2] Add only the current filter's pruning delta here. `newly_pruned_count` 
is initialized once before the outer conjunct loop, so when two eligible 
filters eliminate 3 ranges and then 1 more it progresses `3 -> 4`, while this 
line retains `3 + 4 = 7` even though only four ranges were removed. Initial 
acquisition and one late poll can both deliver multiple ready filters, and this 
retained value is exposed directly as `BucketsPrunedByRuntimeFilter`. Please 
keep a per-filter delta (or add the aggregate once after the loop) and cover 
two filters whose intersection removes an additional bucket.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to