Skip to content

Commit ccaacce

Browse files
committed
Add timeout overflow break handling at leaf stage
1 parent a23c610 commit ccaacce

13 files changed

Lines changed: 734 additions & 22 deletions

File tree

pinot-broker/src/main/java/org/apache/pinot/broker/requesthandler/MultiStageBrokerRequestHandler.java

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -708,13 +708,42 @@ private void fillOldBrokerResponseStats(BrokerResponseNativeV2 brokerResponse,
708708
stageStats.forEach((type, stats) -> type.mergeInto(brokerResponse, stats));
709709
}
710710
}
711+
712+
if (isLeafStageMissingStats(queryStageMap, queryStats)) {
713+
brokerResponse.mergeTimeoutOverflowReached(true);
714+
}
711715
} catch (Exception e) {
712716
LOGGER.warn("Error encountered while collecting multi-stage stats", e);
713717
brokerResponse.setStageStats(JsonNodeFactory.instance.objectNode()
714718
.put("error", "Error encountered while collecting multi-stage stats - " + e));
715719
}
716720
}
717721

722+
private boolean isLeafStageMissingStats(Map<Integer, DispatchablePlanFragment> queryStageMap,
723+
List<MultiStageQueryStats.StageStats.Closed> queryStats) {
724+
for (Map.Entry<Integer, DispatchablePlanFragment> entry : queryStageMap.entrySet()) {
725+
int stageId = entry.getKey();
726+
DispatchablePlanFragment fragment = entry.getValue();
727+
if (!isLeafStage(fragment)) {
728+
continue;
729+
}
730+
MultiStageQueryStats.StageStats.Closed stageStats =
731+
stageId < queryStats.size() ? queryStats.get(stageId) : null;
732+
if (stageStats == null) {
733+
return true;
734+
}
735+
}
736+
return false;
737+
}
738+
739+
private boolean isLeafStage(DispatchablePlanFragment fragment) {
740+
Map<String, String> customProperties = fragment.getCustomProperties();
741+
if (customProperties.containsKey(DispatchablePlanFragment.TABLE_NAME_KEY)) {
742+
return true;
743+
}
744+
return !fragment.getWorkerIdToSegmentsMap().isEmpty();
745+
}
746+
718747
private BrokerResponse constructMultistageExplainPlan(String sql, String plan, Map<String, String> extraFields) {
719748
BrokerResponseNative brokerResponse = BrokerResponseNative.empty();
720749
int totalFieldCount = extraFields.size() + 2;

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: 65 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;
@@ -107,6 +108,7 @@
107108
*/
108109
public class QueryRunner {
109110
private static final Logger LOGGER = LoggerFactory.getLogger(QueryRunner.class);
111+
private static final double DEFAULT_LEAF_TIMEOUT_RATIO = 0.8;
110112

111113
private ExecutorService _executorService;
112114
private OpChainSchedulerService _opChainScheduler;
@@ -138,6 +140,8 @@ public class QueryRunner {
138140
@Nullable
139141
private WindowOverFlowMode _windowOverflowMode;
140142
@Nullable
143+
private TimeoutOverflowMode _timeoutOverflowMode;
144+
@Nullable
141145
private PhysicalTimeSeriesServerPlanVisitor _timeSeriesPhysicalPlanVisitor;
142146
private BooleanSupplier _sendStats;
143147

@@ -191,6 +195,10 @@ public void init(PinotConfiguration serverConf, InstanceDataManager instanceData
191195
String windowOverflowModeStr = serverConf.getProperty(MultiStageQueryRunner.KEY_OF_WINDOW_OVERFLOW_MODE);
192196
_windowOverflowMode = windowOverflowModeStr != null ? WindowOverFlowMode.valueOf(windowOverflowModeStr) : null;
193197

198+
String timeoutOverflowModeStr = serverConf.getProperty(MultiStageQueryRunner.KEY_OF_TIMEOUT_OVERFLOW_MODE);
199+
_timeoutOverflowMode =
200+
timeoutOverflowModeStr != null ? TimeoutOverflowMode.valueOf(timeoutOverflowModeStr) : null;
201+
194202
ExecutorService baseExecutorService =
195203
ExecutorServiceUtils.create(serverConf, Server.MULTISTAGE_EXECUTOR_CONFIG_PREFIX, "query-runner-on-" + port,
196204
Server.DEFAULT_MULTISTAGE_EXECUTOR_TYPE);
@@ -271,6 +279,7 @@ private void processQueryBlocking(WorkerMetadata workerMetadata, StagePlan stage
271279
Map<String, String> requestMetadata) {
272280
StageMetadata stageMetadata = stagePlan.getStageMetadata();
273281
Map<String, String> opChainMetadata = consolidateMetadata(stageMetadata.getCustomProperties(), requestMetadata);
282+
applyLeafDeadlineOverrides(workerMetadata, opChainMetadata);
274283

275284
// run pre-stage execution for all pipeline breakers
276285
PipelineBreakerResult pipelineBreakerResult =
@@ -508,9 +517,64 @@ private Map<String, String> consolidateMetadata(Map<String, String> customProper
508517
opChainMetadata.put(QueryOptionKey.WINDOW_OVERFLOW_MODE, windowOverflowMode.name());
509518
}
510519

520+
TimeoutOverflowMode timeoutOverflowMode = QueryOptionsUtils.getTimeoutOverflowMode(opChainMetadata);
521+
if (timeoutOverflowMode == null) {
522+
timeoutOverflowMode = _timeoutOverflowMode;
523+
}
524+
if (timeoutOverflowMode != null) {
525+
opChainMetadata.put(QueryOptionKey.TIMEOUT_OVERFLOW_MODE, timeoutOverflowMode.name());
526+
}
527+
511528
return opChainMetadata;
512529
}
513530

531+
private void applyLeafDeadlineOverrides(WorkerMetadata workerMetadata, Map<String, String> opChainMetadata) {
532+
if (!workerMetadata.isLeafStageWorker()) {
533+
return;
534+
}
535+
Long leafTimeoutMs = QueryOptionsUtils.getLeafTimeoutMs(opChainMetadata);
536+
Long leafExtraPassiveTimeoutMs = QueryOptionsUtils.getLeafExtraPassiveTimeoutMs(opChainMetadata);
537+
TimeoutOverflowMode timeoutOverflowMode = _timeoutOverflowMode;
538+
String timeoutOverflowModeStr = opChainMetadata.get(QueryOptionKey.TIMEOUT_OVERFLOW_MODE);
539+
if (timeoutOverflowModeStr != null) {
540+
try {
541+
timeoutOverflowMode = TimeoutOverflowMode.valueOf(timeoutOverflowModeStr);
542+
} catch (IllegalArgumentException e) {
543+
LOGGER.warn("Invalid timeout overflow mode: {}", timeoutOverflowModeStr, e);
544+
}
545+
}
546+
QueryThreadContext threadContext = QueryThreadContext.getIfAvailable();
547+
if (threadContext == null) {
548+
return;
549+
}
550+
QueryExecutionContext executionContext = threadContext.getExecutionContext();
551+
long startTimeMs = executionContext.getStartTimeMs();
552+
if (leafTimeoutMs == null && timeoutOverflowMode == TimeoutOverflowMode.BREAK) {
553+
long globalActiveWindowMs = Math.max(1L, executionContext.getActiveDeadlineMs() - startTimeMs);
554+
leafTimeoutMs = Math.max(1L, (long) Math.floor(globalActiveWindowMs * DEFAULT_LEAF_TIMEOUT_RATIO));
555+
}
556+
if (leafExtraPassiveTimeoutMs == null && timeoutOverflowMode == TimeoutOverflowMode.BREAK) {
557+
long globalPassiveWindowMs =
558+
Math.max(0L, executionContext.getPassiveDeadlineMs() - executionContext.getActiveDeadlineMs());
559+
leafExtraPassiveTimeoutMs =
560+
Math.max(0L, (long) Math.floor(globalPassiveWindowMs * DEFAULT_LEAF_TIMEOUT_RATIO));
561+
}
562+
if (leafTimeoutMs == null && leafExtraPassiveTimeoutMs == null) {
563+
return;
564+
}
565+
long activeDeadlineMs = executionContext.getActiveDeadlineMs();
566+
long passiveDeadlineMs = executionContext.getPassiveDeadlineMs();
567+
if (leafTimeoutMs != null) {
568+
activeDeadlineMs = Math.min(activeDeadlineMs, startTimeMs + leafTimeoutMs);
569+
}
570+
if (leafExtraPassiveTimeoutMs != null) {
571+
passiveDeadlineMs = Math.min(passiveDeadlineMs, activeDeadlineMs + leafExtraPassiveTimeoutMs);
572+
}
573+
passiveDeadlineMs = Math.max(passiveDeadlineMs, activeDeadlineMs);
574+
opChainMetadata.put(LeafOperator.LEAF_ACTIVE_DEADLINE_METADATA_KEY, Long.toString(activeDeadlineMs));
575+
opChainMetadata.put(LeafOperator.LEAF_PASSIVE_DEADLINE_METADATA_KEY, Long.toString(passiveDeadlineMs));
576+
}
577+
514578
public Map<Integer, MultiStageQueryStats.StageStats.Closed> cancel(long requestId) {
515579
return _opChainScheduler.cancel(requestId);
516580
}
@@ -524,6 +588,7 @@ public StagePlan explainQuery(WorkerMetadata workerMetadata, StagePlan stagePlan
524588

525589
StageMetadata stageMetadata = stagePlan.getStageMetadata();
526590
Map<String, String> opChainMetadata = consolidateMetadata(stageMetadata.getCustomProperties(), requestMetadata);
591+
applyLeafDeadlineOverrides(workerMetadata, opChainMetadata);
527592

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

0 commit comments

Comments
 (0)