This is an automated email from the ASF dual-hosted git repository.
HappenLee pushed a commit to branch opt_perf_4.1
in repository https://gitbox.apache.org/repos/asf/doris.git
The following commit(s) were added to refs/heads/opt_perf_4.1 by this push:
new 7261a1e48be [opt](scan) Boost scanner concurrency under starvation
7261a1e48be is described below
commit 7261a1e48beb0940bc5c9a016f02bda65fbfe895
Author: happenlee <[email protected]>
AuthorDate: Thu Jul 9 20:07:57 2026 +0800
[opt](scan) Boost scanner concurrency under starvation
---
be/src/exec/scan/scanner_context.cpp | 28 ++++++++++++++++++----
be/src/exec/scan/scanner_context.h | 3 ++-
be/src/exec/scan/scanner_scheduler.cpp | 2 +-
be/test/exec/scan/scanner_context_test.cpp | 25 +++++++++++++++++++
.../java/org/apache/doris/qe/SessionVariable.java | 4 ++--
5 files changed, 53 insertions(+), 9 deletions(-)
diff --git a/be/src/exec/scan/scanner_context.cpp
b/be/src/exec/scan/scanner_context.cpp
index f7376fd2946..3371af5bf8e 100644
--- a/be/src/exec/scan/scanner_context.cpp
+++ b/be/src/exec/scan/scanner_context.cpp
@@ -166,6 +166,7 @@ Status ScannerContext::init() {
}
}
}
+ _min_scan_concurrency = std::min(_min_scan_concurrency,
_max_scan_concurrency);
COUNTER_SET(_local_state->_max_scan_concurrency,
(int64_t)_max_scan_concurrency);
COUNTER_SET(_local_state->_min_scan_concurrency,
(int64_t)_min_scan_concurrency);
@@ -257,6 +258,7 @@ void
ScannerContext::push_back_scan_task(std::shared_ptr<ScanTask> scan_task) {
}
_tasks_queue.push_back(scan_task);
_num_scheduled_scanners--;
+ _scan_starving = false;
_dependency->set_ready();
}
@@ -316,9 +318,13 @@ Status ScannerContext::get_block_from_queue(RuntimeState*
state, Block* block, b
if (scan_task->is_eos()) {
// 1. if eos, record a finished scanner.
_num_finished_scanners++;
+ _scan_starving = _tasks_queue.empty() &&
+ _num_finished_scanners <
cast_set<int32_t>(_all_scanners.size()) &&
+ (_num_scheduled_scanners > 0 ||
!_pending_scanners.empty());
RETURN_IF_ERROR(
_scanner_scheduler->schedule_scan_task(shared_from_this(), nullptr, l));
} else {
+ _scan_starving = _tasks_queue.empty();
RETURN_IF_ERROR(
_scanner_scheduler->schedule_scan_task(shared_from_this(), scan_task, l));
}
@@ -333,7 +339,12 @@ Status ScannerContext::get_block_from_queue(RuntimeState*
state, Block* block, b
*eos = done();
if (_tasks_queue.empty()) {
+ _scan_starving = !done() &&
+ _num_finished_scanners <
cast_set<int32_t>(_all_scanners.size()) &&
+ (_num_scheduled_scanners > 0 ||
!_pending_scanners.empty());
_dependency->block();
+ } else {
+ _scan_starving = false;
}
return Status::OK();
@@ -369,6 +380,7 @@ void ScannerContext::stop_scanners(RuntimeState* state) {
return;
}
_should_stop = true;
+ _scan_starving = false;
_set_scanner_done();
for (const std::weak_ptr<ScannerDelegate>& scanner : _all_scanners) {
if (std::shared_ptr<ScannerDelegate> sc = scanner.lock()) {
@@ -451,8 +463,13 @@ void ScannerContext::update_peak_running_scanner(int num) {
int32_t ScannerContext::_get_margin(std::unique_lock<std::mutex>&
transfer_lock,
std::unique_lock<std::shared_mutex>&
scheduler_lock) {
- // margin_1 is used to ensure each scan operator could have at least
_min_scan_concurrency scan tasks.
- int32_t margin_1 = _min_scan_concurrency -
+ int32_t target_scan_concurrency = _min_scan_concurrency;
+ if (_scan_starving && _tasks_queue.empty()) {
+ target_scan_concurrency = _max_scan_concurrency;
+ }
+
+ // margin_1 is used to ensure each scan operator could have enough scan
tasks.
+ int32_t margin_1 = target_scan_concurrency -
(cast_set<int32_t>(_tasks_queue.size()) +
_num_scheduled_scanners);
// margin_2 is used to ensure the scan scheduler could have at least
_min_scan_concurrency_of_scan_scheduler scan tasks.
@@ -474,10 +491,11 @@ int32_t
ScannerContext::_get_margin(std::unique_lock<std::mutex>& transfer_lock,
VLOG_DEBUG << fmt::format(
"[{}|{}] schedule scan task, margin_1: {} = {} - ({} + {}),
margin_2: {} = {} - "
- "({} + {}), margin: {}",
- print_id(_query_id), ctx_id, margin_1, _min_scan_concurrency,
_tasks_queue.size(),
+ "({} + {}), margin: {}, scan starving: {}",
+ print_id(_query_id), ctx_id, margin_1, target_scan_concurrency,
_tasks_queue.size(),
_num_scheduled_scanners, margin_2,
_min_scan_concurrency_of_scan_scheduler,
- _scanner_scheduler->get_active_threads(),
_scanner_scheduler->get_queue_size(), margin);
+ _scanner_scheduler->get_active_threads(),
_scanner_scheduler->get_queue_size(), margin,
+ _scan_starving);
return margin;
}
diff --git a/be/src/exec/scan/scanner_context.h
b/be/src/exec/scan/scanner_context.h
index 553408ebc96..857dfb736e9 100644
--- a/be/src/exec/scan/scanner_context.h
+++ b/be/src/exec/scan/scanner_context.h
@@ -251,7 +251,8 @@ protected:
// Each scan operator can submit _max_scan_concurrency scanner to
scheduelr if scheduler has enough resource.
// So that for a single query, we can make sure it could make full
utilization of the resource.
int32_t _max_scan_concurrency = 0;
- MOCK_REMOVE(const) int32_t _min_scan_concurrency = 1;
+ int32_t _min_scan_concurrency = 1;
+ bool _scan_starving = false;
std::shared_ptr<ScanTask> _pull_next_scan_task(std::shared_ptr<ScanTask>
current_scan_task,
int32_t
current_concurrency);
diff --git a/be/src/exec/scan/scanner_scheduler.cpp
b/be/src/exec/scan/scanner_scheduler.cpp
index c116c354f10..a6d35f3a0b7 100644
--- a/be/src/exec/scan/scanner_scheduler.cpp
+++ b/be/src/exec/scan/scanner_scheduler.cpp
@@ -397,7 +397,7 @@ int ScannerScheduler::get_remote_scan_thread_queue_size() {
int ScannerScheduler::default_min_active_scan_threads() {
return config::min_active_scan_threads > 0
? config::min_active_scan_threads
- : config::min_active_scan_threads = CpuInfo::num_cores() *
2;
+ : config::min_active_scan_threads =
default_local_scan_thread_num();
}
int ScannerScheduler::default_min_active_file_scan_threads() {
diff --git a/be/test/exec/scan/scanner_context_test.cpp
b/be/test/exec/scan/scanner_context_test.cpp
index 35156865bb3..8b2097716de 100644
--- a/be/test/exec/scan/scanner_context_test.cpp
+++ b/be/test/exec/scan/scanner_context_test.cpp
@@ -423,6 +423,31 @@ TEST_F(ScannerContextTest, get_margin) {
scanner_context->_num_scheduled_scanners = 20;
margin = scanner_context->_get_margin(transfer_lock, scheduler_lock);
ASSERT_EQ(margin, 0);
+
+ // Downstream is waiting for scan data while scheduler is busy. The scan
operator should
+ // refill up to max scan concurrency instead of being limited by min scan
concurrency.
+ scheduler = std::make_unique<MockSimplifiedScanScheduler>(cgroup_cpu_ctl);
+ EXPECT_CALL(*scheduler,
get_active_threads()).WillOnce(testing::Return(50));
+ EXPECT_CALL(*scheduler, get_queue_size()).WillOnce(testing::Return(10));
+ scanner_context->_scanner_scheduler = scheduler.get();
+ scanner_context->_min_scan_concurrency_of_scan_scheduler = 20;
+ scanner_context->_num_scheduled_scanners = 0;
+ scanner_context->_min_scan_concurrency = 1;
+ scanner_context->_max_scan_concurrency = 8;
+ scanner_context->_scan_starving = true;
+ margin = scanner_context->_get_margin(transfer_lock, scheduler_lock);
+ ASSERT_EQ(margin, scanner_context->_max_scan_concurrency);
+
+ // If there is already a produced task waiting in the queue, downstream is
not scan-starved.
+ scheduler = std::make_unique<MockSimplifiedScanScheduler>(cgroup_cpu_ctl);
+ EXPECT_CALL(*scheduler,
get_active_threads()).WillOnce(testing::Return(50));
+ EXPECT_CALL(*scheduler, get_queue_size()).WillOnce(testing::Return(10));
+ scanner_context->_scanner_scheduler = scheduler.get();
+ scanner_context->_tasks_queue.push_back(
+
std::make_shared<ScanTask>(std::make_shared<ScannerDelegate>(scanner)));
+ margin = scanner_context->_get_margin(transfer_lock, scheduler_lock);
+ ASSERT_EQ(margin, 0);
+ scanner_context->_tasks_queue.clear();
}
TEST_F(ScannerContextTest, pull_next_scan_task) {
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 c930696743c..7ec1074bab1 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
@@ -1104,8 +1104,8 @@ public class SessionVariable implements Serializable,
Writable {
public long maxScanQueueMemByte = 2147483648L / 20;
@VariableMgr.VarAttr(name = MAX_SCANNERS_CONCURRENCY, needForward = true,
description = {
- "ScanNode 扫描数据的最大并发,默认为 4", "The max threads to read data of
ScanNode, default 4"})
- public int maxScannersConcurrency = 4;
+ "ScanNode 扫描数据的最大并发,默认为 8", "The max threads to read data of
ScanNode, default 8"})
+ public int maxScannersConcurrency = 8;
@VariableMgr.VarAttr(name = MAX_FILE_SCANNERS_CONCURRENCY, needForward =
true, description = {
"FileScanNode 扫描数据的最大并发,默认为 16", "The max threads to read data of
FileScanNode, default 16"})
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]