This is an automated email from the ASF dual-hosted git repository.
Mryange pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/doris.git
The following commit(s) were added to refs/heads/master by this push:
new b9d20cf337e [fix](profile) avoid out-of-bounds when backends report
uneven pipeline counts (#66283)
b9d20cf337e is described below
commit b9d20cf337e0bba2c792e5abc36721913b4a1ec0
Author: York Cao <[email protected]>
AuthorDate: Tue Aug 4 15:31:30 2026 +0800
[fix](profile) avoid out-of-bounds when backends report uneven pipeline
counts (#66283)
### What problem does this PR solve?
Issue Number: close #66282
Problem Summary:
A fragment's pipeline count is built at runtime per backend
(`add_pipeline` reacts to data distribution and local-exchange
injection), so backends of one fragment can legitimately report
different pipeline counts. `ExecutionProfile.getMultiBeProfile()` takes
`pipelineSize` as the max across backends, then indexed every backend at
that position, throwing `IndexOutOfBoundsException` on the backends with
fewer pipelines and dropping the entire merged profile for the query.
This adds a bounds check so backends with fewer pipelines are skipped at
the higher indices.
### Release note
Fix IndexOutOfBoundsException in merged profile generation when backends
of a fragment report uneven pipeline counts.
### Check List (For Author)
- [x] Test <!-- At least one of them must be included. -->
- [x] Regression test
- [x] Unit Test
- [ ] Manual test (add detailed scripts or steps below)
- [ ] No need to test or manual test. Explain why:
- [x] Behavior changed:
- [x] No.
- [x] Does this need documentation?
- [x] No.
<!-- ExecutionProfileTest.testAggregatedProfileUnevenPipelineCounts
reproduces the crash (Index 1 out of bounds for length 1) before the fix
and passes after. -->
---
.../doris/common/profile/ExecutionProfile.java | 6 ++
.../doris/common/profile/ExecutionProfileTest.java | 71 ++++++++++++++++++++++
2 files changed, 77 insertions(+)
diff --git
a/fe/fe-core/src/main/java/org/apache/doris/common/profile/ExecutionProfile.java
b/fe/fe-core/src/main/java/org/apache/doris/common/profile/ExecutionProfile.java
index 47d6fce03ac..067c695e1d8 100644
---
a/fe/fe-core/src/main/java/org/apache/doris/common/profile/ExecutionProfile.java
+++
b/fe/fe-core/src/main/java/org/apache/doris/common/profile/ExecutionProfile.java
@@ -134,6 +134,12 @@ public class ExecutionProfile {
for (int pipelineIdx = 0; pipelineIdx < pipelineSize;
pipelineIdx++) {
List<RuntimeProfile> allPipelineTask = new
ArrayList<RuntimeProfile>();
for (List<RuntimeProfile> profileSingleBE :
multiPipeline.values()) {
+ // BEs may report different pipeline counts (pipelineSize
is the max
+ // above); a BE with fewer pipelines simply has no profile
at this
+ // index, so skip it instead of indexing out of bounds.
+ if (pipelineIdx >= profileSingleBE.size()) {
+ continue;
+ }
RuntimeProfile pipeline = profileSingleBE.get(pipelineIdx);
for (Pair<RuntimeProfile, Boolean> pipelineTaskProfile :
pipeline.getChildList()) {
allPipelineTask.add(pipelineTaskProfile.first);
diff --git
a/fe/fe-core/src/test/java/org/apache/doris/common/profile/ExecutionProfileTest.java
b/fe/fe-core/src/test/java/org/apache/doris/common/profile/ExecutionProfileTest.java
new file mode 100644
index 00000000000..bd8310148bd
--- /dev/null
+++
b/fe/fe-core/src/test/java/org/apache/doris/common/profile/ExecutionProfileTest.java
@@ -0,0 +1,71 @@
+// 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.
+
+package org.apache.doris.common.profile;
+
+import org.apache.doris.common.Pair;
+import org.apache.doris.thrift.TNetworkAddress;
+import org.apache.doris.thrift.TUniqueId;
+
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import org.junit.Assert;
+import org.junit.Test;
+
+import java.util.List;
+
+public class ExecutionProfileTest {
+
+ // A fragment whose backends report different pipeline counts must not
blow up the
+ // merge. pipelineSize is taken as the max across backends, so a backend
with fewer
+ // pipelines has no profile at the higher indices; before the fix,
indexing it threw
+ // IndexOutOfBoundsException and getPipelineAggregatedProfile lost the
whole tree.
+ @Test
+ public void testAggregatedProfileUnevenPipelineCounts() {
+ TUniqueId queryId = new TUniqueId(1L, 2L);
+ ExecutionProfile executionProfile = new ExecutionProfile(queryId,
Lists.newArrayList(0));
+
+ // Backend A has two pipelines, backend B only one.
+ TNetworkAddress beA = new TNetworkAddress("127.0.0.1", 9050);
+ TNetworkAddress beB = new TNetworkAddress("127.0.0.2", 9050);
+ executionProfile.setMultiBeProfile(0, beA, Lists.newArrayList(
+ pipelineWithTasks("Pipeline 0(host=" + beA + ")", 2),
+ pipelineWithTasks("Pipeline 1(host=" + beA + ")", 2)));
+ executionProfile.setMultiBeProfile(0, beB, Lists.newArrayList(
+ pipelineWithTasks("Pipeline 0(host=" + beB + ")", 2)));
+
+ // Must not throw despite the uneven pipeline counts.
+ RuntimeProfile result =
executionProfile.getPipelineAggregatedProfile(Maps.newHashMap());
+
+ Assert.assertNotNull(result);
+ Assert.assertEquals("Fragments", result.getName());
+ // The max pipeline count (2) is used, so both pipelines are
represented.
+ List<Pair<RuntimeProfile, Boolean>> fragments = result.getChildList();
+ Assert.assertEquals(1, fragments.size());
+ RuntimeProfile fragment0 = fragments.get(0).first;
+ Assert.assertEquals("Fragment 0", fragment0.getName());
+ Assert.assertEquals(2, fragment0.getChildList().size());
+ }
+
+ private RuntimeProfile pipelineWithTasks(String name, int taskNum) {
+ RuntimeProfile pipeline = new RuntimeProfile(name);
+ for (int i = 0; i < taskNum; i++) {
+ pipeline.addChild(new RuntimeProfile(name + "-task" + i), true);
+ }
+ return pipeline;
+ }
+}
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]