You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@doris.apache.org by hu...@apache.org on 2026/04/15 12:00:31 UTC
(doris) branch fe_local_shuffle updated: [fix](local shuffle) Propagate num_tasks from LOCAL_EXCHANGE to ancestor pipelines
This is an automated email from the ASF dual-hosted git repository.
huajianlan pushed a commit to branch fe_local_shuffle
in repository https://gitbox.apache.org/repos/asf/doris.git
The following commit(s) were added to refs/heads/fe_local_shuffle by this push:
new b03722b95e9 [fix](local shuffle) Propagate num_tasks from LOCAL_EXCHANGE to ancestor pipelines
b03722b95e9 is described below
commit b03722b95e98b9d84943e2818cca4c4d710999de
Author: 924060929 <la...@selectdb.com>
AuthorDate: Wed Apr 15 19:59:35 2026 +0800
[fix](local shuffle) Propagate num_tasks from LOCAL_EXCHANGE to ancestor pipelines
When FE inserts LOCAL_EXCHANGE_NODE below a pipeline-splitting operator
(AGG, SORT, JOIN), the LOCAL_EXCHANGE handler restores its immediate
pipeline to _num_instances tasks. However, ancestor pipelines created
earlier by the splitting operators still carry the reduced num_tasks
inherited from a serial operator (e.g., pooling scan or UNPARTITIONED
Exchange).
This causes a num_tasks mismatch: e.g., AggSink pipeline has
_num_instances tasks but AggSource pipeline has only 1 task. Instance
1+ creates AggSink tasks but no AggSource task, leaving source_deps
uninitialized and causing DCHECK failure in set_ready_to_read().
Fix: add _propagate_local_exchange_num_tasks() after _build_pipelines
to walk the DAG and raise ancestor pipelines to _num_instances, stopping
at serial source operators. Only runs when FE-planned LOCAL_EXCHANGE
nodes exist (no impact on BE-native path).
---
be/src/exec/pipeline/pipeline_fragment_context.cpp | 50 ++++++++++++++++++++++
be/src/exec/pipeline/pipeline_fragment_context.h | 4 ++
2 files changed, 54 insertions(+)
diff --git a/be/src/exec/pipeline/pipeline_fragment_context.cpp b/be/src/exec/pipeline/pipeline_fragment_context.cpp
index abfe3048fff..358873e6323 100644
--- a/be/src/exec/pipeline/pipeline_fragment_context.cpp
+++ b/be/src/exec/pipeline/pipeline_fragment_context.cpp
@@ -261,6 +261,10 @@ Status PipelineFragmentContext::_build_and_prepare_full_pipeline(ThreadPool* thr
RETURN_IF_ERROR(_build_pipelines(_runtime_state->obj_pool(), *_query_ctx->desc_tbl,
&_root_op, root_pipeline));
+ // Propagate _num_instances from LOCAL_EXCHANGE pipelines to ancestor pipelines
+ // that inherited reduced num_tasks from a serial operator.
+ _propagate_local_exchange_num_tasks();
+
// Create deferred local exchangers now that all pipelines have final num_tasks.
RETURN_IF_ERROR(_create_deferred_local_exchangers());
@@ -729,6 +733,52 @@ Status PipelineFragmentContext::_create_deferred_local_exchangers() {
return Status::OK();
}
+void PipelineFragmentContext::_propagate_local_exchange_num_tasks() {
+ if (_deferred_exchangers.empty()) {
+ return;
+ }
+ // When FE inserts LOCAL_EXCHANGE_NODE below a pipeline-splitting operator (AGG, SORT,
+ // JOIN), LOCAL_EXCHANGE_NODE processing restores its immediate pipeline to
+ // _num_instances. However, ancestor pipelines created earlier by the splitting
+ // operators still carry the reduced num_tasks inherited from a serial operator.
+ //
+ // Walk the DAG: for each downstream pipeline whose num_tasks < _num_instances, if any
+ // upstream dependency already has _num_instances tasks, raise the downstream too —
+ // unless its source operator is serial (it legitimately has 1 task). Iterate until
+ // stable.
+ std::unordered_map<PipelineId, PipelinePtr> id_to_pipe;
+ for (auto& p : _pipelines) {
+ id_to_pipe[p->id()] = p;
+ }
+
+ bool changed = true;
+ while (changed) {
+ changed = false;
+ for (const auto& [downstream_id, upstream_ids] : _dag) {
+ auto dit = id_to_pipe.find(downstream_id);
+ if (dit == id_to_pipe.end()) {
+ continue;
+ }
+ auto& downstream = dit->second;
+ if (downstream->num_tasks() >= _num_instances) {
+ continue;
+ }
+ if (!downstream->operators().empty() &&
+ downstream->operators().front()->is_serial_operator()) {
+ continue;
+ }
+ for (auto upstream_id : upstream_ids) {
+ auto uit = id_to_pipe.find(upstream_id);
+ if (uit != id_to_pipe.end() && uit->second->num_tasks() >= _num_instances) {
+ downstream->set_num_tasks(_num_instances);
+ changed = true;
+ break;
+ }
+ }
+ }
+ }
+}
+
Status PipelineFragmentContext::_create_tree_helper(
ObjectPool* pool, const std::vector<TPlanNode>& tnodes, const DescriptorTbl& descs,
OperatorPtr parent, int* node_idx, OperatorPtr* root, PipelinePtr& cur_pipe, int child_idx,
diff --git a/be/src/exec/pipeline/pipeline_fragment_context.h b/be/src/exec/pipeline/pipeline_fragment_context.h
index 97bfbef524d..777fc5ac2ba 100644
--- a/be/src/exec/pipeline/pipeline_fragment_context.h
+++ b/be/src/exec/pipeline/pipeline_fragment_context.h
@@ -345,6 +345,10 @@ private:
};
std::vector<DeferredExchangerInfo> _deferred_exchangers;
Status _create_deferred_local_exchangers();
+ // After _build_pipelines, propagate _num_instances from FE-planned LOCAL_EXCHANGE
+ // pipelines upward through the DAG to ancestor pipelines that inherited reduced
+ // num_tasks from a serial operator.
+ void _propagate_local_exchange_num_tasks();
//Here are two types of runtime states:
// - _runtime state is at the Fragment level.
---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org