This is an automated email from the ASF dual-hosted git repository.
gortiz pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/pinot.git
The following commit(s) were added to refs/heads/master by this push:
new cab13822b26 [MSE] Add ExplainableOperator so alternative leaf-stage
engines appear in EXPLAIN (#19447)
cab13822b26 is described below
commit cab13822b267fc301c4b78926331f9773f0d0bfc
Author: Gonzalo Ortiz Jaureguizar <[email protected]>
AuthorDate: Mon Sep 7 15:10:39 2026 +0200
[MSE] Add ExplainableOperator so alternative leaf-stage engines appear in
EXPLAIN (#19447)
---
.../pinot/query/context/PlannerContextTest.java | 2 +-
.../apache/pinot/query/runtime/QueryRunner.java | 8 ++--
.../runtime/operator/ExplainableOperator.java | 56 ++++++++++++++++++++++
.../pinot/query/runtime/operator/LeafOperator.java | 3 +-
4 files changed, 64 insertions(+), 5 deletions(-)
diff --git
a/pinot-query-planner/src/test/java/org/apache/pinot/query/context/PlannerContextTest.java
b/pinot-query-planner/src/test/java/org/apache/pinot/query/context/PlannerContextTest.java
index ab965e53e70..d4f1b9453af 100644
---
a/pinot-query-planner/src/test/java/org/apache/pinot/query/context/PlannerContextTest.java
+++
b/pinot-query-planner/src/test/java/org/apache/pinot/query/context/PlannerContextTest.java
@@ -69,7 +69,7 @@ public class PlannerContextTest {
@Test
public void testOptionsAreAccessibleThroughUnwrap() {
QueryEnvironment.Config config = mock(QueryEnvironment.Config.class);
- Map<String, String> options = Map.of("workerRuntime", "datafusion");
+ Map<String, String> options = Map.of("optionKey", "optionValue");
PlannerContext ctx = PlannerContext.forTesting(options, config);
PlannerContext unwrapped =
ctx.getRelOptPlanner().getContext().unwrap(PlannerContext.class);
diff --git
a/pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/QueryRunner.java
b/pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/QueryRunner.java
index 88477e8b01c..d632202afe8 100644
---
a/pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/QueryRunner.java
+++
b/pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/QueryRunner.java
@@ -54,7 +54,7 @@ import org.apache.pinot.query.routing.WorkerMetadata;
import org.apache.pinot.query.runtime.blocks.ErrorMseBlock;
import org.apache.pinot.query.runtime.executor.OpChainCompletionListener;
import org.apache.pinot.query.runtime.executor.OpChainSchedulerService;
-import org.apache.pinot.query.runtime.operator.LeafOperator;
+import org.apache.pinot.query.runtime.operator.ExplainableOperator;
import org.apache.pinot.query.runtime.operator.MultiStageOperator;
import org.apache.pinot.query.runtime.operator.OpChain;
import org.apache.pinot.query.runtime.plan.OpChainConverterDispatcher;
@@ -608,8 +608,10 @@ public class QueryRunner {
Map<PlanNode, ExplainedNode> leafNodes = new HashMap<>();
BiConsumer<PlanNode, MultiStageOperator> leafNodesConsumer = (node,
operator) -> {
- if (operator instanceof LeafOperator) {
- leafNodes.put(node, ((LeafOperator) operator).explain());
+ // Any leaf-stage operator that can describe itself contributes its
explain subtree, so EXPLAIN
+ // reflects what actually executes instead of the pre-execution plan.
+ if (operator instanceof ExplainableOperator) {
+ leafNodes.put(node, ((ExplainableOperator) operator).explain());
}
};
// compile OpChain
diff --git
a/pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/operator/ExplainableOperator.java
b/pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/operator/ExplainableOperator.java
new file mode 100644
index 00000000000..9fde8e8c520
--- /dev/null
+++
b/pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/operator/ExplainableOperator.java
@@ -0,0 +1,56 @@
+/**
+ * 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.pinot.query.runtime.operator;
+
+import org.apache.pinot.query.planner.plannode.ExplainedNode;
+
+
+/// A leaf-stage operator that can describe itself as an [ExplainedNode] for
`EXPLAIN`.
+///
+/// `QueryRunner#explainQuery` compiles the leaf stage with `explain=true`
and, for each operator built,
+/// records the [ExplainedNode] returned here so it can be spliced back into
the broker plan tree. The default
+/// row leaf ([LeafOperator]) implements this, and alternative leaf-stage
operators can implement it too so
+/// their execution shows up in `EXPLAIN` instead of falling back to the
pre-execution Calcite plan.
+///
+/// Implementations must also be [MultiStageOperator]s: the collector receives
each operator as a
+/// [MultiStageOperator], so an implementation outside that hierarchy is
silently never consulted.
+public interface ExplainableOperator {
+
+ /// Produces the explain representation of this operator's stage subtree.
May run the single-stage engine in
+ /// explain mode to obtain segment-level plans; it does not execute the
multi-stage query.
+ ///
+ /// The returned node must not be null, and its title must contain the
substring `Combine` — the value of
+ /// [org.apache.pinot.query.planner.explain.ExplainNodeSimplifier#COMBINE].
That substring is what switches
+ /// `PlanNodeMerger` and `PlanNodeSorter` from positional child matching to
per-segment grouping and
+ /// de-duplication. Positional matching bails out unless every server
reports the same number of children,
+ /// which per-segment plans do not, so a title without `Combine` silently
produces `EXPLAIN` output repeated
+ /// per server and per segment instead of one merged subtree.
+ ///
+ /// For the same reason the title must be identical across servers —
`PlanNodeMerger` compares titles with
+ /// `equals`, so per-server detail in the title defeats merging — and
attributes carrying per-server values
+ /// should be declared `IDEMPOTENT` where equal everywhere, since a
differing non-idempotent `long`
+ /// attribute also leaves the nodes unmerged.
+ ///
+ /// Called at most once per instance, on the thread that compiles the leaf
stage and before the op chain is
+ /// scheduled. Implementations therefore need not be thread-safe here, and
need not support explaining and
+ /// executing the same instance. It may block until the passive deadline
+ /// (`OpChainExecutionContext#getPassiveDeadlineMs()`) and may throw. An
implementation must release
+ /// whatever it started before throwing: the caller does not close an
operator whose `explain()` failed.
+ ExplainedNode explain();
+}
diff --git
a/pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/operator/LeafOperator.java
b/pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/operator/LeafOperator.java
index efa846b409e..dff4f49b05d 100644
---
a/pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/operator/LeafOperator.java
+++
b/pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/operator/LeafOperator.java
@@ -77,7 +77,7 @@ import org.slf4j.LoggerFactory;
/// The data schema of the result expected from leaf stage might be different
from the one returned from single-stage
/// engine, thus the leaf stage operator needs to convert the data types of
the result to conform with the expected
/// data schema.
-public class LeafOperator extends MultiStageOperator {
+public class LeafOperator extends MultiStageOperator implements
ExplainableOperator {
private static final Logger LOGGER =
LoggerFactory.getLogger(LeafOperator.class);
private static final String EXPLAIN_NAME = "LEAF";
private static final ErrorMseBlock CANCELLED_BLOCK =
@@ -230,6 +230,7 @@ public class LeafOperator extends MultiStageOperator {
terminateException.getMessage()) : errorBlock;
}
+ @Override
public ExplainedNode explain() {
if (_executionFuture == null) {
_executionFuture = startExecution();
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]