Skip to content

Commit aa35571

Browse files
committed
Add timeout overflow break handling at leaf stage
1 parent 54fdace commit aa35571

13 files changed

Lines changed: 705 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
@@ -975,6 +975,10 @@ private void fillOldBrokerResponseStats(BrokerResponseNativeV2 brokerResponse,
975975
(int) numSegmentsPrunedByBroker);
976976
brokerResponse.addBrokerStats(brokerPruningStats);
977977
}
978+
979+
if (isLeafStageMissingStats(queryStageMap, queryStats)) {
980+
brokerResponse.mergeTimeoutOverflowReached(true);
981+
}
978982
} catch (Exception e) {
979983
LOGGER.warn("Error encountered while collecting multi-stage stats", e);
980984
brokerResponse.setStageStats(JsonNodeFactory.instance.objectNode()
@@ -996,6 +1000,31 @@ private void fillOldBrokerResponseStats(BrokerResponseNativeV2 brokerResponse,
9961000
}
9971001
}
9981002

1003+
private boolean isLeafStageMissingStats(Map<Integer, DispatchablePlanFragment> queryStageMap,
1004+
List<MultiStageQueryStats.StageStats.Closed> queryStats) {
1005+
for (Map.Entry<Integer, DispatchablePlanFragment> entry : queryStageMap.entrySet()) {
1006+
int stageId = entry.getKey();
1007+
DispatchablePlanFragment fragment = entry.getValue();
1008+
if (!isLeafStage(fragment)) {
1009+
continue;
1010+
}
1011+
MultiStageQueryStats.StageStats.Closed stageStats =
1012+
stageId < queryStats.size() ? queryStats.get(stageId) : null;
1013+
if (stageStats == null) {
1014+
return true;
1015+
}
1016+
}
1017+
return false;
1018+
}
1019+
1020+
private boolean isLeafStage(DispatchablePlanFragment fragment) {
1021+
Map<String, String> customProperties = fragment.getCustomProperties();
1022+
if (customProperties.containsKey(DispatchablePlanFragment.TABLE_NAME_KEY)) {
1023+
return true;
1024+
}
1025+
return !fragment.getWorkerIdToSegmentsMap().isEmpty();
1026+
}
1027+
9991028
private BrokerResponse constructMultistageExplainPlan(String sql, String plan, Map<String, String> extraFields) {
10001029
BrokerResponseNative brokerResponse = BrokerResponseNative.empty();
10011030
int totalFieldCount = extraFields.size() + 2;

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

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@ public class BrokerResponseNativeV2 implements BrokerResponse {
8888
private String _requestId;
8989
private String _clientRequestId;
9090
private String _brokerId;
91+
private boolean _timeoutOverflowReached;
9192
private int _numServersQueried;
9293
private int _numServersResponded;
9394
private long _brokerReduceTimeMs;
@@ -126,7 +127,7 @@ public int getNumRowsResultSet() {
126127
@Override
127128
public boolean isPartialResult() {
128129
return getExceptionsSize() > 0 || isNumGroupsLimitReached() || !getEarlyTerminationReasons().isEmpty()
129-
|| isMaxRowsInJoinReached() || isMseLiteLeafStageLimitReached();
130+
|| isMaxRowsInJoinReached() || isMseLiteLeafStageLimitReached() || isTimeoutOverflowReached();
130131
}
131132

132133
@Override
@@ -211,6 +212,18 @@ public List<String> getEarlyTerminationReasons() {
211212
return List.copyOf(_brokerStats.getStringSet(StatKey.EARLY_TERMINATION_REASONS));
212213
}
213214

215+
@JsonProperty("timeoutOverflowReached")
216+
public boolean isTimeoutOverflowReached() {
217+
return _timeoutOverflowReached;
218+
}
219+
220+
public void mergeTimeoutOverflowReached(boolean timeoutOverflowReached) {
221+
if (timeoutOverflowReached) {
222+
_timeoutOverflowReached = true;
223+
}
224+
_brokerStats.merge(StatKey.TIMEOUT_OVERFLOW_REACHED, timeoutOverflowReached);
225+
}
226+
214227
@Override
215228
public boolean isMaxRowsInJoinReached() {
216229
return _maxRowsInJoinReached;
@@ -515,6 +528,9 @@ public boolean getRLSFiltersApplied() {
515528

516529
public void addBrokerStats(StatMap<StatKey> brokerStats) {
517530
_brokerStats.merge(brokerStats);
531+
if (brokerStats.getBoolean(StatKey.TIMEOUT_OVERFLOW_REACHED)) {
532+
_timeoutOverflowReached = true;
533+
}
518534
}
519535

520536
// NOTE: The following keys should match the keys in the leaf-stage operator.
@@ -550,7 +566,8 @@ public long merge(long value1, long value2) {
550566
}
551567
},
552568
EARLY_TERMINATION_REASONS(StatMap.Type.STRING_SET),
553-
LITE_MODE_LEAF_STAGE_LIMIT_REACHED(StatMap.Type.BOOLEAN);
569+
LITE_MODE_LEAF_STAGE_LIMIT_REACHED(StatMap.Type.BOOLEAN),
570+
TIMEOUT_OVERFLOW_REACHED(StatMap.Type.BOOLEAN);
554571

555572
private final StatMap.Type _type;
556573

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
@@ -33,6 +33,7 @@
3333
import org.apache.pinot.spi.utils.CommonConstants;
3434
import org.apache.pinot.spi.utils.CommonConstants.Broker.Request.QueryOptionKey;
3535
import org.apache.pinot.spi.utils.CommonConstants.MultiStageQueryRunner.JoinOverFlowMode;
36+
import org.apache.pinot.spi.utils.CommonConstants.MultiStageQueryRunner.TimeoutOverflowMode;
3637
import org.apache.pinot.spi.utils.CommonConstants.MultiStageQueryRunner.WindowOverFlowMode;
3738

3839

@@ -511,6 +512,24 @@ public static WindowOverFlowMode getWindowOverflowMode(Map<String, String> query
511512
return windowOverflowModeStr != null ? WindowOverFlowMode.valueOf(windowOverflowModeStr) : null;
512513
}
513514

515+
@Nullable
516+
public static TimeoutOverflowMode getTimeoutOverflowMode(Map<String, String> queryOptions) {
517+
String timeoutOverflowModeStr = queryOptions.get(QueryOptionKey.TIMEOUT_OVERFLOW_MODE);
518+
return timeoutOverflowModeStr != null ? TimeoutOverflowMode.valueOf(timeoutOverflowModeStr) : null;
519+
}
520+
521+
@Nullable
522+
public static Long getLeafTimeoutMs(Map<String, String> queryOptions) {
523+
String leafTimeoutMs = queryOptions.get(QueryOptionKey.LEAF_TIMEOUT_MS);
524+
return checkedParseLongPositive(QueryOptionKey.LEAF_TIMEOUT_MS, leafTimeoutMs);
525+
}
526+
527+
@Nullable
528+
public static Long getLeafExtraPassiveTimeoutMs(Map<String, String> queryOptions) {
529+
String leafExtraPassiveTimeoutMs = queryOptions.get(QueryOptionKey.LEAF_EXTRA_PASSIVE_TIMEOUT_MS);
530+
return checkedParseLong(QueryOptionKey.LEAF_EXTRA_PASSIVE_TIMEOUT_MS, leafExtraPassiveTimeoutMs, 0);
531+
}
532+
514533
public static boolean isSkipUnavailableServers(Map<String, String> queryOptions) {
515534
return Boolean.parseBoolean(queryOptions.get(QueryOptionKey.SKIP_UNAVAILABLE_SERVERS));
516535
}

pinot-common/src/test/java/org/apache/pinot/common/response/broker/BrokerResponseNativeV2Test.java

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,13 @@
1818
*/
1919
package org.apache.pinot.common.response.broker;
2020

21+
import com.fasterxml.jackson.databind.JsonNode;
22+
import java.io.IOException;
2123
import java.util.LinkedHashSet;
2224
import java.util.List;
2325
import java.util.Set;
2426
import org.apache.pinot.common.datatable.StatMap;
27+
import org.apache.pinot.spi.utils.JsonUtils;
2528
import org.testng.annotations.Test;
2629

2730
import static org.testng.Assert.assertEquals;
@@ -47,6 +50,23 @@ public void testEarlyTerminationReasonsMarkPartialResponse() {
4750
assertTrue(brokerResponse.isPartialResult());
4851
}
4952

53+
@Test
54+
public void testTimeoutOverflowFlagSerialized()
55+
throws IOException {
56+
BrokerResponseNativeV2 response = new BrokerResponseNativeV2();
57+
assertFalse(response.isTimeoutOverflowReached());
58+
assertFalse(response.isPartialResult());
59+
60+
response.mergeTimeoutOverflowReached(true);
61+
62+
assertTrue(response.isTimeoutOverflowReached());
63+
assertTrue(response.isPartialResult());
64+
65+
JsonNode json = JsonUtils.objectToJsonNode(response);
66+
assertTrue(json.get("timeoutOverflowReached").asBoolean());
67+
assertTrue(json.get("partialResult").asBoolean());
68+
}
69+
5070
private static Set<String> stringSet(String... values) {
5171
return new LinkedHashSet<>(List.of(values));
5272
}

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
@@ -77,6 +77,7 @@
7777
import org.apache.pinot.spi.utils.CommonConstants.Helix;
7878
import org.apache.pinot.spi.utils.CommonConstants.MultiStageQueryRunner;
7979
import org.apache.pinot.spi.utils.CommonConstants.MultiStageQueryRunner.JoinOverFlowMode;
80+
import org.apache.pinot.spi.utils.CommonConstants.MultiStageQueryRunner.TimeoutOverflowMode;
8081
import org.apache.pinot.spi.utils.CommonConstants.MultiStageQueryRunner.WindowOverFlowMode;
8182
import org.apache.pinot.spi.utils.CommonConstants.Query.Request;
8283
import org.apache.pinot.spi.utils.CommonConstants.Query.Response;
@@ -98,6 +99,7 @@
9899
*/
99100
public class QueryRunner {
100101
private static final Logger LOGGER = LoggerFactory.getLogger(QueryRunner.class);
102+
private static final double DEFAULT_LEAF_TIMEOUT_RATIO = 0.8;
101103

102104
private ExecutorService _executorService;
103105
private OpChainSchedulerService _opChainScheduler;
@@ -130,6 +132,8 @@ public class QueryRunner {
130132
@Nullable
131133
private WindowOverFlowMode _windowOverflowMode;
132134
@Nullable
135+
private TimeoutOverflowMode _timeoutOverflowMode;
136+
@Nullable
133137
private PhysicalTimeSeriesServerPlanVisitor _timeSeriesPhysicalPlanVisitor;
134138
/// Cluster-level decision on whether to send stats over the mailbox path, driven by the {@code SendStatsPredicate}
135139
/// at startup time. <b>May be overridden per-request</b> via the {@code KEY_OF_STATS_REPORTING_MODE} metadata key —
@@ -191,6 +195,10 @@ public void init(PinotConfiguration serverConf, String instanceId, @Nullable Ins
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);
@@ -301,6 +309,7 @@ private void processQueryBlocking(WorkerMetadata workerMetadata, StagePlan stage
301309
Map<String, String> requestMetadata) {
302310
StageMetadata stageMetadata = stagePlan.getStageMetadata();
303311
Map<String, String> opChainMetadata = consolidateMetadata(stageMetadata.getCustomProperties(), requestMetadata);
312+
applyLeafDeadlineOverrides(workerMetadata, opChainMetadata);
304313

305314
// The cluster-level _sendStats decision can be overridden per-request by the SubmitWithStream RPC handler via
306315
// MultiStageQueryRunner.KEY_OF_STATS_REPORTING_MODE; in stream mode stats travel out-of-band
@@ -557,6 +566,14 @@ private Map<String, String> consolidateMetadata(Map<String, String> customProper
557566
opChainMetadata.put(QueryOptionKey.WINDOW_OVERFLOW_MODE, windowOverflowMode.name());
558567
}
559568

569+
TimeoutOverflowMode timeoutOverflowMode = QueryOptionsUtils.getTimeoutOverflowMode(opChainMetadata);
570+
if (timeoutOverflowMode == null) {
571+
timeoutOverflowMode = _timeoutOverflowMode;
572+
}
573+
if (timeoutOverflowMode != null) {
574+
opChainMetadata.put(QueryOptionKey.TIMEOUT_OVERFLOW_MODE, timeoutOverflowMode.name());
575+
}
576+
560577
return opChainMetadata;
561578
}
562579

@@ -604,6 +621,53 @@ public void unregisterOpChainCompletionListener(long requestId) {
604621
_opChainScheduler.unregisterCompletionListener(requestId);
605622
}
606623

624+
private void applyLeafDeadlineOverrides(WorkerMetadata workerMetadata, Map<String, String> opChainMetadata) {
625+
if (!workerMetadata.isLeafStageWorker()) {
626+
return;
627+
}
628+
Long leafTimeoutMs = QueryOptionsUtils.getLeafTimeoutMs(opChainMetadata);
629+
Long leafExtraPassiveTimeoutMs = QueryOptionsUtils.getLeafExtraPassiveTimeoutMs(opChainMetadata);
630+
TimeoutOverflowMode timeoutOverflowMode = _timeoutOverflowMode;
631+
String timeoutOverflowModeStr = opChainMetadata.get(QueryOptionKey.TIMEOUT_OVERFLOW_MODE);
632+
if (timeoutOverflowModeStr != null) {
633+
try {
634+
timeoutOverflowMode = TimeoutOverflowMode.valueOf(timeoutOverflowModeStr);
635+
} catch (IllegalArgumentException e) {
636+
LOGGER.warn("Invalid timeout overflow mode: {}", timeoutOverflowModeStr, e);
637+
}
638+
}
639+
QueryThreadContext threadContext = QueryThreadContext.getIfAvailable();
640+
if (threadContext == null) {
641+
return;
642+
}
643+
QueryExecutionContext executionContext = threadContext.getExecutionContext();
644+
long startTimeMs = executionContext.getStartTimeMs();
645+
if (leafTimeoutMs == null && timeoutOverflowMode == TimeoutOverflowMode.BREAK) {
646+
long globalActiveWindowMs = Math.max(1L, executionContext.getActiveDeadlineMs() - startTimeMs);
647+
leafTimeoutMs = Math.max(1L, (long) Math.floor(globalActiveWindowMs * DEFAULT_LEAF_TIMEOUT_RATIO));
648+
}
649+
if (leafExtraPassiveTimeoutMs == null && timeoutOverflowMode == TimeoutOverflowMode.BREAK) {
650+
long globalPassiveWindowMs =
651+
Math.max(0L, executionContext.getPassiveDeadlineMs() - executionContext.getActiveDeadlineMs());
652+
leafExtraPassiveTimeoutMs =
653+
Math.max(0L, (long) Math.floor(globalPassiveWindowMs * DEFAULT_LEAF_TIMEOUT_RATIO));
654+
}
655+
if (leafTimeoutMs == null && leafExtraPassiveTimeoutMs == null) {
656+
return;
657+
}
658+
long activeDeadlineMs = executionContext.getActiveDeadlineMs();
659+
long passiveDeadlineMs = executionContext.getPassiveDeadlineMs();
660+
if (leafTimeoutMs != null) {
661+
activeDeadlineMs = Math.min(activeDeadlineMs, startTimeMs + leafTimeoutMs);
662+
}
663+
if (leafExtraPassiveTimeoutMs != null) {
664+
passiveDeadlineMs = Math.min(passiveDeadlineMs, activeDeadlineMs + leafExtraPassiveTimeoutMs);
665+
}
666+
passiveDeadlineMs = Math.max(passiveDeadlineMs, activeDeadlineMs);
667+
opChainMetadata.put(LeafOperator.LEAF_ACTIVE_DEADLINE_METADATA_KEY, Long.toString(activeDeadlineMs));
668+
opChainMetadata.put(LeafOperator.LEAF_PASSIVE_DEADLINE_METADATA_KEY, Long.toString(passiveDeadlineMs));
669+
}
670+
607671
public StagePlan explainQuery(WorkerMetadata workerMetadata, StagePlan stagePlan,
608672
Map<String, String> requestMetadata) {
609673
if (!workerMetadata.isLeafStageWorker()) {
@@ -613,6 +677,7 @@ public StagePlan explainQuery(WorkerMetadata workerMetadata, StagePlan stagePlan
613677

614678
StageMetadata stageMetadata = stagePlan.getStageMetadata();
615679
Map<String, String> opChainMetadata = consolidateMetadata(stageMetadata.getCustomProperties(), requestMetadata);
680+
applyLeafDeadlineOverrides(workerMetadata, opChainMetadata);
616681

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

0 commit comments

Comments
 (0)