Skip to content

Commit d04720b

Browse files
committed
Add timeout overflow break handling at leaf stage
1 parent 62b20d9 commit d04720b

12 files changed

Lines changed: 659 additions & 22 deletions

File tree

pinot-common/src/main/java/org/apache/pinot/common/response/broker/BrokerResponseNativeV2.java

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@ public class BrokerResponseNativeV2 implements BrokerResponse {
7676
private String _requestId;
7777
private String _clientRequestId;
7878
private String _brokerId;
79+
private boolean _timeoutOverflowReached;
7980
private int _numServersQueried;
8081
private int _numServersResponded;
8182
private long _brokerReduceTimeMs;
@@ -109,7 +110,8 @@ public int getNumRowsResultSet() {
109110
@JsonProperty(access = JsonProperty.Access.READ_ONLY)
110111
@Override
111112
public boolean isPartialResult() {
112-
return getExceptionsSize() > 0 || isNumGroupsLimitReached() || isMaxRowsInJoinReached();
113+
return getExceptionsSize() > 0 || isNumGroupsLimitReached() || isMaxRowsInJoinReached()
114+
|| isTimeoutOverflowReached();
113115
}
114116

115117
@Override
@@ -152,6 +154,18 @@ public void mergeNumGroupsWarningLimitReached(boolean numGroupsWarningLimitReach
152154
_brokerStats.merge(StatKey.NUM_GROUPS_WARNING_LIMIT_REACHED, numGroupsWarningLimitReached);
153155
}
154156

157+
@JsonProperty("timeoutOverflowReached")
158+
public boolean isTimeoutOverflowReached() {
159+
return _timeoutOverflowReached;
160+
}
161+
162+
public void mergeTimeoutOverflowReached(boolean timeoutOverflowReached) {
163+
if (timeoutOverflowReached) {
164+
_timeoutOverflowReached = true;
165+
}
166+
_brokerStats.merge(StatKey.TIMEOUT_OVERFLOW_REACHED, timeoutOverflowReached);
167+
}
168+
155169
@Override
156170
public boolean isMaxRowsInJoinReached() {
157171
return _maxRowsInJoinReached;
@@ -427,6 +441,9 @@ public boolean getRLSFiltersApplied() {
427441

428442
public void addBrokerStats(StatMap<StatKey> brokerStats) {
429443
_brokerStats.merge(brokerStats);
444+
if (brokerStats.getBoolean(StatKey.TIMEOUT_OVERFLOW_REACHED)) {
445+
_timeoutOverflowReached = true;
446+
}
430447
}
431448

432449
// NOTE: The following keys should match the keys in the leaf-stage operator.
@@ -453,7 +470,8 @@ public long merge(long value1, long value2) {
453470
NUM_SEGMENTS_PRUNED_BY_VALUE(StatMap.Type.INT),
454471
GROUPS_TRIMMED(StatMap.Type.BOOLEAN),
455472
NUM_GROUPS_LIMIT_REACHED(StatMap.Type.BOOLEAN),
456-
NUM_GROUPS_WARNING_LIMIT_REACHED(StatMap.Type.BOOLEAN);
473+
NUM_GROUPS_WARNING_LIMIT_REACHED(StatMap.Type.BOOLEAN),
474+
TIMEOUT_OVERFLOW_REACHED(StatMap.Type.BOOLEAN);
457475

458476
private final StatMap.Type _type;
459477

pinot-common/src/main/java/org/apache/pinot/common/utils/config/QueryOptionsUtils.java

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
import org.apache.pinot.spi.utils.CommonConstants;
3535
import org.apache.pinot.spi.utils.CommonConstants.Broker.Request.QueryOptionKey;
3636
import org.apache.pinot.spi.utils.CommonConstants.MultiStageQueryRunner.JoinOverFlowMode;
37+
import org.apache.pinot.spi.utils.CommonConstants.MultiStageQueryRunner.TimeoutOverflowMode;
3738
import org.apache.pinot.spi.utils.CommonConstants.MultiStageQueryRunner.WindowOverFlowMode;
3839

3940

@@ -432,6 +433,24 @@ public static WindowOverFlowMode getWindowOverflowMode(Map<String, String> query
432433
return windowOverflowModeStr != null ? WindowOverFlowMode.valueOf(windowOverflowModeStr) : null;
433434
}
434435

436+
@Nullable
437+
public static TimeoutOverflowMode getTimeoutOverflowMode(Map<String, String> queryOptions) {
438+
String timeoutOverflowModeStr = queryOptions.get(QueryOptionKey.TIMEOUT_OVERFLOW_MODE);
439+
return timeoutOverflowModeStr != null ? TimeoutOverflowMode.valueOf(timeoutOverflowModeStr) : null;
440+
}
441+
442+
@Nullable
443+
public static Long getLeafTimeoutMs(Map<String, String> queryOptions) {
444+
String leafTimeoutMs = queryOptions.get(QueryOptionKey.LEAF_TIMEOUT_MS);
445+
return checkedParseLongPositive(QueryOptionKey.LEAF_TIMEOUT_MS, leafTimeoutMs);
446+
}
447+
448+
@Nullable
449+
public static Long getLeafExtraPassiveTimeoutMs(Map<String, String> queryOptions) {
450+
String leafExtraPassiveTimeoutMs = queryOptions.get(QueryOptionKey.LEAF_EXTRA_PASSIVE_TIMEOUT_MS);
451+
return checkedParseLong(QueryOptionKey.LEAF_EXTRA_PASSIVE_TIMEOUT_MS, leafExtraPassiveTimeoutMs, 0);
452+
}
453+
435454
public static boolean isSkipUnavailableServers(Map<String, String> queryOptions) {
436455
return Boolean.parseBoolean(queryOptions.get(QueryOptionKey.SKIP_UNAVAILABLE_SERVERS));
437456
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
/**
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
package org.apache.pinot.common.response.broker;
20+
21+
import com.fasterxml.jackson.databind.JsonNode;
22+
import java.io.IOException;
23+
import org.apache.pinot.spi.utils.JsonUtils;
24+
import org.testng.Assert;
25+
import org.testng.annotations.Test;
26+
27+
28+
public class BrokerResponseNativeV2Test {
29+
30+
@Test
31+
public void testTimeoutOverflowFlagSerialized()
32+
throws IOException {
33+
BrokerResponseNativeV2 response = new BrokerResponseNativeV2();
34+
Assert.assertFalse(response.isTimeoutOverflowReached());
35+
Assert.assertFalse(response.isPartialResult());
36+
37+
response.mergeTimeoutOverflowReached(true);
38+
39+
Assert.assertTrue(response.isTimeoutOverflowReached());
40+
Assert.assertTrue(response.isPartialResult());
41+
42+
JsonNode json = JsonUtils.objectToJsonNode(response);
43+
Assert.assertTrue(json.get("timeoutOverflowReached").asBoolean());
44+
Assert.assertTrue(json.get("partialResult").asBoolean());
45+
}
46+
}

pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/QueryRunner.java

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@
8686
import org.apache.pinot.spi.utils.CommonConstants.Helix;
8787
import org.apache.pinot.spi.utils.CommonConstants.MultiStageQueryRunner;
8888
import org.apache.pinot.spi.utils.CommonConstants.MultiStageQueryRunner.JoinOverFlowMode;
89+
import org.apache.pinot.spi.utils.CommonConstants.MultiStageQueryRunner.TimeoutOverflowMode;
8990
import org.apache.pinot.spi.utils.CommonConstants.MultiStageQueryRunner.WindowOverFlowMode;
9091
import org.apache.pinot.spi.utils.CommonConstants.Query.Request;
9192
import org.apache.pinot.spi.utils.CommonConstants.Query.Response;
@@ -138,6 +139,8 @@ public class QueryRunner {
138139
@Nullable
139140
private WindowOverFlowMode _windowOverflowMode;
140141
@Nullable
142+
private TimeoutOverflowMode _timeoutOverflowMode;
143+
@Nullable
141144
private PhysicalTimeSeriesServerPlanVisitor _timeSeriesPhysicalPlanVisitor;
142145
private BooleanSupplier _sendStats;
143146

@@ -191,6 +194,10 @@ public void init(PinotConfiguration serverConf, InstanceDataManager instanceData
191194
String windowOverflowModeStr = serverConf.getProperty(MultiStageQueryRunner.KEY_OF_WINDOW_OVERFLOW_MODE);
192195
_windowOverflowMode = windowOverflowModeStr != null ? WindowOverFlowMode.valueOf(windowOverflowModeStr) : null;
193196

197+
String timeoutOverflowModeStr = serverConf.getProperty(MultiStageQueryRunner.KEY_OF_TIMEOUT_OVERFLOW_MODE);
198+
_timeoutOverflowMode =
199+
timeoutOverflowModeStr != null ? TimeoutOverflowMode.valueOf(timeoutOverflowModeStr) : null;
200+
194201
ExecutorService baseExecutorService =
195202
ExecutorServiceUtils.create(serverConf, Server.MULTISTAGE_EXECUTOR_CONFIG_PREFIX, "query-runner-on-" + port,
196203
Server.DEFAULT_MULTISTAGE_EXECUTOR_TYPE);
@@ -271,6 +278,7 @@ private void processQueryBlocking(WorkerMetadata workerMetadata, StagePlan stage
271278
Map<String, String> requestMetadata) {
272279
StageMetadata stageMetadata = stagePlan.getStageMetadata();
273280
Map<String, String> opChainMetadata = consolidateMetadata(stageMetadata.getCustomProperties(), requestMetadata);
281+
applyLeafDeadlineOverrides(workerMetadata, opChainMetadata);
274282

275283
// run pre-stage execution for all pipeline breakers
276284
PipelineBreakerResult pipelineBreakerResult =
@@ -508,9 +516,45 @@ private Map<String, String> consolidateMetadata(Map<String, String> customProper
508516
opChainMetadata.put(QueryOptionKey.WINDOW_OVERFLOW_MODE, windowOverflowMode.name());
509517
}
510518

519+
TimeoutOverflowMode timeoutOverflowMode = QueryOptionsUtils.getTimeoutOverflowMode(opChainMetadata);
520+
if (timeoutOverflowMode == null) {
521+
timeoutOverflowMode = _timeoutOverflowMode;
522+
}
523+
if (timeoutOverflowMode != null) {
524+
opChainMetadata.put(QueryOptionKey.TIMEOUT_OVERFLOW_MODE, timeoutOverflowMode.name());
525+
}
526+
511527
return opChainMetadata;
512528
}
513529

530+
private void applyLeafDeadlineOverrides(WorkerMetadata workerMetadata, Map<String, String> opChainMetadata) {
531+
if (!workerMetadata.isLeafStageWorker()) {
532+
return;
533+
}
534+
Long leafTimeoutMs = QueryOptionsUtils.getLeafTimeoutMs(opChainMetadata);
535+
Long leafExtraPassiveTimeoutMs = QueryOptionsUtils.getLeafExtraPassiveTimeoutMs(opChainMetadata);
536+
if (leafTimeoutMs == null && leafExtraPassiveTimeoutMs == null) {
537+
return;
538+
}
539+
QueryThreadContext threadContext = QueryThreadContext.getIfAvailable();
540+
if (threadContext == null) {
541+
return;
542+
}
543+
QueryExecutionContext executionContext = threadContext.getExecutionContext();
544+
long startTimeMs = executionContext.getStartTimeMs();
545+
long activeDeadlineMs = executionContext.getActiveDeadlineMs();
546+
long passiveDeadlineMs = executionContext.getPassiveDeadlineMs();
547+
if (leafTimeoutMs != null) {
548+
activeDeadlineMs = Math.min(activeDeadlineMs, startTimeMs + leafTimeoutMs);
549+
}
550+
if (leafExtraPassiveTimeoutMs != null) {
551+
passiveDeadlineMs = Math.min(passiveDeadlineMs, activeDeadlineMs + leafExtraPassiveTimeoutMs);
552+
}
553+
passiveDeadlineMs = Math.max(passiveDeadlineMs, activeDeadlineMs);
554+
opChainMetadata.put(LeafOperator.LEAF_ACTIVE_DEADLINE_METADATA_KEY, Long.toString(activeDeadlineMs));
555+
opChainMetadata.put(LeafOperator.LEAF_PASSIVE_DEADLINE_METADATA_KEY, Long.toString(passiveDeadlineMs));
556+
}
557+
514558
public Map<Integer, MultiStageQueryStats.StageStats.Closed> cancel(long requestId) {
515559
return _opChainScheduler.cancel(requestId);
516560
}
@@ -524,6 +568,7 @@ public StagePlan explainQuery(WorkerMetadata workerMetadata, StagePlan stagePlan
524568

525569
StageMetadata stageMetadata = stagePlan.getStageMetadata();
526570
Map<String, String> opChainMetadata = consolidateMetadata(stageMetadata.getCustomProperties(), requestMetadata);
571+
applyLeafDeadlineOverrides(workerMetadata, opChainMetadata);
527572

528573
if (PipelineBreakerExecutor.hasPipelineBreakers(stagePlan)) {
529574
//TODO: See https://github.com/apache/pinot/pull/13733#discussion_r1752031714

pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/operator/BaseJoinOperator.java

Lines changed: 49 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,9 +39,11 @@
3939
import org.apache.pinot.query.runtime.operator.operands.TransformOperandFactory;
4040
import org.apache.pinot.query.runtime.plan.OpChainExecutionContext;
4141
import org.apache.pinot.spi.exception.QueryErrorCode;
42+
import org.apache.pinot.spi.exception.QueryException;
4243
import org.apache.pinot.spi.utils.BooleanUtils;
4344
import org.apache.pinot.spi.utils.CommonConstants.Broker.Request.QueryOptionKey;
4445
import org.apache.pinot.spi.utils.CommonConstants.MultiStageQueryRunner.JoinOverFlowMode;
46+
import org.apache.pinot.spi.utils.CommonConstants.MultiStageQueryRunner.TimeoutOverflowMode;
4547
import org.slf4j.Logger;
4648
import org.slf4j.LoggerFactory;
4749

@@ -83,10 +85,12 @@ public abstract class BaseJoinOperator extends MultiStageOperator {
8385
* BREAK: Break right table build process, continue to perform JOIN operation, results might be partial.
8486
*/
8587
protected final JoinOverFlowMode _joinOverflowMode;
88+
protected final TimeoutOverflowMode _timeoutOverflowMode;
8689

8790
protected boolean _isRightTableBuilt;
8891
@Nullable
8992
protected MseBlock.Eos _eos;
93+
private boolean _timeoutOverflowTriggered;
9094

9195
public BaseJoinOperator(OpChainExecutionContext context, MultiStageOperator leftInput, DataSchema leftSchema,
9296
MultiStageOperator rightInput, JoinNode node) {
@@ -106,6 +110,8 @@ public BaseJoinOperator(OpChainExecutionContext context, MultiStageOperator left
106110
PlanNode.NodeHint nodeHint = node.getNodeHint();
107111
_maxRowsInJoin = getMaxRowsInJoin(metadata, nodeHint);
108112
_joinOverflowMode = getJoinOverflowMode(metadata, nodeHint);
113+
TimeoutOverflowMode timeoutOverflowMode = QueryOptionsUtils.getTimeoutOverflowMode(metadata);
114+
_timeoutOverflowMode = timeoutOverflowMode != null ? timeoutOverflowMode : TimeoutOverflowMode.THROW;
109115
}
110116

111117
/// Constructor that takes the schema for NonEquiEvaluator as an argument
@@ -127,6 +133,8 @@ public BaseJoinOperator(OpChainExecutionContext context, MultiStageOperator left
127133
PlanNode.NodeHint nodeHint = node.getNodeHint();
128134
_maxRowsInJoin = getMaxRowsInJoin(metadata, nodeHint);
129135
_joinOverflowMode = getJoinOverflowMode(metadata, nodeHint);
136+
TimeoutOverflowMode timeoutOverflowMode = QueryOptionsUtils.getTimeoutOverflowMode(metadata);
137+
_timeoutOverflowMode = timeoutOverflowMode != null ? timeoutOverflowMode : TimeoutOverflowMode.THROW;
130138
}
131139

132140
protected static int getMaxRowsInJoin(Map<String, String> opChainMetadata, @Nullable PlanNode.NodeHint nodeHint) {
@@ -158,6 +166,14 @@ protected static JoinOverFlowMode getJoinOverflowMode(Map<String, String> contex
158166
return joinOverflowMode != null ? joinOverflowMode : DEFAULT_JOIN_OVERFLOW_MODE;
159167
}
160168

169+
@Override
170+
protected long getDeadlineMs() {
171+
if (_timeoutOverflowTriggered && _timeoutOverflowMode == TimeoutOverflowMode.BREAK) {
172+
return Long.MAX_VALUE;
173+
}
174+
return super.getDeadlineMs();
175+
}
176+
161177
@Override
162178
public void registerExecution(long time, int numRows, long memoryUsedBytes, long gcTimeMs) {
163179
_statMap.merge(StatKey.EXECUTION_TIME_MS, time);
@@ -283,6 +299,14 @@ protected MseBlock buildJoinedDataBlock() {
283299

284300
protected abstract List<Object[]> buildNonMatchRightRows();
285301

302+
@Override
303+
protected MseBlock handleException(Exception e) {
304+
if (maybeHandleTimeoutOverflow(e)) {
305+
return SuccessMseBlock.INSTANCE;
306+
}
307+
return super.handleException(e);
308+
}
309+
286310
// TODO: Optimize this to avoid unnecessary object copy.
287311
protected Object[] joinRow(@Nullable Object[] leftRow, @Nullable Object[] rightRow) {
288312
Object[] resultRow = new Object[_resultColumnSize];
@@ -329,6 +353,29 @@ protected void earlyTerminateLeftInput() {
329353
_eos = (MseBlock.Eos) leftBlock;
330354
}
331355

356+
private boolean maybeHandleTimeoutOverflow(Exception e) {
357+
if (_timeoutOverflowMode != TimeoutOverflowMode.BREAK) {
358+
return false;
359+
}
360+
if (e instanceof QueryException) {
361+
QueryException queryException = (QueryException) e;
362+
if (queryException.getErrorCode() == QueryErrorCode.EXECUTION_TIMEOUT) {
363+
if (!_timeoutOverflowTriggered) {
364+
logger().warn("Join operator {} returning partial results after timeout: {}", _operatorId,
365+
queryException.getMessage());
366+
}
367+
_timeoutOverflowTriggered = true;
368+
_statMap.merge(StatKey.TIMEOUT_OVERFLOW_REACHED, true);
369+
_leftInput.earlyTerminate();
370+
_rightInput.earlyTerminate();
371+
_eos = SuccessMseBlock.INSTANCE;
372+
onEosProduced();
373+
return true;
374+
}
375+
}
376+
return false;
377+
}
378+
332379
@Override
333380
protected StatMap<?> copyStatMaps() {
334381
return new StatMap<>(_statMap);
@@ -397,7 +444,8 @@ public boolean includeDefaultInJson() {
397444
/**
398445
* Time spent on GC while this operator or its children in the same stage were running.
399446
*/
400-
GC_TIME_MS(StatMap.Type.LONG);
447+
GC_TIME_MS(StatMap.Type.LONG),
448+
TIMEOUT_OVERFLOW_REACHED(StatMap.Type.BOOLEAN);
401449

402450
private final StatMap.Type _type;
403451

0 commit comments

Comments
 (0)