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 37b4b4c5a6c branch-4.1: [fix](be) Continue sorted merge when sender
queue is ready #65004 (#65106)
37b4b4c5a6c is described below
commit 37b4b4c5a6cd8e89e7d33d31568bde34f6414145
Author: github-actions[bot]
<41898282+github-actions[bot]@users.noreply.github.com>
AuthorDate: Thu Jul 2 08:20:12 2026 +0800
branch-4.1: [fix](be) Continue sorted merge when sender queue is ready
#65004 (#65106)
Cherry-picked from #65004
Co-authored-by: HappenLee <[email protected]>
---
be/src/exec/exchange/vdata_stream_recvr.cpp | 12 +-
be/src/exec/exchange/vdata_stream_recvr.h | 2 +
be/src/exec/sort/sort_cursor.h | 22 ++-
be/src/exec/sort/vsorted_run_merger.cpp | 34 ++++-
be/src/exec/sort/vsorted_run_merger.h | 8 +-
be/test/core/value/sort_merger_test.cpp | 201 +++++++++++++++++++++++++++-
6 files changed, 264 insertions(+), 15 deletions(-)
diff --git a/be/src/exec/exchange/vdata_stream_recvr.cpp
b/be/src/exec/exchange/vdata_stream_recvr.cpp
index d98b49a9e09..ebce52683cd 100644
--- a/be/src/exec/exchange/vdata_stream_recvr.cpp
+++ b/be/src/exec/exchange/vdata_stream_recvr.cpp
@@ -130,6 +130,11 @@ Status VDataStreamRecvr::SenderQueue::get_batch(Block*
block, bool* eos) {
return Status::OK();
}
+bool VDataStreamRecvr::SenderQueue::has_data_or_finished() {
+ std::lock_guard<std::mutex> l(_lock);
+ return _is_cancelled || !_block_queue.empty() || _num_remaining_senders ==
0;
+}
+
void
VDataStreamRecvr::SenderQueue::set_source_ready(std::lock_guard<std::mutex>&) {
// Here, it is necessary to check if _source_dependency is not nullptr.
// This is because the queue might be closed before setting the source
dependency.
@@ -391,16 +396,21 @@ Status VDataStreamRecvr::create_merger(const
VExprContextSPtrs& ordering_expr,
DCHECK(_is_merging);
SCOPED_CONSUME_MEM_TRACKER(_mem_tracker.get());
std::vector<BlockSupplier> child_block_suppliers;
+ std::vector<BlockSupplierReadyChecker> child_block_supplier_ready_checkers;
// Create the merger that will a single stream of sorted rows.
_merger.reset(new VSortedRunMerger(ordering_expr, is_asc_order,
nulls_first, batch_size, limit,
offset, _profile));
+ child_block_suppliers.reserve(_sender_queues.size());
+ child_block_supplier_ready_checkers.reserve(_sender_queues.size());
for (int i = 0; i < _sender_queues.size(); ++i) {
child_block_suppliers.emplace_back(std::bind(std::mem_fn(&SenderQueue::get_batch),
_sender_queues[i],
std::placeholders::_1,
std::placeholders::_2));
+ child_block_supplier_ready_checkers.emplace_back(
+ std::bind(std::mem_fn(&SenderQueue::has_data_or_finished),
_sender_queues[i]));
}
- RETURN_IF_ERROR(_merger->prepare(child_block_suppliers));
+ RETURN_IF_ERROR(_merger->prepare(child_block_suppliers,
child_block_supplier_ready_checkers));
return Status::OK();
}
diff --git a/be/src/exec/exchange/vdata_stream_recvr.h
b/be/src/exec/exchange/vdata_stream_recvr.h
index 88e2604a601..c465e8b597c 100644
--- a/be/src/exec/exchange/vdata_stream_recvr.h
+++ b/be/src/exec/exchange/vdata_stream_recvr.h
@@ -179,6 +179,8 @@ public:
Status get_batch(Block* next_block, bool* eos);
+ bool has_data_or_finished();
+
Status add_block(std::unique_ptr<PBlock> pblock, int be_number, int64_t
packet_seq,
::google::protobuf::Closure** done, const int64_t
wait_for_worker,
const uint64_t time_to_find_recvr);
diff --git a/be/src/exec/sort/sort_cursor.h b/be/src/exec/sort/sort_cursor.h
index d5b4a14e461..6d30c4a9351 100644
--- a/be/src/exec/sort/sort_cursor.h
+++ b/be/src/exec/sort/sort_cursor.h
@@ -22,6 +22,7 @@
#include <glog/logging.h>
+#include <functional>
#include <utility>
#include "core/block/block.h"
@@ -108,6 +109,7 @@ struct MergeSortCursorImpl {
virtual void process_next() {}
virtual Block* block_ptr() { return nullptr; }
virtual bool eof() const { return false; }
+ virtual bool has_ready_block_or_eos() const { return false; }
Field get_top_value() const {
Field field {PrimitiveType::TYPE_NULL};
@@ -117,14 +119,18 @@ struct MergeSortCursorImpl {
};
using BlockSupplier = std::function<Status(Block*, bool* eos)>;
+using BlockSupplierReadyChecker = std::function<bool()>;
struct BlockSupplierSortCursorImpl : public MergeSortCursorImpl {
ENABLE_FACTORY_CREATOR(BlockSupplierSortCursorImpl);
BlockSupplierSortCursorImpl(BlockSupplier block_supplier,
const VExprContextSPtrs& ordering_expr,
const std::vector<bool>& is_asc_order,
- const std::vector<bool>& nulls_first)
- : _ordering_expr(ordering_expr),
_block_supplier(std::move(block_supplier)) {
+ const std::vector<bool>& nulls_first,
+ BlockSupplierReadyChecker
block_supplier_ready_checker = {})
+ : _ordering_expr(ordering_expr),
+ _block_supplier(std::move(block_supplier)),
+
_block_supplier_ready_checker(std::move(block_supplier_ready_checker)) {
block = Block::create_shared();
sort_columns_size = ordering_expr.size();
@@ -136,11 +142,18 @@ struct BlockSupplierSortCursorImpl : public
MergeSortCursorImpl {
process_next();
}
- BlockSupplierSortCursorImpl(BlockSupplier block_supplier, const
SortDescription& desc_)
- : MergeSortCursorImpl(desc_),
_block_supplier(std::move(block_supplier)) {
+ BlockSupplierSortCursorImpl(BlockSupplier block_supplier, const
SortDescription& desc_,
+ BlockSupplierReadyChecker
block_supplier_ready_checker = {})
+ : MergeSortCursorImpl(desc_),
+ _block_supplier(std::move(block_supplier)),
+
_block_supplier_ready_checker(std::move(block_supplier_ready_checker)) {
process_next();
}
+ bool has_ready_block_or_eos() const override {
+ return _block_supplier_ready_checker &&
_block_supplier_ready_checker();
+ }
+
void process_next() override {
if (_is_eof) {
return;
@@ -167,6 +180,7 @@ struct BlockSupplierSortCursorImpl : public
MergeSortCursorImpl {
VExprContextSPtrs _ordering_expr;
BlockSupplier _block_supplier;
+ BlockSupplierReadyChecker _block_supplier_ready_checker;
bool _is_eof = false;
};
diff --git a/be/src/exec/sort/vsorted_run_merger.cpp
b/be/src/exec/sort/vsorted_run_merger.cpp
index 4981b0ae3ea..928bb99c16b 100644
--- a/be/src/exec/sort/vsorted_run_merger.cpp
+++ b/be/src/exec/sort/vsorted_run_merger.cpp
@@ -62,14 +62,22 @@ void VSortedRunMerger::init_timers(RuntimeProfile* profile)
{
_get_next_block_timer = ADD_TIMER(profile, "MergeGetNextBlock");
}
-Status VSortedRunMerger::prepare(const std::vector<BlockSupplier>& input_runs)
{
+Status VSortedRunMerger::prepare(
+ const std::vector<BlockSupplier>& input_runs,
+ const std::vector<BlockSupplierReadyChecker>&
input_run_ready_checkers) {
try {
- for (const auto& supplier : input_runs) {
+ for (size_t i = 0; i < input_runs.size(); ++i) {
+ const auto& supplier = input_runs[i];
+ BlockSupplierReadyChecker ready_checker;
+ if (i < input_run_ready_checkers.size()) {
+ ready_checker = input_run_ready_checkers[i];
+ }
if (_use_sort_desc) {
-
_cursors.emplace_back(BlockSupplierSortCursorImpl::create_shared(supplier,
_desc));
+ _cursors.emplace_back(
+ BlockSupplierSortCursorImpl::create_shared(supplier,
_desc, ready_checker));
} else {
_cursors.emplace_back(BlockSupplierSortCursorImpl::create_shared(
- supplier, _ordering_expr, _is_asc_order,
_nulls_first));
+ supplier, _ordering_expr, _is_asc_order, _nulls_first,
ready_checker));
}
}
} catch (const std::exception& e) {
@@ -194,7 +202,12 @@ Status VSortedRunMerger::get_next(Block* output_block,
bool* eos) {
}
current->next();
- if (_need_more_data(current)) {
+ const bool has_ready_block_or_eos =
+ current->is_last(0) && !current->eof() &&
current->has_ready_block_or_eos();
+ if (has_ready_block_or_eos) {
+ do_insert();
+ }
+ if (_need_more_data(current, has_ready_block_or_eos)) {
do_insert();
scoped_mutable_block.restore();
return Status::OK();
@@ -212,12 +225,21 @@ Status VSortedRunMerger::get_next(Block* output_block,
bool* eos) {
return Status::OK();
}
-bool VSortedRunMerger::_need_more_data(MergeSortCursor& current) {
+bool VSortedRunMerger::_need_more_data(MergeSortCursor& current, bool
has_ready_block_or_eos) {
if (!current->is_last(0)) {
_priority_queue.push(current);
return false;
} else if (current->eof()) {
return false;
+ } else if (has_ready_block_or_eos) {
+ {
+ ScopedTimer<MonotonicStopWatch> timer(_get_next_block_timer);
+ current->process_next();
+ }
+ if (!current->eof()) {
+ _priority_queue.push(current);
+ }
+ return false;
} else {
_pending_cursor = current.impl;
return true;
diff --git a/be/src/exec/sort/vsorted_run_merger.h
b/be/src/exec/sort/vsorted_run_merger.h
index 471333e862e..d06510f35aa 100644
--- a/be/src/exec/sort/vsorted_run_merger.h
+++ b/be/src/exec/sort/vsorted_run_merger.h
@@ -56,7 +56,8 @@ public:
// Prepare this merger to merge and return rows from the sorted runs in
'input_runs'.
// Retrieves the first batch from each run and sets up the binary heap
implementing
// the priority queue.
- Status prepare(const std::vector<BlockSupplier>& input_runs);
+ Status prepare(const std::vector<BlockSupplier>& input_runs,
+ const std::vector<BlockSupplierReadyChecker>&
input_run_ready_checkers = {});
// Return the next block of sorted rows from this merger.
Status get_next(Block* output_block, bool* eos);
@@ -92,8 +93,9 @@ protected:
private:
void init_timers(RuntimeProfile* profile);
- // If current stream is exhausted and not eof, we should break this loop
and read more blocks.
- bool _need_more_data(MergeSortCursor& current);
+ // If current stream is exhausted and not eof, keep merging only when the
next block or eos is
+ // already available. Otherwise break this loop and wait for more data.
+ bool _need_more_data(MergeSortCursor& current, bool
has_ready_block_or_eos);
};
} // namespace doris
diff --git a/be/test/core/value/sort_merger_test.cpp
b/be/test/core/value/sort_merger_test.cpp
index b5c0b8ced95..75a95db7c9b 100644
--- a/be/test/core/value/sort_merger_test.cpp
+++ b/be/test/core/value/sort_merger_test.cpp
@@ -17,8 +17,13 @@
#include <gtest/gtest.h>
+#include <cstdint>
+#include <string>
+#include <vector>
+
#include "core/data_type/data_type_nullable.h"
#include "core/data_type/data_type_number.h"
+#include "core/data_type/data_type_string.h"
#include "exec/sort/vsorted_run_merger.h"
#include "testutil/column_helper.h"
#include "testutil/mock/mock_slot_ref.h"
@@ -33,6 +38,16 @@ public:
void TearDown() override {}
};
+static Block create_int_string_block(const std::vector<int64_t>& keys,
+ const std::vector<std::string>& values) {
+ DCHECK_EQ(keys.size(), values.size());
+ auto key_column = ColumnHelper::create_column<DataTypeInt64>(keys);
+ auto value_column = ColumnHelper::create_column<DataTypeString>(values);
+ Block block({ColumnWithTypeAndName(key_column,
std::make_shared<DataTypeInt64>(), "key"),
+ ColumnWithTypeAndName(value_column,
std::make_shared<DataTypeString>(), "value")});
+ return block;
+}
+
TEST(SortMergerTest, NULL_FIRST_ASC) {
/**
* in: [([NULL, 1, 2, 3, 4], eos = false), ([], eos = true)]
@@ -478,4 +493,188 @@ TEST(SortMergerTest, TEST_SINGLE_STREAM) {
}
}
-} // namespace doris
\ No newline at end of file
+TEST(SortMergerTest, CONTINUE_MERGE_WHEN_NEXT_BLOCK_READY) {
+ const int batch_size = 4;
+ std::vector<std::vector<std::vector<int64_t>>> input_blocks = {{{1}, {3}},
{{2}, {4}}};
+ std::vector<size_t> next_block_index(input_blocks.size(), 0);
+
+ std::unique_ptr<VSortedRunMerger> merger;
+ auto profile = std::make_shared<RuntimeProfile>("");
+ auto ordering_expr =
MockSlotRef::create_mock_contexts(std::make_shared<DataTypeInt64>());
+ {
+ std::vector<bool> is_asc_order = {true};
+ std::vector<bool> nulls_first = {true};
+ const int limit = -1;
+ const int offset = 0;
+ merger.reset(new VSortedRunMerger(ordering_expr, is_asc_order,
nulls_first, batch_size,
+ limit, offset, profile.get()));
+ }
+ {
+ std::vector<BlockSupplier> child_block_suppliers;
+ std::vector<BlockSupplierReadyChecker> ready_checkers;
+ for (size_t child_idx = 0; child_idx < input_blocks.size();
child_idx++) {
+ BlockSupplier block_supplier = [&, id = child_idx](Block* block,
bool* eos) {
+ if (next_block_index[id] >= input_blocks[id].size()) {
+ *eos = true;
+ return Status::OK();
+ }
+ *block = ColumnHelper::create_block<DataTypeInt64>(
+ input_blocks[id][next_block_index[id]++]);
+ *eos = false;
+ return Status::OK();
+ };
+ child_block_suppliers.push_back(block_supplier);
+ ready_checkers.emplace_back([] { return true; });
+ }
+ EXPECT_TRUE(merger->prepare(child_block_suppliers,
ready_checkers).ok());
+ }
+ {
+ Block block;
+ bool eos = false;
+ EXPECT_TRUE(merger->get_next(&block, &eos).ok());
+ auto expect_block = ColumnHelper::create_column<DataTypeInt64>({1, 2,
3, 4});
+
EXPECT_TRUE(ColumnHelper::column_equal(block.get_by_position(0).column,
expect_block));
+ EXPECT_EQ(block.rows(), batch_size);
+ EXPECT_FALSE(eos);
+ }
+ {
+ Block block;
+ bool eos = false;
+ EXPECT_TRUE(merger->get_next(&block, &eos).ok());
+ EXPECT_EQ(block.rows(), 0);
+ EXPECT_TRUE(eos);
+ }
+}
+
+TEST(SortMergerTest, CONTINUE_MERGE_WITH_STRING_COLUMN_WHEN_NEXT_BLOCK_READY) {
+ const int batch_size = 4;
+ std::vector<std::vector<Block>> input_blocks;
+ input_blocks.emplace_back(
+ std::vector<Block> {create_int_string_block({1, 3}, {"old-1",
"old-3"}),
+ create_int_string_block({5, 7}, {"new-5",
"new-7"})});
+ input_blocks.emplace_back(
+ std::vector<Block> {create_int_string_block({2, 4}, {"other-2",
"other-4"})});
+ std::vector<size_t> next_block_index(input_blocks.size(), 0);
+
+ std::unique_ptr<VSortedRunMerger> merger;
+ auto profile = std::make_shared<RuntimeProfile>("");
+ auto ordering_expr =
MockSlotRef::create_mock_contexts(std::make_shared<DataTypeInt64>());
+ {
+ std::vector<bool> is_asc_order = {true};
+ std::vector<bool> nulls_first = {true};
+ const int limit = -1;
+ const int offset = 0;
+ merger.reset(new VSortedRunMerger(ordering_expr, is_asc_order,
nulls_first, batch_size,
+ limit, offset, profile.get()));
+ }
+ {
+ std::vector<BlockSupplier> child_block_suppliers;
+ std::vector<BlockSupplierReadyChecker> ready_checkers;
+ for (size_t child_idx = 0; child_idx < input_blocks.size();
child_idx++) {
+ BlockSupplier block_supplier = [&, id = child_idx](Block* block,
bool* eos) {
+ if (next_block_index[id] >= input_blocks[id].size()) {
+ *eos = true;
+ return Status::OK();
+ }
+ block->swap(input_blocks[id][next_block_index[id]++]);
+ *eos = false;
+ return Status::OK();
+ };
+ child_block_suppliers.push_back(block_supplier);
+ ready_checkers.emplace_back([] { return true; });
+ }
+ EXPECT_TRUE(merger->prepare(child_block_suppliers,
ready_checkers).ok());
+ }
+ {
+ Block block;
+ bool eos = false;
+ EXPECT_TRUE(merger->get_next(&block, &eos).ok());
+ auto expect_key_column =
ColumnHelper::create_column<DataTypeInt64>({1, 2, 3, 4});
+ auto expect_value_column = ColumnHelper::create_column<DataTypeString>(
+ {"old-1", "other-2", "old-3", "other-4"});
+
EXPECT_TRUE(ColumnHelper::column_equal(block.get_by_position(0).column,
expect_key_column));
+ EXPECT_TRUE(
+ ColumnHelper::column_equal(block.get_by_position(1).column,
expect_value_column));
+ EXPECT_EQ(block.rows(), batch_size);
+ EXPECT_FALSE(eos);
+ }
+ {
+ Block block;
+ bool eos = false;
+ EXPECT_TRUE(merger->get_next(&block, &eos).ok());
+ auto expect_key_column =
ColumnHelper::create_column<DataTypeInt64>({5, 7});
+ auto expect_value_column =
ColumnHelper::create_column<DataTypeString>({"new-5", "new-7"});
+
EXPECT_TRUE(ColumnHelper::column_equal(block.get_by_position(0).column,
expect_key_column));
+ EXPECT_TRUE(
+ ColumnHelper::column_equal(block.get_by_position(1).column,
expect_value_column));
+ EXPECT_EQ(block.rows(), 2);
+ EXPECT_FALSE(eos);
+ }
+}
+
+TEST(SortMergerTest, KEEP_PENDING_CURSOR_WHEN_NEXT_BLOCK_NOT_READY) {
+ const int batch_size = 4;
+ std::vector<std::vector<std::vector<int64_t>>> input_blocks = {{{1}, {3}},
{{2}, {4}}};
+ std::vector<size_t> next_block_index(input_blocks.size(), 0);
+ std::vector<bool> ready(input_blocks.size(), false);
+
+ std::unique_ptr<VSortedRunMerger> merger;
+ auto profile = std::make_shared<RuntimeProfile>("");
+ auto ordering_expr =
MockSlotRef::create_mock_contexts(std::make_shared<DataTypeInt64>());
+ {
+ std::vector<bool> is_asc_order = {true};
+ std::vector<bool> nulls_first = {true};
+ const int limit = -1;
+ const int offset = 0;
+ merger.reset(new VSortedRunMerger(ordering_expr, is_asc_order,
nulls_first, batch_size,
+ limit, offset, profile.get()));
+ }
+ {
+ std::vector<BlockSupplier> child_block_suppliers;
+ std::vector<BlockSupplierReadyChecker> ready_checkers;
+ for (size_t child_idx = 0; child_idx < input_blocks.size();
child_idx++) {
+ BlockSupplier block_supplier = [&, id = child_idx](Block* block,
bool* eos) {
+ if (next_block_index[id] >= input_blocks[id].size()) {
+ *eos = true;
+ return Status::OK();
+ }
+ *block = ColumnHelper::create_block<DataTypeInt64>(
+ input_blocks[id][next_block_index[id]++]);
+ *eos = false;
+ return Status::OK();
+ };
+ child_block_suppliers.push_back(block_supplier);
+ ready_checkers.emplace_back([&, id = child_idx] { return
ready[id]; });
+ }
+ EXPECT_TRUE(merger->prepare(child_block_suppliers,
ready_checkers).ok());
+ }
+ {
+ Block block;
+ bool eos = false;
+ EXPECT_TRUE(merger->get_next(&block, &eos).ok());
+ auto expect_block = ColumnHelper::create_column<DataTypeInt64>({1});
+
EXPECT_TRUE(ColumnHelper::column_equal(block.get_by_position(0).column,
expect_block));
+ EXPECT_EQ(block.rows(), 1);
+ EXPECT_FALSE(eos);
+ }
+ {
+ ready[0] = true;
+ ready[1] = true;
+ Block block;
+ bool eos = false;
+ EXPECT_TRUE(merger->get_next(&block, &eos).ok());
+ auto expect_block = ColumnHelper::create_column<DataTypeInt64>({2, 3,
4});
+
EXPECT_TRUE(ColumnHelper::column_equal(block.get_by_position(0).column,
expect_block));
+ EXPECT_EQ(block.rows(), 3);
+ EXPECT_FALSE(eos);
+ }
+ {
+ Block block;
+ bool eos = false;
+ EXPECT_TRUE(merger->get_next(&block, &eos).ok());
+ EXPECT_EQ(block.rows(), 0);
+ EXPECT_TRUE(eos);
+ }
+}
+
+} // namespace doris
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]