Overview
The StarTree Engine is an alternative, high-performance execution runtime for the Multi-Stage Query Engine (MSQE). It keeps everything you already know about MSQE — the SQL dialect, the query planner, and Pinot’s segment/index-powered table scans — but executes most of the query’s operators (joins, aggregations, sorts, window functions, set operations) in native code instead of Java. Under the hood it is built on Apache DataFusion, a vectorized query engine written in Rust, extended with a native Arrow-based data exchange layer for moving data between query stages.What changes — and what doesn’t
A multi-stage query runs in three kinds of stages:
Query planning is unchanged: the broker still parses, plans, and optimizes the query the same
way, so
EXPLAIN plans, query options, and the general query lifecycle look the same. Because
leaf stages stay in Java, all of Pinot’s indexing (inverted, sorted, star-tree, JSON, etc.)
keeps working exactly as before.
Enabling the StarTree Engine
Enablement has two layers: a cluster-level prerequisite and a per-query switch. The StarTree Engine is off by default — queries run on the Java MSQE runtime unless explicitly routed.Cluster prerequisite
Every broker and server must start the native runtime. This is controlled by one configuration key, set in the broker and server configuration:In StarTree Cloud these settings are managed as part of the deployment and are enabled by
default. Contact StarTree support if the StarTree Engine is not available in your environment.
Memory and spill-to-disk behavior has its own set of settings — see
Spill to disk for large queries.
Per-query switch
Route an individual multi-stage query to the StarTree Engine with theworkerRuntime query
option. Using the SET command:
The StarTree Engine only applies to multi-stage queries. Single-stage queries are unaffected by
the
workerRuntime option.workerRuntime = 'startree' but the cluster prerequisite is missing, the query
fails with a “runtime is not registered” error — the cluster configuration must be in place
first.
When to expect benefits
The StarTree Engine accelerates the parts of the query that run above the table scan. It shines when intermediate stages do significant work:- Join-heavy queries, especially large hash joins between fact tables.
- Large aggregations and group-bys over data shuffled between servers.
- Sorts and window functions over sizeable intermediate results.
- Queries that move a lot of data between stages — the native exchange layer transfers Arrow batches with far less serialization and garbage-collection overhead than the Java mailbox.
- Memory-intensive queries: the native runtime uses a bounded off-heap memory pool with spill-to-disk for sorts and aggregations, so large queries degrade gracefully instead of pressuring the JVM heap.
When it doesn’t help
- Leaf-dominated queries. If most of the time is spent scanning segments (highly selective filters, simple aggregations pushed down to the leaf), the leaf stage is identical in both runtimes and the StarTree Engine has little to accelerate.
- Very fast, small queries. Crossing between the JVM and the native runtime has a small fixed cost per query and per batch. Queries that complete in a few tens of milliseconds on Java MSQE may see no gain, or a slight regression.
- A few specific query shapes currently regress and are being actively closed (for example, some ASOF joins and certain UNION ALL patterns).
Join strategies
Beyond the default hash join, two join strategies are available natively through the standard Calcite join hint:Spill to disk for large queries
Aggregations (GROUP BY/DISTINCT), sorts, and sort-merge joins can spill to local disk under
memory pressure instead of failing with an out-of-memory error — the query gets slower, not
killed. Spill is backed by a shared native memory pool on each broker and server, sized from
the available off-heap memory (container memory minus the JVM max heap).
Malformed or out-of-range values for these settings fail broker/server startup rather than
silently falling back to a default.
Unsupported cases
Not every query pattern runs natively yet. There are two very different behaviors, depending on when the engine detects the unsupported pattern.Transparent fallback to Java MSQE (query still succeeds)
At planning time the broker checks the query plan for patterns the StarTree Engine does not support. If it finds one, it silently ignores theworkerRuntime option and runs the whole
query on the Java MSQE runtime. The query succeeds and returns the same results as if you had
never set the option; the only trace is an INFO log line on the broker
(Disabling DataFusion for this query: <reason> ...).
Patterns that currently fall back at planning time:
INTERSECT ALLandEXCEPT ALL/MINUS ALLset operations.- Aggregations that return arrays or collections (e.g.
ARRAY_AGG). - Certain optimizer-hint-driven aggregation paths that have no native equivalent, such as
is_leaf_return_final_resultcombined withSUMPRECISION, funnel functions, or raw sketch aggregations. UNNESTwhen the column-pruning optimization is enabled for it.- Unsupported column types, when they are visible in the logical plan (see below).
Query fails with an error (no fallback)
Some patterns can only be detected after planning, while the plan is being prepared for native execution on the servers. At that point there is no fallback: the query fails and the client receives an error. To run such a query, remove theworkerRuntime option (or rewrite
the query).
Cases that fail at runtime:
- Unsupported column types reaching an intermediate stage when not detectable at planning
time:
MAP,OBJECT, and exotic array shapes. Supported types are INT, LONG, FLOAT, DOUBLE, BOOLEAN, TIMESTAMP, STRING, JSON, BIG_DECIMAL, BYTES, and their common array variants. - Unsupported join sub-cases: LOOKUP joins outside INNER/LEFT/SEMI/ANTI, SEMI/ANTI lookup joins with non-equality conditions, and certain ASOF join match conditions.
- Functions outside the supported envelope — see the function support section below for the specific lists of native, bridged, and unsupported functions.
Function support
Aggregation functions. The common aggregations run fully natively:COUNT, SUM, MIN,
MAX, AVG, BOOL_AND, BOOL_OR, and the population statistics VAR_POP, STDDEV_POP,
and COVAR_POP. Every other Pinot aggregation function — percentiles, DISTINCTCOUNT and its
sketch variants, SUMPRECISION, funnel functions, and so on — executes through a built-in
Java bridge: results are identical to Java MSQE, at some extra cost when aggregation state
crosses the native/Java boundary. Known exceptions:
VAR_SAMPandSTDDEV_SAMPcurrently fail at runtime with anInvalid arithmetic operationerror.- Aggregations that return arrays or collections (e.g.
ARRAY_AGG) fall back to Java MSQE at planning time, as noted above.
CASE, IN/NOT IN,
and array construction run natively. All other registered Pinot scalar functions are
automatically bridged to their Java implementation as long as their argument and return types
are among INT, LONG, FLOAT, DOUBLE, BOOLEAN, STRING, BYTES, and TIMESTAMP (BIG_DECIMAL and
JSON results are carried as strings). A scalar function outside that envelope — for example
one that takes or returns arrays or Java objects (theta-sketch set operations being a
supported exception) — fails at runtime with an Unsupported function error.
Window functions. The standard window functions run natively: ROW_NUMBER, RANK,
DENSE_RANK, PERCENT_RANK, CUME_DIST, NTILE, LAG, LEAD, FIRST_VALUE,
LAST_VALUE, and NTH_VALUE, plus the native aggregations listed above used with an OVER
clause. There is no Java bridge for window functions: a Pinot-specific window function outside
this set fails at runtime.
BIG_DECIMAL precision. BIG_DECIMAL columns are fully supported in comparisons,
sorting, joins, GROUP BY, DISTINCT, and MIN/MAX/SUM/AVG/COUNT, using an
order-preserving binary encoding. However, arithmetic (+ - * / %) and aggregation over
BIG_DECIMAL are computed in double-precision floating point — matching Java MSQE behavior,
not arbitrary-precision decimal arithmetic. If your workload depends on exact decimal
arithmetic beyond double precision, verify results before relying on it. Custom Java
UDFs/UDAFs over BIG_DECIMAL are not yet supported on the StarTree Engine.Behavioral differences to be aware of
A small number of cases run natively but intentionally behave differently from Java MSQE:- Integer division follows standard SQL.
1 / int_coltruncates to an integer, as in PostgreSQL, whereas Java MSQE historically evaluated it as floating-point division. If a query relies on the legacy behavior, add an explicitCAST(... AS DOUBLE). - Some Java-MSQE-specific optimizer hints are not honored (e.g. group-trim hints), so queries using them may return results computed by a different — standard — strategy.
- Detailed stats require the streaming stats channel. Per-stage and per-operator response
stats for natively executed stages — including leaf counters such as
numSegmentsQueried— are only delivered when MSQE’s streaming stats channel is active. Enable it per query withSET streamStats = true;or cluster-wide with the broker configurationpinot.broker.mse.stream.stats=true(all servers must run a version that supports it). Without it, StarTree Engine responses carry only broker-side stats. In addition, a few plan shapes (e.g. semi-join and dynamic-broadcast sub-plans) do not yet surface their leaf-stage stats. All of this affects observability, not results.
Monitoring
- The broker logs when a query falls back to Java MSQE and why.
-
The native runtime exposes its own Prometheus metrics endpoint
(
pinot.startree.rust.metrics.port, default8079). All metrics are prefixedrust_:In StarTree Cloud these metrics are also stored in Prometheus and can be read using Grafana as usual. -
In addition, brokers and servers export two engine-related meters through the standard Pinot
metrics pipeline:
datafusionOpchainAllocatorLeakCloseCountanddatafusionOpchainAllocatorLeakedBytes. These are safety-net counters that track native memory reclaimed when a query’s allocator is closed while still holding buffers; they should normally stay at zero. - Standard Pinot query response metadata is returned for all queries, with the stats-coverage caveat noted above.

