This is an automated email from the ASF dual-hosted git repository.
yiguolei pushed a commit to branch branch-4.1
in repository https://gitbox.apache.org/repos/asf/doris.git
The following commit(s) were added to refs/heads/branch-4.1 by this push:
new f084c0a34bf branch-4.1: [feature](runtime-filter) Add adaptive global
runtime filter tree publish (#65599)
f084c0a34bf is described below
commit f084c0a34bf7caef86ed6f4e616dc58bea137c01
Author: Pxl <[email protected]>
AuthorDate: Fri Jul 17 14:28:12 2026 +0800
branch-4.1: [feature](runtime-filter) Add adaptive global runtime filter
tree publish (#65599)
### What problem does this PR solve?
Issue Number: None
Related PR: #64851
Problem Summary: Backport #64851 to branch-4.1. Large global runtime
filters are currently published directly from the merge coordinator to
every scan target, causing repeated large RPC attachments. This backport
adds adaptive tree publish controlled by
`runtime_filter_tree_publish_max_send_bytes`.
The branch-4.1 adaptation keeps its `DummyBrpcCallback` and
query-context lifetime model, and falls back to direct publish for
legacy targets without fragment IDs. The `TQueryOptions` field keeps
master field id `225` for wire compatibility.
### Release note
Add session variable `runtime_filter_tree_publish_max_send_bytes` to
control adaptive global runtime filter tree publish. Set it to 0 to
disable tree publish.
### Check List (For Author)
- Test:
- Generated Thrift and protobuf sources with `./generated-source.sh
noclean`
- `./run-fe-ut.sh --run org.apache.doris.qe.SessionVariablesTest`
- 9 tests passed, 0 failures, 0 errors
- `runtime_filter_mgr.cpp` and `internal_service.cpp` passed
`-fsyntax-only` with the branch-4.1 release compile commands
- `clang-format --dry-run --Werror` on all changed C++ files
- `git diff --check upstream/branch-4.1..HEAD`
- Behavior changed: Yes. Large global runtime filters can be relayed
through an adaptive publish tree instead of direct coordinator fanout.
- Does this need documentation: No
Co-authored-by: Copilot <[email protected]>
---
be/src/exec/runtime_filter/runtime_filter_mgr.cpp | 254 +++++++++++++++++++--
be/src/exec/runtime_filter/runtime_filter_mgr.h | 31 ++-
be/src/service/internal_service.cpp | 23 +-
.../runtime_filter/runtime_filter_mgr_test.cpp | 110 +++++++++
.../java/org/apache/doris/qe/SessionVariable.java | 26 +++
gensrc/proto/internal_service.proto | 9 +-
gensrc/thrift/PaloInternalService.thrift | 2 +-
7 files changed, 426 insertions(+), 29 deletions(-)
diff --git a/be/src/exec/runtime_filter/runtime_filter_mgr.cpp
b/be/src/exec/runtime_filter/runtime_filter_mgr.cpp
index e52439d9c26..29b7aaa3e2f 100644
--- a/be/src/exec/runtime_filter/runtime_filter_mgr.cpp
+++ b/be/src/exec/runtime_filter/runtime_filter_mgr.cpp
@@ -25,6 +25,9 @@
#include <gen_cpp/internal_service.pb.h>
#include <gen_cpp/types.pb.h>
+#include <algorithm>
+#include <cstdlib>
+#include <cstring>
#include <mutex>
#include <ostream>
#include <string>
@@ -44,9 +47,190 @@
#include "runtime/thread_context.h"
#include "util/brpc_client_cache.h"
#include "util/brpc_closure.h"
+#include "util/uid_util.h"
namespace doris {
#include "common/compile_check_begin.h"
+
+namespace {
+
+bool can_tree_publish(const std::vector<TRuntimeFilterTargetParamsV2>&
targets) {
+ return std::all_of(targets.begin(), targets.end(), [](const auto& target) {
+ return target.__isset.target_fragment_ids &&
!target.target_fragment_ids.empty();
+ });
+}
+
+std::vector<RuntimeFilterPublishTarget> build_runtime_filter_publish_targets(
+ const std::vector<TRuntimeFilterTargetParamsV2>& targets) {
+ std::vector<RuntimeFilterPublishTarget> publish_targets;
+ publish_targets.reserve(targets.size());
+ for (const auto& target : targets) {
+ DORIS_CHECK(target.__isset.target_fragment_ids);
+ DORIS_CHECK(!target.target_fragment_ids.empty());
+ RuntimeFilterPublishTarget publish_target;
+
publish_target.addr.set_hostname(target.target_fragment_instance_addr.hostname);
+
publish_target.addr.set_port(target.target_fragment_instance_addr.port);
+ publish_target.fragment_ids = target.target_fragment_ids;
+ publish_targets.emplace_back(std::move(publish_target));
+ }
+ return publish_targets;
+}
+
+Status send_runtime_filter_relay_rpc(const RuntimeFilterPublishTask& task,
+ const butil::IOBuf& request_attachment,
int timeout_ms,
+ std::weak_ptr<QueryContext> query_ctx) {
+ std::shared_ptr<PBackendService_Stub> stub(
+
ExecEnv::GetInstance()->brpc_internal_client_cache()->get_client(task.receiver.addr));
+ if (stub == nullptr) {
+ LOG(WARNING) << "Failed to init runtime filter relay rpc to "
+ << task.receiver.addr.hostname() << ":" <<
task.receiver.addr.port();
+ return Status::InternalError("Failed to init runtime filter relay rpc
to {}:{}",
+ task.receiver.addr.hostname(),
task.receiver.addr.port());
+ }
+
+ auto closure =
+ AutoReleaseClosure<PPublishFilterRequestV2,
DummyBrpcCallback<PPublishFilterResponse>>::
+
create_unique(std::make_shared<PPublishFilterRequestV2>(task.request),
+
DummyBrpcCallback<PPublishFilterResponse>::create_shared(),
+ query_ctx);
+ if (!request_attachment.empty()) {
+ closure->cntl_->request_attachment().append(request_attachment);
+ }
+ closure->cntl_->set_timeout_ms(timeout_ms);
+ if (config::execution_ignore_eovercrowded) {
+ closure->cntl_->ignore_eovercrowded();
+ }
+ stub->apply_filterv2(closure->cntl_.get(), closure->request_.get(),
closure->response_.get(),
+ closure.get());
+ closure.release();
+ return Status::OK();
+}
+
+void set_request_direct_publish_target(const TRuntimeFilterTargetParamsV2&
target,
+ PPublishFilterRequestV2* request) {
+ if (target.__isset.target_fragment_ids &&
!target.target_fragment_ids.empty()) {
+ for (const auto& target_fragment_id : target.target_fragment_ids) {
+ request->add_fragment_ids(target_fragment_id);
+ }
+ } else {
+ for (const auto& target_fragment_instance_id :
target.target_fragment_instance_ids) {
+ PUniqueId* id = request->add_fragment_instance_ids();
+ id->set_hi(target_fragment_instance_id.hi);
+ id->set_lo(target_fragment_instance_id.lo);
+ }
+ }
+}
+
+} // namespace
+
+std::vector<std::vector<RuntimeFilterPublishTarget>>
split_runtime_filter_publish_targets(
+ const std::vector<RuntimeFilterPublishTarget>& targets, int fanout) {
+ DORIS_CHECK(!targets.empty());
+ DORIS_CHECK(fanout > 0);
+ size_t slice_count = std::min(targets.size(), static_cast<size_t>(fanout));
+ std::vector<std::vector<RuntimeFilterPublishTarget>> slices;
+ slices.reserve(slice_count);
+ for (size_t offset = 0; offset < targets.size();) {
+ size_t remaining_targets = targets.size() - offset;
+ size_t remaining_slices = slice_count - slices.size();
+ size_t slice_size = (remaining_targets + remaining_slices - 1) /
remaining_slices;
+ slices.emplace_back(targets.begin() + offset, targets.begin() + offset
+ slice_size);
+ offset += slice_size;
+ }
+ return slices;
+}
+
+std::vector<RuntimeFilterPublishTask> build_runtime_filter_publish_tasks(
+ const PPublishFilterRequestV2& base_request,
+ const std::vector<RuntimeFilterPublishTarget>& targets, int fanout) {
+ std::vector<RuntimeFilterPublishTask> tasks;
+ auto slices = split_runtime_filter_publish_targets(targets, fanout);
+ PPublishFilterRequestV2 request_template = base_request;
+ request_template.clear_fragment_ids();
+ request_template.clear_fragment_instance_ids();
+ request_template.clear_forward_targets();
+ tasks.reserve(slices.size());
+ for (const auto& slice : slices) {
+ DORIS_CHECK(!slice.empty());
+ RuntimeFilterPublishTask task;
+ task.receiver = slice.front();
+ task.request = request_template;
+
+ for (int32_t fragment_id : task.receiver.fragment_ids) {
+ task.request.add_fragment_ids(fragment_id);
+ }
+ for (size_t i = 1; i < slice.size(); ++i) {
+ DORIS_CHECK(!slice[i].fragment_ids.empty());
+ PPublishFilterForwardTarget* forward_target =
task.request.add_forward_targets();
+ forward_target->mutable_target_addr()->CopyFrom(slice[i].addr);
+ for (int32_t fragment_id : slice[i].fragment_ids) {
+ forward_target->add_fragment_ids(fragment_id);
+ }
+ }
+ tasks.emplace_back(std::move(task));
+ }
+ return tasks;
+}
+
+int calculate_tree_publish_fanout(int64_t serialized_filter_size, size_t
target_count,
+ int64_t max_send_bytes) {
+ DORIS_CHECK(serialized_filter_size > 0);
+ DORIS_CHECK(max_send_bytes >= 0);
+ if (max_send_bytes == 0 || target_count <= 1) {
+ return 0;
+ }
+
+ const int64_t direct_target_limit = max_send_bytes /
serialized_filter_size;
+ if (target_count <= static_cast<size_t>(direct_target_limit)) {
+ return 0;
+ }
+
+ return static_cast<int>(std::max<int64_t>(1, direct_target_limit));
+}
+
+Status forward_runtime_filter(const PPublishFilterRequestV2& request,
+ const butil::IOBuf& request_attachment,
+ std::weak_ptr<QueryContext> query_ctx) {
+ if (request.forward_targets().empty()) {
+ return Status::OK();
+ }
+
+ std::vector<RuntimeFilterPublishTarget> targets;
+ targets.reserve(request.forward_targets_size());
+ for (const auto& forward_target : request.forward_targets()) {
+ DORIS_CHECK(forward_target.has_target_addr());
+ DORIS_CHECK(forward_target.fragment_ids_size() > 0);
+ RuntimeFilterPublishTarget target;
+ target.addr.CopyFrom(forward_target.target_addr());
+ target.fragment_ids.assign(forward_target.fragment_ids().begin(),
+ forward_target.fragment_ids().end());
+ targets.emplace_back(std::move(target));
+ }
+
+ DORIS_CHECK(request.has_tree_publish_fanout());
+ int fanout = request.tree_publish_fanout();
+ DORIS_CHECK(fanout > 0);
+ DORIS_CHECK(request.has_publish_rpc_timeout_ms());
+ int timeout_ms = request.publish_rpc_timeout_ms();
+ auto tasks = build_runtime_filter_publish_tasks(request, targets, fanout);
+ VLOG_NOTICE << "Runtime filter relay publish filter_id=" <<
request.filter_id()
+ << ", forward_targets=" << targets.size() << ",
child_rpc_count=" << tasks.size()
+ << ", fanout=" << fanout;
+ auto st = Status::OK();
+ for (const auto& task : tasks) {
+ Status rpc_st =
+ send_runtime_filter_relay_rpc(task, request_attachment,
timeout_ms, query_ctx);
+ if (!rpc_st.ok()) {
+ LOG(WARNING) << "Failed to forward runtime filter, query_id="
+ << print_id(request.query_id()) << ", filter_id=" <<
request.filter_id()
+ << ", target=" << task.receiver.addr.hostname() << ":"
+ << task.receiver.addr.port() << ", status=" << rpc_st;
+ st = std::move(rpc_st);
+ }
+ }
+ return st;
+}
+
RuntimeFilterMgr::RuntimeFilterMgr(const bool is_global)
: _is_global(is_global),
_tracker(std::make_unique<MemTracker>(
@@ -406,17 +590,20 @@ Status
RuntimeFilterMergeControllerEntity::merge(std::shared_ptr<QueryContext> q
query_ctx->ignore_runtime_filter_error()
? std::weak_ptr<QueryContext> {}
: query_ctx,
- merge_time, request->query_id(),
query_ctx->execution_timeout());
+ merge_time, request->query_id(),
query_ctx->execution_timeout(),
+ query_ctx->query_options());
}
return Status::OK();
}
-Status
RuntimeFilterMergeControllerEntity::_send_rf_to_target(GlobalMergeContext&
cnt_val,
-
std::weak_ptr<QueryContext> ctx,
- int64_t
merge_time,
- PUniqueId
query_id,
- int
execution_timeout) {
- DCHECK_GT(cnt_val.targetv2_info.size(), 0);
+Status RuntimeFilterMergeControllerEntity::_send_rf_to_target(
+ GlobalMergeContext& cnt_val, std::weak_ptr<QueryContext> ctx, int64_t
merge_time,
+ PUniqueId query_id, int execution_timeout, const TQueryOptions&
query_options) {
+ if (cnt_val.targetv2_info.empty()) {
+ return Status::InternalError(
+ "_send_rf_to_target called with empty targetv2_info, filter:
{}",
+ cnt_val.merger ? cnt_val.merger->debug_string() : "unknown");
+ }
if (cnt_val.done) {
return Status::InternalError("Runtime filter has been sent",
@@ -446,6 +633,41 @@ Status
RuntimeFilterMergeControllerEntity::_send_rf_to_target(GlobalMergeContext
}
std::vector<TRuntimeFilterTargetParamsV2>& targets = cnt_val.targetv2_info;
+ int timeout_ms = get_execution_rpc_timeout_ms(execution_timeout);
+ apply_request.set_merge_time(merge_time);
+ *apply_request.mutable_query_id() = query_id;
+ const int64_t serialized_filter_size =
+ static_cast<int64_t>(apply_request.ByteSizeLong()) + std::max(0,
len);
+ int fanout = 0;
+ if (can_tree_publish(targets) &&
+ query_options.__isset.runtime_filter_tree_publish_max_send_bytes) {
+ int64_t max_send_bytes =
query_options.runtime_filter_tree_publish_max_send_bytes;
+ DORIS_CHECK(max_send_bytes >= 0);
+ fanout = calculate_tree_publish_fanout(serialized_filter_size,
targets.size(),
+ max_send_bytes);
+ }
+ const bool use_tree_publish = fanout > 0 && targets.size() >
static_cast<size_t>(fanout);
+
+ if (use_tree_publish) {
+ apply_request.set_tree_publish_fanout(fanout);
+ apply_request.set_publish_rpc_timeout_ms(timeout_ms);
+ auto publish_targets = build_runtime_filter_publish_targets(targets);
+ auto tasks = build_runtime_filter_publish_tasks(apply_request,
publish_targets, fanout);
+ VLOG_NOTICE << "Runtime filter tree publish filter_id=" <<
apply_request.filter_id()
+ << ", serialized_bytes=" << serialized_filter_size
+ << ", target_count=" << targets.size() << ",
child_rpc_count=" << tasks.size()
+ << ", fanout=" << fanout;
+ auto st = Status::OK();
+ for (const auto& task : tasks) {
+ Status rpc_st =
+ send_runtime_filter_relay_rpc(task, request_attachment,
timeout_ms, ctx);
+ if (!rpc_st.ok()) {
+ st = std::move(rpc_st);
+ }
+ }
+ return st;
+ }
+
auto st = Status::OK();
for (auto& target : targets) {
auto closure = AutoReleaseClosure<PPublishFilterRequestV2,
@@ -453,30 +675,16 @@ Status
RuntimeFilterMergeControllerEntity::_send_rf_to_target(GlobalMergeContext
create_unique(std::make_shared<PPublishFilterRequestV2>(apply_request),
DummyBrpcCallback<PPublishFilterResponse>::create_shared(), ctx);
- closure->request_->set_merge_time(merge_time);
- *closure->request_->mutable_query_id() = query_id;
if (has_attachment) {
closure->cntl_->request_attachment().append(request_attachment);
}
-
closure->cntl_->set_timeout_ms(get_execution_rpc_timeout_ms(execution_timeout));
+ closure->cntl_->set_timeout_ms(timeout_ms);
if (config::execution_ignore_eovercrowded) {
closure->cntl_->ignore_eovercrowded();
}
- // set fragment-id
- if (target.__isset.target_fragment_ids) {
- for (auto& target_fragment_id : target.target_fragment_ids) {
- closure->request_->add_fragment_ids(target_fragment_id);
- }
- } else {
- // FE not upgraded yet.
- for (auto& target_fragment_instance_id :
target.target_fragment_instance_ids) {
- PUniqueId* cur_id =
closure->request_->add_fragment_instance_ids();
- cur_id->set_hi(target_fragment_instance_id.hi);
- cur_id->set_lo(target_fragment_instance_id.lo);
- }
- }
+ set_request_direct_publish_target(target, closure->request_.get());
std::shared_ptr<PBackendService_Stub> stub(
ExecEnv::GetInstance()->brpc_internal_client_cache()->get_client(
diff --git a/be/src/exec/runtime_filter/runtime_filter_mgr.h
b/be/src/exec/runtime_filter/runtime_filter_mgr.h
index d680e339f61..94058c2c547 100644
--- a/be/src/exec/runtime_filter/runtime_filter_mgr.h
+++ b/be/src/exec/runtime_filter/runtime_filter_mgr.h
@@ -23,6 +23,7 @@
#include <gen_cpp/internal_service.pb.h>
#include <glog/logging.h>
+#include <cstddef>
#include <cstdint>
#include <map>
#include <memory>
@@ -35,8 +36,9 @@
#include "util/uid_util.h"
namespace butil {
+class IOBuf;
class IOBufAsZeroCopyInputStream;
-}
+} // namespace butil
namespace doris {
#include "common/compile_check_begin.h"
@@ -52,6 +54,30 @@ class RuntimeFilterWrapper;
class QueryContext;
class ExecEnv;
+struct RuntimeFilterPublishTarget {
+ PNetworkAddress addr;
+ std::vector<int32_t> fragment_ids;
+};
+
+struct RuntimeFilterPublishTask {
+ RuntimeFilterPublishTarget receiver;
+ PPublishFilterRequestV2 request;
+};
+
+std::vector<std::vector<RuntimeFilterPublishTarget>>
split_runtime_filter_publish_targets(
+ const std::vector<RuntimeFilterPublishTarget>& targets, int fanout);
+
+std::vector<RuntimeFilterPublishTask> build_runtime_filter_publish_tasks(
+ const PPublishFilterRequestV2& base_request,
+ const std::vector<RuntimeFilterPublishTarget>& targets, int fanout);
+
+int calculate_tree_publish_fanout(int64_t serialized_filter_size, size_t
target_count,
+ int64_t max_send_bytes);
+
+Status forward_runtime_filter(const PPublishFilterRequestV2& request,
+ const butil::IOBuf& request_attachment,
+ std::weak_ptr<QueryContext> query_ctx);
+
struct LocalMergeContext {
std::shared_ptr<RuntimeFilterMerger> merger;
std::vector<std::shared_ptr<RuntimeFilterProducer>> producers;
@@ -184,7 +210,8 @@ private:
const int producer_size);
Status _send_rf_to_target(GlobalMergeContext& cnt_val,
std::weak_ptr<QueryContext> ctx,
- int64_t merge_time, PUniqueId query_id, int
execution_timeout);
+ int64_t merge_time, PUniqueId query_id, int
execution_timeout,
+ const TQueryOptions& query_options);
// protect _filter_map
AnnotatedSharedMutex _filter_map_mutex;
diff --git a/be/src/service/internal_service.cpp
b/be/src/service/internal_service.cpp
index 050692f1488..47c84b56323 100644
--- a/be/src/service/internal_service.cpp
+++ b/be/src/service/internal_service.cpp
@@ -67,6 +67,7 @@
#include "exec/connector/vjdbc_connector.h"
#include "exec/exchange/vdata_stream_mgr.h"
#include "exec/rowid_fetcher.h"
+#include "exec/runtime_filter/runtime_filter_mgr.h"
#include "exec/sink/writer/varrow_flight_result_writer.h"
#include "exec/sink/writer/vmysql_result_writer.h"
#include "exprs/function/dictionary_factory.h"
@@ -95,6 +96,7 @@
#include "runtime/exec_env.h"
#include "runtime/fold_constant_executor.h"
#include "runtime/fragment_mgr.h"
+#include "runtime/query_context.h"
#include "runtime/result_block_buffer.h"
#include "runtime/result_buffer_mgr.h"
#include "runtime/runtime_profile.h"
@@ -1492,8 +1494,11 @@ void
PInternalService::apply_filterv2(::google::protobuf::RpcController* control
bool ret = _light_work_pool.try_offer([this, controller, request,
response, done]() {
signal::SignalTaskIdKeeper keeper(request->query_id());
brpc::ClosureGuard closure_guard(done);
- auto attachment =
static_cast<brpc::Controller*>(controller)->request_attachment();
- butil::IOBufAsZeroCopyInputStream zero_copy_input_stream(attachment);
+ const butil::IOBuf& request_attachment =
+
static_cast<brpc::Controller*>(controller)->request_attachment();
+ butil::IOBuf apply_attachment = request_attachment;
+ butil::IOBuf forward_attachment = request_attachment;
+ butil::IOBufAsZeroCopyInputStream
zero_copy_input_stream(apply_attachment);
VLOG_NOTICE << "rpc apply_filterv2 recv";
Status st;
try {
@@ -1504,6 +1509,20 @@ void
PInternalService::apply_filterv2(::google::protobuf::RpcController* control
if (!st.ok()) {
LOG(WARNING) << "apply filter meet error: " << st.to_string();
}
+ std::weak_ptr<QueryContext> forward_ctx;
+ if (auto query_ctx = _exec_env->fragment_mgr()->get_query_ctx(
+ UniqueId(request->query_id()).to_thrift())) {
+ if (!query_ctx->ignore_runtime_filter_error()) {
+ forward_ctx = query_ctx;
+ }
+ }
+ Status forward_st = forward_runtime_filter(*request,
forward_attachment, forward_ctx);
+ if (!forward_st.ok()) {
+ LOG(WARNING) << "forward runtime filter meet error: " <<
forward_st.to_string();
+ if (st.ok()) {
+ st = std::move(forward_st);
+ }
+ }
st.to_protobuf(response->mutable_status());
});
if (!ret) {
diff --git a/be/test/exec/runtime_filter/runtime_filter_mgr_test.cpp
b/be/test/exec/runtime_filter/runtime_filter_mgr_test.cpp
index 5a50e762c25..5f69f411cf1 100644
--- a/be/test/exec/runtime_filter/runtime_filter_mgr_test.cpp
+++ b/be/test/exec/runtime_filter/runtime_filter_mgr_test.cpp
@@ -20,12 +20,37 @@
#include <glog/logging.h>
#include <gtest/gtest.h>
+#include <string>
+
#include "exec/pipeline/thrift_builder.h"
#include "exec/runtime_filter/runtime_filter_producer.h"
#include "runtime/query_context.h"
#include "testutil/mock/mock_runtime_state.h"
namespace doris {
+namespace {
+
+RuntimeFilterPublishTarget make_publish_target(int index) {
+ RuntimeFilterPublishTarget target;
+ target.addr.set_hostname("host" + std::to_string(index));
+ target.addr.set_port(9000 + index);
+ target.fragment_ids.push_back(index);
+ return target;
+}
+
+std::vector<int32_t> flatten_fragment_ids(
+ const std::vector<std::vector<RuntimeFilterPublishTarget>>& slices) {
+ std::vector<int32_t> fragment_ids;
+ for (const auto& slice : slices) {
+ for (const auto& target : slice) {
+ fragment_ids.insert(fragment_ids.end(),
target.fragment_ids.begin(),
+ target.fragment_ids.end());
+ }
+ }
+ return fragment_ids;
+}
+
+} // namespace
class RuntimeFilterMgrTest : public testing::Test {
public:
@@ -186,4 +211,89 @@ TEST_F(RuntimeFilterMgrTest,
TestRuntimeFilterMergeControllerEntity) {
}
}
+TEST_F(RuntimeFilterMgrTest, SplitRuntimeFilterPublishTargets) {
+ std::vector<RuntimeFilterPublishTarget> targets;
+ for (int i = 0; i < 10; ++i) {
+ targets.push_back(make_publish_target(i));
+ }
+
+ auto slices = split_runtime_filter_publish_targets(targets, 3);
+ ASSERT_EQ(slices.size(), 3);
+ EXPECT_EQ(slices[0].size(), 4);
+ EXPECT_EQ(slices[1].size(), 3);
+ EXPECT_EQ(slices[2].size(), 3);
+ EXPECT_EQ(flatten_fragment_ids(slices), (std::vector<int32_t> {0, 1, 2, 3,
4, 5, 6, 7, 8, 9}));
+
+ slices = split_runtime_filter_publish_targets(targets, 20);
+ ASSERT_EQ(slices.size(), targets.size());
+ for (const auto& slice : slices) {
+ EXPECT_EQ(slice.size(), 1);
+ }
+
+ slices = split_runtime_filter_publish_targets(targets, 1);
+ ASSERT_EQ(slices.size(), 1);
+ EXPECT_EQ(slices[0].size(), targets.size());
+}
+
+TEST_F(RuntimeFilterMgrTest, CalculateTreePublishFanout) {
+ constexpr int64_t MB = 1024L * 1024L;
+ constexpr int64_t max_send_bytes = 256L * MB;
+ constexpr size_t target_count = 48;
+
+ EXPECT_EQ(calculate_tree_publish_fanout(1L * MB, target_count,
max_send_bytes), 0);
+ EXPECT_EQ(calculate_tree_publish_fanout(4L * MB, target_count,
max_send_bytes), 0);
+ EXPECT_EQ(calculate_tree_publish_fanout(8L * MB, target_count,
max_send_bytes), 32);
+ EXPECT_EQ(calculate_tree_publish_fanout(16L * MB, target_count,
max_send_bytes), 16);
+ EXPECT_EQ(calculate_tree_publish_fanout(32L * MB, target_count,
max_send_bytes), 8);
+ EXPECT_EQ(calculate_tree_publish_fanout(64L * MB, target_count,
max_send_bytes), 4);
+ EXPECT_EQ(calculate_tree_publish_fanout(128L * MB, target_count,
max_send_bytes), 2);
+
+ EXPECT_EQ(calculate_tree_publish_fanout(4L * MB, target_count, 0), 0);
+ EXPECT_EQ(calculate_tree_publish_fanout(256L * MB, target_count,
max_send_bytes), 1);
+ EXPECT_EQ(calculate_tree_publish_fanout(1L * MB, 1024, max_send_bytes),
256);
+}
+
+TEST_F(RuntimeFilterMgrTest, BuildRuntimeFilterPublishTasks) {
+ PPublishFilterRequestV2 base_request;
+ base_request.set_filter_id(10);
+ base_request.mutable_query_id()->set_hi(1);
+ base_request.mutable_query_id()->set_lo(2);
+ base_request.set_filter_type(PFilterType::BLOOM_FILTER);
+ base_request.set_tree_publish_fanout(2);
+ base_request.set_publish_rpc_timeout_ms(3000);
+ base_request.add_fragment_ids(999);
+ auto* stale_forward_target = base_request.add_forward_targets();
+ stale_forward_target->mutable_target_addr()->set_hostname("stale");
+ stale_forward_target->mutable_target_addr()->set_port(1);
+ stale_forward_target->add_fragment_ids(999);
+
+ std::vector<RuntimeFilterPublishTarget> targets;
+ for (int i = 0; i < 5; ++i) {
+ targets.push_back(make_publish_target(i));
+ }
+
+ auto tasks = build_runtime_filter_publish_tasks(base_request, targets, 2);
+ ASSERT_EQ(tasks.size(), 2);
+
+ EXPECT_EQ(tasks[0].receiver.addr.hostname(), "host0");
+ EXPECT_EQ(tasks[0].request.fragment_ids_size(), 1);
+ EXPECT_EQ(tasks[0].request.fragment_ids(0), 0);
+ ASSERT_EQ(tasks[0].request.forward_targets_size(), 2);
+ EXPECT_EQ(tasks[0].request.forward_targets(0).target_addr().hostname(),
"host1");
+ EXPECT_EQ(tasks[0].request.forward_targets(0).fragment_ids(0), 1);
+ EXPECT_EQ(tasks[0].request.forward_targets(1).target_addr().hostname(),
"host2");
+ EXPECT_EQ(tasks[0].request.forward_targets(1).fragment_ids(0), 2);
+
+ EXPECT_EQ(tasks[1].receiver.addr.hostname(), "host3");
+ EXPECT_EQ(tasks[1].request.fragment_ids_size(), 1);
+ EXPECT_EQ(tasks[1].request.fragment_ids(0), 3);
+ ASSERT_EQ(tasks[1].request.forward_targets_size(), 1);
+ EXPECT_EQ(tasks[1].request.forward_targets(0).target_addr().hostname(),
"host4");
+ EXPECT_EQ(tasks[1].request.forward_targets(0).fragment_ids(0), 4);
+
+ EXPECT_EQ(tasks[0].request.filter_id(), base_request.filter_id());
+ EXPECT_EQ(tasks[0].request.tree_publish_fanout(), 2);
+ EXPECT_EQ(tasks[0].request.publish_rpc_timeout_ms(), 3000);
+}
+
} // namespace doris
diff --git a/fe/fe-core/src/main/java/org/apache/doris/qe/SessionVariable.java
b/fe/fe-core/src/main/java/org/apache/doris/qe/SessionVariable.java
index 582b3d5b722..df5ac1ec75b 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/qe/SessionVariable.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/qe/SessionVariable.java
@@ -259,6 +259,8 @@ public class SessionVariable implements Serializable,
Writable {
"runtime_filter_broadcast_join_producer_num";
public static final String ENABLE_SYNC_RUNTIME_FILTER_SIZE =
"enable_sync_runtime_filter_size";
+ public static final String RUNTIME_FILTER_TREE_PUBLISH_MAX_SEND_BYTES =
+ "runtime_filter_tree_publish_max_send_bytes";
public static final String ENABLE_PARALLEL_RESULT_SINK =
"enable_parallel_result_sink";
@@ -1751,6 +1753,10 @@ public class SessionVariable implements Serializable,
Writable {
+ "The legacy Coordinator path keeps the existing
behavior."})
private int runtimeFilterBroadcastJoinProducerNum = 3;
+ @VariableMgr.VarAttr(name = RUNTIME_FILTER_TREE_PUBLISH_MAX_SEND_BYTES,
needForward = true, fuzzy = true,
+ checker = "checkRuntimeFilterTreePublishMaxSendBytes")
+ private long runtimeFilterTreePublishMaxSendBytes = 256L * 1024L * 1024L;
+
@VariableMgr.VarAttr(name = "runtime_filter_max_build_row_count",
needForward = true, fuzzy = false)
public long runtimeFilterMaxBuildRowCount = 64L * 1024L * 1024L;
@@ -3757,6 +3763,9 @@ public class SessionVariable implements Serializable,
Writable {
this.enableParallelScan = random.nextInt(2) == 0;
this.enableRuntimeFilterPrune = (randomInt % 10) == 0;
this.enableRuntimeFilterPartitionPrune = (randomInt % 2) == 0;
+ this.runtimeFilterTreePublishMaxSendBytes =
+ Util.getRandomLong(0, 64L * 1024L * 1024L, 128L * 1024L *
1024L,
+ 256L * 1024L * 1024L);
switch (randomInt) {
case 0:
@@ -4716,6 +4725,10 @@ public class SessionVariable implements Serializable,
Writable {
this.runtimeFilterBroadcastJoinProducerNum =
runtimeFilterBroadcastJoinProducerNum;
}
+ public long getRuntimeFilterTreePublishMaxSendBytes() {
+ return runtimeFilterTreePublishMaxSendBytes;
+ }
+
public void setEnableLocalShuffle(boolean enableLocalShuffle) {
this.enableLocalShuffle = enableLocalShuffle;
}
@@ -5513,6 +5526,7 @@ public class SessionVariable implements Serializable,
Writable {
tResult.setRuntimeBloomFilterMinSize(runtimeBloomFilterMinSize);
tResult.setRuntimeBloomFilterMaxSize(runtimeBloomFilterMaxSize);
tResult.setRuntimeFilterWaitInfinitely(runtimeFilterWaitInfinitely);
+
tResult.setRuntimeFilterTreePublishMaxSendBytes(runtimeFilterTreePublishMaxSendBytes);
tResult.setEnableFuzzyBlockableTask(enableFuzzyBlockableTask);
if (cpuResourceLimit > 0) {
@@ -6465,6 +6479,18 @@ public class SessionVariable implements Serializable,
Writable {
}
}
+ public void checkRuntimeFilterTreePublishMaxSendBytes(String maxSendBytes)
{
+ long value = Long.valueOf(maxSendBytes);
+ if (value < 0) {
+ UnsupportedOperationException exception =
+ new UnsupportedOperationException(
+ "runtime_filter_tree_publish_max_send_bytes can
not be set to "
+ + maxSendBytes + ", it must be greater or
equal to 0");
+ LOG.warn("Check runtime_filter_tree_publish_max_send_bytes
failed", exception);
+ throw exception;
+ }
+ }
+
public void checkSqlConvertorFeatures(String features) {
if (Strings.isNullOrEmpty(features)) {
return;
diff --git a/gensrc/proto/internal_service.proto
b/gensrc/proto/internal_service.proto
index 223e7afb07c..65f385a228b 100644
--- a/gensrc/proto/internal_service.proto
+++ b/gensrc/proto/internal_service.proto
@@ -680,8 +680,16 @@ message PPublishFilterRequestV2 {
optional uint64 local_merge_time = 13;
optional bool disabled = 14;
optional uint32 stage = 15;
+ repeated PPublishFilterForwardTarget forward_targets = 16;
+ optional int32 tree_publish_fanout = 17;
+ optional int32 publish_rpc_timeout_ms = 18;
};
+message PPublishFilterForwardTarget {
+ optional PNetworkAddress target_addr = 1;
+ repeated int32 fragment_ids = 2;
+}
+
message PPublishFilterResponse {
required PStatus status = 1;
};
@@ -1251,4 +1259,3 @@ service PBackendService {
rpc fetch_peer_data(PFetchPeerDataRequest) returns
(PFetchPeerDataResponse);
rpc request_cdc_client(PRequestCdcClientRequest) returns
(PRequestCdcClientResult);
};
-
diff --git a/gensrc/thrift/PaloInternalService.thrift
b/gensrc/thrift/PaloInternalService.thrift
index bffd2b546c9..6add308255d 100644
--- a/gensrc/thrift/PaloInternalService.thrift
+++ b/gensrc/thrift/PaloInternalService.thrift
@@ -496,10 +496,10 @@ struct TQueryOptions {
219: optional i64 ann_index_candidate_rows_threshold = 0
// Candidate row ratio threshold against segment rows. Existing default is
0.3.
220: optional double ann_index_candidate_rows_percent_threshold = 0.3
-
// To control whether BE scan readers may apply expression-based ZoneMap
pruning.
224: optional bool enable_expr_zonemap_filter = true
+ 225: optional i64 runtime_filter_tree_publish_max_send_bytes = 268435456
// For cloud, to control if the content would be written into file cache
// In write path, to control if the content would be written into file cache.
// In read path, read from file cache or remote storage when execute query.
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]