Skip to content

Commit ba1d7db

Browse files
committed
Limit shared exchange budgets and ready Collect to table fragments
1 parent 5dda422 commit ba1d7db

13 files changed

Lines changed: 322 additions & 85 deletions

File tree

‎integration-test/src/test/java/org/apache/iotdb/relational/it/query/recent/IoTDBCollectFanInIT.java‎

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,9 @@
3434
import java.sql.ResultSet;
3535
import java.sql.Statement;
3636
import java.util.ArrayList;
37+
import java.util.HashSet;
3738
import java.util.List;
39+
import java.util.Set;
3840

3941
import static org.apache.iotdb.db.it.utils.CollectQueryAssertions.assertUnorderedQueryResult;
4042
import static org.junit.Assert.assertEquals;
@@ -109,7 +111,7 @@ public void testUnorderedUnionPreservesEveryDuplicateOnEachNode() throws Excepti
109111
}
110112

111113
@Test
112-
public void testSharedExchangePathsForTreeAndTreeToTable() throws Exception {
114+
public void testLegacyTreeAndSharedTreeToTablePaths() throws Exception {
113115
try (Connection connection = EnvFactory.getEnv().getConnection();
114116
Statement statement = connection.createStatement()) {
115117
statement.execute("CREATE DATABASE root.collect_tree");
@@ -124,10 +126,25 @@ public void testSharedExchangePathsForTreeAndTreeToTable() throws Exception {
124126
try (ResultSet result =
125127
statement.executeQuery("SELECT value FROM root.collect_tree.*.* ALIGN BY DEVICE")) {
126128
int rows = 0;
129+
Set<String> seenDevices = new HashSet<>();
130+
String currentDevice = null;
131+
int deviceRows = 0;
127132
while (result.next()) {
133+
String nextDevice = result.getString("Device");
134+
if (!nextDevice.equals(currentDevice)) {
135+
if (currentDevice != null) {
136+
assertEquals(ROWS, deviceRows);
137+
}
138+
assertTrue("Tree results must retain contiguous devices", seenDevices.add(nextDevice));
139+
currentDevice = nextDevice;
140+
deviceRows = 0;
141+
}
142+
assertEquals(++deviceRows, result.getLong("Time"));
128143
assertEquals(result.getLong("Time"), result.getLong("value"));
129144
rows++;
130145
}
146+
assertEquals(ROWS, deviceRows);
147+
assertEquals(DEVICES, seenDevices.size());
131148
assertEquals(DEVICES * ROWS, rows);
132149
}
133150
}
@@ -300,7 +317,7 @@ public void testOrderByAfterGlobalWindowAndConstantFill() throws Exception {
300317
}
301318

302319
@Test
303-
public void testTopKAfterGlobalWindowConsumesUnorderedInput() throws Exception {
320+
public void testExcludedTopKPlanRetainsQueryResults() throws Exception {
304321
try (Connection connection = EnvFactory.getEnv().getTableConnection();
305322
Statement statement = connection.createStatement()) {
306323
statement.execute("USE " + DATABASE);

‎iotdb-core/calc-commons/src/main/java/org/apache/iotdb/calc/execution/operator/process/CollectOperator.java‎

Lines changed: 57 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,9 @@ public class CollectOperator implements ProcessOperator {
3939

4040
private final CommonOperatorContext operatorContext;
4141
protected final List<Operator> children;
42-
private final AnyChildBlocked anyChildBlocked;
42+
private AnyChildBlocked anyChildBlocked;
43+
private boolean inited;
44+
protected int currentIndex;
4345
private ListenableFuture<Integer> selectionFuture;
4446
private ListenableFuture<Integer> exposedFuture;
4547
private int remainingChildren;
@@ -48,17 +50,36 @@ public class CollectOperator implements ProcessOperator {
4850
public CollectOperator(CommonOperatorContext operatorContext, List<Operator> children) {
4951
this.operatorContext = operatorContext;
5052
this.children = children;
51-
this.anyChildBlocked = new AnyChildBlocked(children);
5253
this.remainingChildren = children.size();
5354
}
5455

56+
/**
57+
* Enable only after the complete table FI has passed eligibility checks, before drivers start.
58+
*/
59+
public void enableReadyInputConsumption() {
60+
if (anyChildBlocked == null) {
61+
anyChildBlocked = new AnyChildBlocked(children);
62+
}
63+
}
64+
5565
@Override
5666
public boolean hasNext() {
57-
return !closed && remainingChildren > 0;
67+
return anyChildBlocked == null
68+
? currentIndex < children.size()
69+
: !closed && remainingChildren > 0;
5870
}
5971

6072
@Override
6173
public TsBlock next() throws Exception {
74+
if (anyChildBlocked == null) {
75+
if (children.get(currentIndex).hasNextWithTimer()) {
76+
TsBlock block = children.get(currentIndex).nextWithTimer();
77+
return block == null ? null : transformBlock(currentIndex, block);
78+
}
79+
closeCurrentChild(currentIndex);
80+
currentIndex++;
81+
return null;
82+
}
6283
anyChildBlocked.checkFailure();
6384
if (!hasNext()) {
6485
return null;
@@ -98,6 +119,11 @@ protected TsBlock transformBlock(int childId, TsBlock block) {
98119
}
99120

100121
protected void closeCurrentChild(int index) throws Exception {
122+
if (anyChildBlocked == null) {
123+
children.get(index).close();
124+
children.set(index, null);
125+
return;
126+
}
101127
Operator child = children.get(index);
102128
if (child == null) {
103129
return;
@@ -111,6 +137,15 @@ protected void closeCurrentChild(int index) throws Exception {
111137

112138
@Override
113139
public ListenableFuture<?> isBlocked() {
140+
if (anyChildBlocked == null) {
141+
if (!inited) {
142+
inited = true;
143+
for (Operator child : children) {
144+
child.isBlocked();
145+
}
146+
}
147+
return currentIndex >= children.size() ? NOT_BLOCKED : children.get(currentIndex).isBlocked();
148+
}
114149
if (!hasNext()) {
115150
return NOT_BLOCKED;
116151
}
@@ -135,6 +170,14 @@ public CommonOperatorContext getOperatorContext() {
135170

136171
@Override
137172
public void close() throws Exception {
173+
if (anyChildBlocked == null) {
174+
for (int i = currentIndex; i < children.size(); i++) {
175+
if (children.get(i) != null) {
176+
closeCurrentChild(i);
177+
}
178+
}
179+
return;
180+
}
138181
if (closed) {
139182
return;
140183
}
@@ -161,6 +204,13 @@ public void close() throws Exception {
161204

162205
@Override
163206
public long calculateMaxPeekMemory() {
207+
if (anyChildBlocked == null) {
208+
long maxPeekMemory = 0;
209+
for (Operator child : children) {
210+
maxPeekMemory = Math.max(maxPeekMemory, child.calculateMaxPeekMemoryWithCounter());
211+
}
212+
return maxPeekMemory;
213+
}
164214
long retained = calculateRetainedSizeAfterCallingNext();
165215
long peak = retained;
166216
for (Operator child : children) {
@@ -189,6 +239,9 @@ public long calculateMaxReturnSize() {
189239

190240
@Override
191241
public long calculateRetainedSizeAfterCallingNext() {
242+
if (anyChildBlocked == null) {
243+
return 0;
244+
}
192245
long retained = 0;
193246
for (Operator child : children) {
194247
if (child != null) {
@@ -206,7 +259,7 @@ public List<Operator> getChildren() {
206259
@Override
207260
public long ramBytesUsed() {
208261
return INSTANCE_SIZE
209-
+ anyChildBlocked.ramBytesUsed()
262+
+ (anyChildBlocked == null ? 0 : anyChildBlocked.ramBytesUsed())
210263
+ children.stream()
211264
.mapToLong(MemoryEstimationHelper::getEstimatedSizeOfAccountableObject)
212265
.sum()

‎iotdb-core/calc-commons/src/test/java/org/apache/iotdb/calc/execution/operator/process/CollectOperatorTest.java‎

Lines changed: 50 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,10 @@ private static TsBlock block(int... values) {
7171
}
7272

7373
private static CollectOperator collect(Operator... children) {
74-
return new CollectOperator(new TestContext(), new ArrayList<>(Arrays.asList(children)));
74+
CollectOperator collect =
75+
new CollectOperator(new TestContext(), new ArrayList<>(Arrays.asList(children)));
76+
collect.enableReadyInputConsumption();
77+
return collect;
7578
}
7679

7780
@Test
@@ -100,6 +103,7 @@ public void testThousandChildrenAndOneListenerPerPendingPeriod() throws Exceptio
100103
children.add(new Child(block(i)));
101104
}
102105
CollectOperator collect = new CollectOperator(new TestContext(), children);
106+
collect.enableReadyInputConsumption();
103107
Child ready = (Child) children.get(count - 1);
104108
for (int round = 0; round < 5; round++) {
105109
ListenableFuture<?> wait = collect.isBlocked();
@@ -133,6 +137,7 @@ public void testAllReadyChildrenHaveLinearSelectionCost() throws Exception {
133137
}
134138
CountingList countedChildren = new CountingList(children);
135139
CollectOperator collect = new CollectOperator(new TestContext(), countedChildren);
140+
collect.enableReadyInputConsumption();
136141
try {
137142
for (int round = 0; round < rounds; round++) {
138143
assertTrue(collect.isBlocked().isDone());
@@ -178,6 +183,7 @@ public void testClosedChildrenDoNotIncreaseRemainingChildSelectionCost() throws
178183
}
179184
CountingList countedChildren = new CountingList(children);
180185
CollectOperator collect = new CollectOperator(new TestContext(), countedChildren);
186+
collect.enableReadyInputConsumption();
181187
try {
182188
// Retire the first 999 children and consume one block from the remaining child.
183189
for (int i = 0; i < childCount; i++) {
@@ -378,6 +384,47 @@ public void testCloseWakesWaitAndClosesEveryChildDespiteException() throws Excep
378384
assertEquals(1, b.closes);
379385
}
380386

387+
@Test
388+
public void testDefaultCollectRetainsSequentialConsumption() throws Exception {
389+
Child a = new Child(block(1));
390+
Child b = new Child(block(2));
391+
b.ready();
392+
CollectOperator collect =
393+
new CollectOperator(new TestContext(), new ArrayList<>(Arrays.asList(a, b)));
394+
assertFalse(collect.isBlocked().isDone());
395+
a.ready();
396+
assertSame(a.output, collect.next());
397+
a.hasNext = false;
398+
a.finished = true;
399+
assertNull(collect.next());
400+
assertSame(b.output, collect.next());
401+
assertEquals(0, collect.calculateRetainedSizeAfterCallingNext());
402+
collect.close();
403+
assertEquals(1, a.closes);
404+
assertEquals(1, b.closes);
405+
}
406+
407+
@Test
408+
public void testDefaultMappingCollectRetainsSequentialConsumption() throws Exception {
409+
Child a = new Child(block(10, 11));
410+
Child b = new Child(block(20, 21));
411+
b.ready();
412+
List<List<Integer>> mappings = new ArrayList<>();
413+
mappings.add(Collections.singletonList(0));
414+
mappings.add(Collections.singletonList(1));
415+
MappingCollectOperator collect =
416+
new MappingCollectOperator(
417+
new TestContext(), new ArrayList<>(Arrays.asList(a, b)), mappings);
418+
assertFalse(collect.isBlocked().isDone());
419+
a.ready();
420+
assertEquals(10, collect.next().getColumn(0).getInt(0));
421+
a.hasNext = false;
422+
a.finished = true;
423+
assertNull(collect.next());
424+
assertEquals(21, collect.next().getColumn(0).getInt(0));
425+
collect.close();
426+
}
427+
381428
@Test
382429
public void testMappingUsesActualChildIdentity() throws Exception {
383430
Child a = new Child(block(10, 11));
@@ -389,6 +436,7 @@ public void testMappingUsesActualChildIdentity() throws Exception {
389436
MappingCollectOperator collect =
390437
new MappingCollectOperator(
391438
new TestContext(), new ArrayList<>(Arrays.asList(a, b)), mappings);
439+
collect.enableReadyInputConsumption();
392440
assertTrue(collect.isBlocked().isDone());
393441
assertEquals(21, collect.next().getColumn(0).getInt(0));
394442
a.ready();
@@ -473,6 +521,7 @@ public void testConcurrentCompletionsConsumeEveryChildExactlyOnce() throws Excep
473521
List<Child> children = new ArrayList<>();
474522
for (int i = 0; i < count; i++) children.add(new Child(block(i)));
475523
CollectOperator collect = new CollectOperator(new TestContext(), new ArrayList<>(children));
524+
collect.enableReadyInputConsumption();
476525
ListenableFuture<?> pending = collect.isBlocked();
477526
assertFalse(pending.isDone());
478527
ExecutorService executor = Executors.newFixedThreadPool(count);

‎iotdb-core/datanode/src/main/i18n/en/org/apache/iotdb/db/i18n/DataNodeQueryMessages.java‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3942,4 +3942,5 @@ private DataNodeQueryMessages() {}
39423942

39433943
public static final String EXCEPTION_PLAN_NODE_ARG_IS_ALREADY_REGISTERED_ON_MEMORY_SIDE_ARG_NOT_ARG_4C1BB625 = "Plan node %s is already registered on memory side %s, not %s";
39443944
public static final String EXCEPTION_MEMORY_RESERVATION_IS_NOT_REGISTERED_FOR_QUERY_ARG_FRAGMENT_ARG_PLAN_NODE_ARG_67A4B81F = "Memory reservation is not registered for query %s, fragment %s, plan node %s";
3945+
public static final String LOG_SHARED_EXCHANGE_BUDGET_FOR_FI_ARG_INPUT_ARG_OUTPUT_ARG_98AE1109 = "Shared exchange budget for FI {}: INPUT={}, OUTPUT={}";
39453946
}

‎iotdb-core/datanode/src/main/i18n/zh/org/apache/iotdb/db/i18n/DataNodeQueryMessages.java‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4698,4 +4698,5 @@ private DataNodeQueryMessages() {}
46984698

46994699
public static final String EXCEPTION_PLAN_NODE_ARG_IS_ALREADY_REGISTERED_ON_MEMORY_SIDE_ARG_NOT_ARG_4C1BB625 = "PlanNode %s 已注册到内存侧 %s,不能注册到 %s";
47004700
public static final String EXCEPTION_MEMORY_RESERVATION_IS_NOT_REGISTERED_FOR_QUERY_ARG_FRAGMENT_ARG_PLAN_NODE_ARG_67A4B81F = "查询 %s、Fragment %s、PlanNode %s 尚未注册内存账目";
4701+
public static final String LOG_SHARED_EXCHANGE_BUDGET_FOR_FI_ARG_INPUT_ARG_OUTPUT_ARG_98AE1109 = "FI {} 的共享 exchange 预算:INPUT={},OUTPUT={}";
47014702
}

‎iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/planner/DataNodeTableOperatorGenerator.java‎

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import org.apache.iotdb.calc.execution.filter.TopKRuntimeFilter;
2323
import org.apache.iotdb.calc.execution.operator.CommonOperatorContext;
2424
import org.apache.iotdb.calc.execution.operator.Operator;
25+
import org.apache.iotdb.calc.execution.operator.process.CollectOperator;
2526
import org.apache.iotdb.calc.execution.operator.process.FilterAndProjectOperator;
2627
import org.apache.iotdb.calc.execution.operator.process.LimitOperator;
2728
import org.apache.iotdb.calc.execution.operator.process.OffsetOperator;
@@ -47,7 +48,9 @@
4748
import org.apache.iotdb.commons.queryengine.plan.relational.metadata.QualifiedObjectName;
4849
import org.apache.iotdb.commons.queryengine.plan.relational.planner.Symbol;
4950
import org.apache.iotdb.commons.queryengine.plan.relational.planner.node.AggregationNode;
51+
import org.apache.iotdb.commons.queryengine.plan.relational.planner.node.CollectNode;
5052
import org.apache.iotdb.commons.queryengine.plan.relational.planner.node.TopKNode;
53+
import org.apache.iotdb.commons.queryengine.plan.relational.planner.node.UnionNode;
5154
import org.apache.iotdb.commons.queryengine.plan.relational.sql.ast.Expression;
5255
import org.apache.iotdb.commons.queryengine.plan.relational.sql.ast.FunctionCall;
5356
import org.apache.iotdb.commons.queryengine.plan.relational.sql.ast.LongLiteral;
@@ -301,6 +304,20 @@ protected OperatorContext addOperatorContext(
301304
.addOperatorContext(context.getNextOperatorId(), planNodeId, operatorType);
302305
}
303306

307+
@Override
308+
public Operator visitCollect(CollectNode node, LocalExecutionPlanContext context) {
309+
CollectOperator collect = (CollectOperator) super.visitCollect(node, context);
310+
context.addCollectOperator(collect);
311+
return collect;
312+
}
313+
314+
@Override
315+
public Operator visitUnion(UnionNode node, LocalExecutionPlanContext context) {
316+
CollectOperator collect = (CollectOperator) super.visitUnion(node, context);
317+
context.addCollectOperator(collect);
318+
return collect;
319+
}
320+
304321
@Override
305322
public Operator visitCteScan(CteScanNode node, LocalExecutionPlanContext context) {
306323
OperatorContext operatorContext =

0 commit comments

Comments
 (0)