This is an automated email from the ASF dual-hosted git repository.
JackieTien97 pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/iotdb.git
The following commit(s) were added to refs/heads/master by this push:
new 81caad0395f [Query] Allow concurrent local fragment execution for
last-query collection (#18727)
81caad0395f is described below
commit 81caad0395fd5ad748440b7c39d8506a6ade213d
Author: Jackie Tien <[email protected]>
AuthorDate: Mon Sep 28 14:00:26 2026 +0800
[Query] Allow concurrent local fragment execution for last-query collection
(#18727)
---
.../process/last/LastQueryCollectOperator.java | 12 +-
.../operator/LastQueryCollectOperatorTest.java | 140 +++++++++++++++++++++
2 files changed, 150 insertions(+), 2 deletions(-)
diff --git
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/execution/operator/process/last/LastQueryCollectOperator.java
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/execution/operator/process/last/LastQueryCollectOperator.java
index 4e4603fcfcc..b5642956bcf 100644
---
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/execution/operator/process/last/LastQueryCollectOperator.java
+++
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/execution/operator/process/last/LastQueryCollectOperator.java
@@ -24,7 +24,6 @@ import
org.apache.iotdb.calc.execution.operator.process.ProcessOperator;
import org.apache.iotdb.commons.queryengine.execution.MemoryEstimationHelper;
import org.apache.iotdb.db.queryengine.execution.operator.OperatorContext;
-import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import org.apache.tsfile.read.common.block.TsBlock;
import org.apache.tsfile.utils.RamUsageEstimator;
@@ -42,6 +41,8 @@ public class LastQueryCollectOperator implements
ProcessOperator {
private final int inputOperatorsCount;
+ private boolean inited = false;
+
private int currentIndex;
public LastQueryCollectOperator(OperatorContext operatorContext,
List<Operator> children) {
@@ -58,10 +59,17 @@ public class LastQueryCollectOperator implements
ProcessOperator {
@Override
public ListenableFuture<?> isBlocked() {
+ if (!inited) {
+ inited = true;
+ // Unblock all local upstream fragment instances before consuming
children one by one.
+ for (Operator child : children) {
+ child.isBlocked();
+ }
+ }
if (currentIndex < inputOperatorsCount) {
return children.get(currentIndex).isBlocked();
} else {
- return Futures.immediateVoidFuture();
+ return NOT_BLOCKED;
}
}
diff --git
a/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/queryengine/execution/operator/LastQueryCollectOperatorTest.java
b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/queryengine/execution/operator/LastQueryCollectOperatorTest.java
new file mode 100644
index 00000000000..9e6bb9ad6a0
--- /dev/null
+++
b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/queryengine/execution/operator/LastQueryCollectOperatorTest.java
@@ -0,0 +1,140 @@
+/*
+ * 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.iotdb.db.queryengine.execution.operator;
+
+import org.apache.iotdb.calc.execution.operator.Operator;
+import org.apache.iotdb.commons.queryengine.plan.planner.plan.node.PlanNodeId;
+import
org.apache.iotdb.db.queryengine.execution.exchange.MPPDataExchangeManager.SinkListener;
+import
org.apache.iotdb.db.queryengine.execution.exchange.MPPDataExchangeManager.SourceHandleListener;
+import org.apache.iotdb.db.queryengine.execution.exchange.SharedTsBlockQueue;
+import org.apache.iotdb.db.queryengine.execution.exchange.Utils;
+import
org.apache.iotdb.db.queryengine.execution.exchange.sink.LocalSinkChannel;
+import
org.apache.iotdb.db.queryengine.execution.exchange.source.LocalSourceHandle;
+import org.apache.iotdb.db.queryengine.execution.memory.LocalMemoryManager;
+import org.apache.iotdb.db.queryengine.execution.memory.MemoryPool;
+import
org.apache.iotdb.db.queryengine.execution.operator.process.last.LastQueryCollectOperator;
+import
org.apache.iotdb.db.queryengine.execution.operator.source.ExchangeOperator;
+import org.apache.iotdb.mpp.rpc.thrift.TFragmentInstanceId;
+
+import com.google.common.util.concurrent.ListenableFuture;
+import org.apache.tsfile.read.common.block.TsBlock;
+import org.junit.Test;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.concurrent.ExecutorService;
+
+import static
com.google.common.util.concurrent.MoreExecutors.newDirectExecutorService;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+public class LastQueryCollectOperatorTest {
+
+ @Test
+ public void testUnblockAllLocalFragmentInstances() throws Exception {
+ ExecutorService executor = newDirectExecutorService();
+ LocalMemoryManager memoryManager = mock(LocalMemoryManager.class);
+ MemoryPool memoryPool = Utils.createMockNonBlockedMemoryPool();
+ when(memoryManager.getQueryPool()).thenReturn(memoryPool);
+ TFragmentInstanceId downstreamId = new TFragmentInstanceId("q0", 0, "0");
+ List<Operator> children = new ArrayList<>();
+ List<LocalSinkChannel> sinks = new ArrayList<>();
+ List<ListenableFuture<?>> producerBlocked = new ArrayList<>();
+
+ for (int i = 0; i < 2; i++) {
+ String planNodeId = "exchange_" + i;
+ SharedTsBlockQueue queue =
+ new SharedTsBlockQueue(downstreamId, planNodeId, memoryManager,
executor);
+ LocalSinkChannel sink =
+ new LocalSinkChannel(
+ new TFragmentInstanceId("q0", i + 1, "0"), queue,
mock(SinkListener.class));
+ sinks.add(sink);
+ producerBlocked.add(sink.isFull());
+ LocalSourceHandle source =
+ new LocalSourceHandle(downstreamId, planNodeId, queue,
mock(SourceHandleListener.class));
+ OperatorContext context = mock(OperatorContext.class);
+ when(context.getSpecifiedInfo()).thenReturn(new HashMap<>());
+ children.add(new ExchangeOperator(context, source, new
PlanNodeId(planNodeId)));
+ }
+
+ try (LastQueryCollectOperator collect =
+ new LastQueryCollectOperator(mock(OperatorContext.class), children)) {
+ for (ListenableFuture<?> blocked : producerBlocked) {
+ assertFalse(blocked.isDone());
+ }
+
+ ListenableFuture<?> blocked = collect.isBlocked();
+ assertFalse(blocked.isDone());
+ for (ListenableFuture<?> producer : producerBlocked) {
+ assertTrue(producer.isDone());
+ }
+
+ // Consume the first child's output without waiting for the second child
to produce data.
+ TsBlock first = Utils.createMockTsBlock(1024);
+ sinks.get(0).send(first);
+ assertTrue(blocked.isDone());
+ assertTrue(collect.isBlocked().isDone());
+ assertSame(first, collect.next());
+
+ // The second fragment can produce data while the first fragment is
still unfinished.
+ blocked = collect.isBlocked();
+ assertFalse(blocked.isDone());
+ TsBlock second = Utils.createMockTsBlock(1024);
+ sinks.get(1).send(second);
+ sinks.get(1).setNoMoreTsBlocks();
+ assertFalse(blocked.isDone());
+ assertFalse(collect.isBlocked().isDone());
+
+ sinks.get(0).setNoMoreTsBlocks();
+ assertTrue(blocked.isDone());
+ assertNull(collect.next());
+ assertTrue(collect.hasNext());
+ assertTrue(collect.isBlocked().isDone());
+ assertSame(second, collect.next());
+ assertTrue(collect.isBlocked().isDone());
+ assertNull(collect.next());
+ assertFalse(collect.hasNext());
+ assertTrue(collect.isFinished());
+ assertTrue(collect.isBlocked().isDone());
+ } finally {
+ for (LocalSinkChannel sink : sinks) {
+ sink.close();
+ }
+ executor.shutdownNow();
+ }
+ }
+
+ @Test
+ public void testEmptyChildren() throws Exception {
+ try (LastQueryCollectOperator collect =
+ new LastQueryCollectOperator(mock(OperatorContext.class),
Collections.emptyList())) {
+ assertTrue(collect.isBlocked().isDone());
+ assertFalse(collect.hasNext());
+ assertTrue(collect.isFinished());
+ assertTrue(collect.isBlocked().isDone());
+ }
+ }
+}