Visitar URL original
[MSE] Add opt-in sender-sorted merging for global ordered windows by xiangfu0 · Pull Request #19396 · apache/pinot · GitHub
Skip to content

[MSE] Add opt-in sender-sorted merging for global ordered windows - #19396

Open
xiangfu0 wants to merge 18 commits into
apache:masterfrom
xiangfu0:xiangfu0/pinot-issue-19395-ff8b8a
Open

xiangfu0 wants to merge 18 commits into
apache:masterfrom
xiangfu0:xiangfu0/pinot-issue-19395-ff8b8a

Conversation

@xiangfu0

@xiangfu0 xiangfu0 commented Aug 29, 2026 •

Copy link
Copy Markdown
Contributor

PR flow

Broker version check enables sender-sorted merge for global ordered windows when query option set and cluster homogeneous.

flowchart TD
  N0["BrokerStart #40;F1#41;"]:::stAdded
  N1["VersionCheck #40;F2#44; F5#41;"]:::stAdded
  N2["SetKWayMergeSupported #40;F1#44; F3#41;"]:::stModified
  N3["QueryEnvConf #40;F3#44; F4#41;"]:::stModified
  N4["WindowRuleDecision #40;F4#44; F9#41;"]:::stModified
  N5["CreateKWayMergeSortExchange #40;F7#44; F19#41;"]:::stAdded
  N6["CreateMailboxMergeReceive #40;F17#44; F26#41;"]:::stModified
  N7["CreateMergeReceiveOperator #40;F41#41;"]:::stModified
  N8["KWayMergeSupportPredicate #40;F2#41;"]:::stAdded
  N9["ClusterVersionTracker #40;F5#41;"]:::stAdded
  N0 -->|"creates"| N8
  N8 -->|"extends"| N9
  N0 -->|"sets flag in handler"| N2
  N2 -->|"provides flag"| N3
  N3 -->|"used in rule"| N4
  N4 -->|"creates exchange when enabled"| N5
  N5 -->|"converts to plan node"| N6
  N6 -->|"creates operator"| N7
  classDef stAdded fill:#dafbe1,stroke:#1a7f37,color:#1f2328,stroke-width:2px
  classDef stModified fill:#fff8c5,stroke:#9a6700,color:#1f2328,stroke-width:2px
  classDef stRemoved fill:#ffebe9,stroke:#cf222e,color:#1f2328,stroke-width:2px
  classDef stUnchanged fill:#f6f8fa,stroke:#656d76,color:#1f2328,stroke-width:1px
Loading

AI-generated · Green: added · Yellow: modified · Red: removed · Gray: existing

Partial evidence: 3 file patches omitted; 0 truncated.

Diff evidence
  • F1: pinot-broker/src/main/java/org/apache/pinot/broker/broker/helix/BaseBrokerStarter.java — before · after
  • F2: pinot-broker/src/main/java/org/apache/pinot/broker/requesthandler/KWayMergeSupportPredicate.java — after
  • F3: pinot-broker/src/main/java/org/apache/pinot/broker/requesthandler/MultiStageBrokerRequestHandler.java — before · after
  • F4: pinot-common/src/main/java/org/apache/pinot/common/utils/config/QueryOptionsUtils.java — before · after
  • F5: pinot-common/src/main/java/org/apache/pinot/common/utils/helix/ClusterVersionTracker.java — after
  • F7: pinot-query-planner/src/main/java/org/apache/pinot/calcite/rel/logical/PinotKWayMergeSortExchange.java — after
  • F9: pinot-query-planner/src/main/java/org/apache/pinot/calcite/rel/rules/PinotWindowExchangeNodeInsertRule.java — before · after
  • F17: pinot-query-planner/src/main/java/org/apache/pinot/query/planner/logical/PlanFragmenter.java — before · after
  • F19: pinot-query-planner/src/main/java/org/apache/pinot/query/planner/logical/RelToPlanNodeConverter.java — before · after
  • F26: pinot-query-planner/src/main/java/org/apache/pinot/query/planner/plannode/MailboxMergeReceiveNode.java — after
  • F41: pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/plan/PlanNodeToOpChain.java — before · after
  • Regenerate PR flow

Run SET windowKWayMerge=true; SELECT x, SUM(v) OVER (ORDER BY x) FROM t to merge sorted sender streams for a global ordered window.
The switch defaults off; broker config pinot.broker.multistage.window.k.way.merge and query option windowKWayMerge enable it only for homogeneous matching non-SNAPSHOT releases. The sibling MailboxMergeReceiveNode uses oneof tag 19 and visitMailboxMergeReceive; mixed clusters keep a complete receiver sort understood by older servers.
Validation: focused planner/runtime/serde tests, real two-server hybrid ROWS/RANGE comparisons and scoped formatting/license checks passed; all current-head CI suites pass. Maintainer re-review remains required.

@xiangfu0 xiangfu0 added multi-stage Related to the multi-stage query engine query Related to query processing performance Related to performance optimization labels Aug 29, 2026
@codecov-commenter

codecov-commenter commented Aug 29, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 75.21008% with 118 lines in your changes missing coverage. Please review.
✅ Project coverage is 68.59%. Comparing base (f818bae) to head (85be3b0).

Files with missing lines Patch % Lines
...query/planner/plannode/BaseMailboxReceiveNode.java 60.00% 6 Missing and 6 partials ⚠️
...me/operator/SortedMailboxMergeReceiveOperator.java 90.97% 3 Missing and 9 partials ⚠️
...inot/common/utils/helix/ClusterVersionTracker.java 89.53% 2 Missing and 7 partials ⚠️
...he/pinot/query/planner/explain/PlanNodeMerger.java 0.00% 7 Missing and 2 partials ⚠️
...me/operator/utils/BlockingMultiStreamConsumer.java 76.92% 6 Missing and 3 partials ⚠️
...e/pinot/query/runtime/InStageStatsTreeBuilder.java 0.00% 6 Missing ⚠️
...e/pinot/broker/broker/helix/BaseBrokerStarter.java 50.00% 5 Missing ⚠️
...e/rel/rules/PinotWindowExchangeNodeInsertRule.java 68.75% 1 Missing and 4 partials ⚠️
.../query/planner/logical/PlanNodeToRelConverter.java 0.00% 5 Missing ⚠️
.../query/planner/logical/RelToPlanNodeConverter.java 73.68% 2 Missing and 3 partials ⚠️
... and 17 more
Additional details and impacted files
@@             Coverage Diff              @@
##             master   #19396      +/-   ##
============================================
+ Coverage     68.55%   68.59%   +0.04%     
  Complexity     1486     1486              
============================================
  Files          3525     3531       +6     
  Lines        229852   230210     +358     
  Branches      36507    36572      +65     
============================================
+ Hits         157568   157907     +339     
+ Misses        59965    59953      -12     
- Partials      12319    12350      +31     
Flag Coverage Δ
integration 100.00% <ø> (ø)
integration1 100.00% <ø> (ø)
integration2 0.00% <ø> (ø)
java-25 68.59% <75.21%> (+0.04%) ⬆️
lane-a 100.00% <ø> (ø)
lane-b 0.00% <ø> (ø)
temurin 68.59% <75.21%> (+0.04%) ⬆️
unittests 68.58% <75.21%> (+0.04%) ⬆️
unittests1 58.38% <75.72%> (+0.07%) ⬆️
unittests2 40.15% <18.06%> (-0.02%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@xiangfu0
xiangfu0 force-pushed the xiangfu0/pinot-issue-19395-ff8b8a branch from afe7a5d to af8af6a Compare August 31, 2026 09:21
@gortiz

gortiz commented Aug 31, 2026

Copy link
Copy Markdown
Contributor

We are already working on something like that in #19120, #19121 and #19122. I would recommend coordinating our efforts.

@gortiz

gortiz commented Aug 31, 2026 •

Copy link
Copy Markdown
Contributor

I've been working on my own fix for SortedMailboxReceiverOperator, which goes back to what used to be:

┌───────────────────────────────────┬─────────────────────────┬──────────────────────┐
│                                   │ #19396                  │      My branch       │
├───────────────────────────────────┼─────────────────────────┼──────────────────────┤
│                                   │ sortOnSender =          │ sortOnReceiver =     │
│ PinotWindowExchangeNodeInsertRule │ **true**, keeps         │ **false** + explicit │
│                                   │ sortOnReceiver = true   │  Sort                │
├───────────────────────────────────┼─────────────────────────┼──────────────────────┤
│                                   │ +336 lines — k-way      │                      │
│ SortedMailboxReceiveOperator      │ merge over sorted       │ @Deprecated          │
│                                   │ senders                 │                      │
├───────────────────────────────────┼─────────────────────────┼──────────────────────┤
│ MailboxSendOperator               │ +143 — sorts sender     │ untouched            │
│                                   │ output, 10k blocks      │                      │
├───────────────────────────────────┼─────────────────────────┼──────────────────────┤
│                                   │ new sortedOnSender      │                      │
│ Mailbox protocol                  │ confirmation over gRPC  │ untouched            │
│                                   │ + in-memory             │                      │
├───────────────────────────────────┼─────────────────────────┼──────────────────────┤
│ PinotSortExchangeNodeInsertRule   │ comment only —          │ sortOnReceiver =     │
│                                   │ deliberately deferred   │ false                │
└───────────────────────────────────┴─────────────────────────┴──────────────────────┘

The idea I have is to stop generating SortedMailboxReceiveOperator and substitute that with a sort on the receiver side (with optional sort on the sender side when the limit is small). Once we have that, we can start thinking about recovering SortedMailboxReceiveOperator as a k-way merge when the senders guarantee data is sent in order, which is what you and #19121 are doing (although #19121 keeps both the current and the k-way merge).

Also, I don't think MailboxSendOperator should sort on send. Instead, we should add a sort operator on the sender opchain and keep MailboxSendOperator agnostic about ordering.

@xiangfu0
xiangfu0 force-pushed the xiangfu0/pinot-issue-19395-ff8b8a branch 2 times, most recently from cafa948 to f151331 Compare September 1, 2026 09:48
@xiangfu0 xiangfu0 changed the title [MSE] Support sender-side sorting for sort exchanges [MSE] Merge sender-sorted streams for ordered windows Sep 1, 2026
@xiangfu0

xiangfu0 commented Sep 1, 2026

Copy link
Copy Markdown
Contributor Author

@gortiz I rebased this onto current master and refactored it around the coordination feedback.

Validation on head f151331206ca6a13157b1d93e55bb889d4c39c25: the 20-module runtime reactor passed (planner 1,540 tests; runtime 4,640 tests, 18 skips), 348 focused planner/runtime tests passed, all four hygiene gates passed for planner/runtime/perf, and the eight-domain Pinot review completed with no remaining findings. The refreshed 400k-row matched JMH comparison was noisy in both directions, so I reported the raw values in the PR body without making a performance claim.

@xiangfu0
xiangfu0 force-pushed the xiangfu0/pinot-issue-19395-ff8b8a branch 5 times, most recently from fa82748 to e968380 Compare September 8, 2026 09:08
@xiangfu0
xiangfu0 force-pushed the xiangfu0/pinot-issue-19395-ff8b8a branch 2 times, most recently from 515927f to f87d986 Compare September 23, 2026 21:36
@xiangfu0

xiangfu0 commented Sep 23, 2026 •

Copy link
Copy Markdown
Contributor Author

Rebased onto master after #19412 and kept the responsibilities separate: sender sorting owns ordering; #19396 k-way merges only confirmed-sorted local/gRPC streams and safely falls back for mixed/custom transports.

Final coverage review added two targeted regressions for queued read-ahead preservation during fallback and buffered-cursor cleanup on sender error. The focused class passes 21/21, module formatting/checkstyle/license gates pass, and Codecov improved to 91.37% patch coverage.

Exact head 9642c66b99 is conflict-free and all 13 check runs pass. The linter setup failure was a transient Maven Central resolution miss; retrying only that job passed. No review threads remain; maintainer approval is the only remaining gate.

@xiangfu0
xiangfu0 force-pushed the xiangfu0/pinot-issue-19395-ff8b8a branch 2 times, most recently from 9642c66 to 7ecc01c Compare September 24, 2026 01:38
@xiangfu0

xiangfu0 commented Sep 24, 2026 •

Copy link
Copy Markdown
Contributor Author

Rebased and simplified on top of master 257a0e7df2 (which includes #19412); the updated PR head is 7ecc01c0fd. The patch shrank from 38 to 27 files and from 2,379 to 1,957 changed lines, with the benchmark harness kept temporary and out of the PR.

Fresh matched JMH results on JDK 25, 400k rows, two servers, local + gRPC fan-in:

Ordered window Rebased master PR Mean change
Global 193.400 ± 9.810 ms/op (3 forks, 15 measurements) 137.801 ± 14.177 ms/op -28.7%
Partitioned 247.859 ± 27.999 ms/op (2 forks, 10 measurements) 184.532 ± 31.146 ms/op -25.5%

Both JMH-reported intervals are non-overlapping. Global is the stronger signal: every PR measurement was below every baseline measurement. The partitioned aggregate also favors the PR, but visible fork variance means 25.5% is this run's estimate, not a stable production-wide effect size.

Local validation passed: 89 focused runtime tests, all 243 QueryCompilationTest cases, and planner/runtime Spotless, Checkstyle, and license gates. Fresh exact-head CI is now running after the force-with-lease update.

@xiangfu0
xiangfu0 force-pushed the xiangfu0/pinot-issue-19395-ff8b8a branch from 7ecc01c to 5aeb374 Compare September 24, 2026 09:35
@xiangfu0 xiangfu0 changed the title [MSE] Merge sender-sorted streams for ordered windows [MSE] Merge sender-sorted streams for global ordered windows Sep 24, 2026
@xiangfu0
xiangfu0 force-pushed the xiangfu0/pinot-issue-19395-ff8b8a branch from aa52944 to 10dd7e9 Compare September 25, 2026 12:47

@gortiz gortiz left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the thorough PR description and benchmark matrix; the design is careful about mixed-version compatibility.

Main requests:

  1. Drop the no-op Sort above the exchange when the exchange sorts on the receiver (inline at PinotWindowExchangeNodeInsertRule). Every receiver already returns rows in order. On new servers the Sort is a pass-through, and on old servers it is a second full sort. With it gone, SortOperator.isInputSorted and SortedMultiStageOperator are no longer needed.
  2. Add a kill switch: a broker config plus a query option, in the style of sortExchangeCopyThreshold, but as a boolean, because the window Sort has no fetch for a threshold to act on.
  3. Rolling upgrade: with a new broker and old servers, the same rows can be sorted several times (old leaf V1 ORDER BY ... LIMIT 2147483647, old receiver full sort, old Sort above it). Results stay correct, but queries are slower until the upgrade finishes.
  4. Tests: a randomized merge-vs-full-sort test (senders, block splits, tie density, starvation order) would protect the equal-head / lazy-heap logic.

Other comments are inline; several are nits or confirmations.

@xiangfu0
xiangfu0 marked this pull request as ready for review September 30, 2026 16:00
@xiangfu0
xiangfu0 force-pushed the xiangfu0/pinot-issue-19395-ff8b8a branch from 555f55c to 6dff651 Compare September 30, 2026 19:06
@gortiz

gortiz commented Oct 2, 2026

Copy link
Copy Markdown
Contributor

I'd like to use this PR to fix how MSE assigns the responsibility for ordering. Today, ordering should be a requirement that the logical plan guarantees. Instead, the physical plan and the runtime keep getting extra machinery to guarantee it: sort/sortedOnSender on the exchange and mailbox nodes, SortedMailboxReceiveOperator, and now a per-block sorted.on.sender marker. I consider the marker a blocker.

The runtime has to trust the plan. Re-checking ordering on every block is like a JIT adding checks for invariants that the front-end compiler already proved. It is also fragile: MailboxSendOperator.isSortedOnSender() requires a SortOperator right below the send, so any order-preserving operator we add there later (a buffer, for example) silently disables the optimization. And it goes through the whole mailbox layer (GrpcSendingMailbox, InMemorySendingMailbox, MailboxService, ReceivingMailbox, MailboxContentObserver, ChannelUtils, BlockingMultiStreamConsumer) for an opt-in feature.

What I'd suggest instead is to express ordering with simple plan nodes, not flags:

On the Calcite side, this could be a new PinotKWayMergeSortExchange: an exchange that requires its input to be sorted on the collation and preserves that order in its output. PlanFragmenter would turn it into a plain MailboxSendNode plus the new merge receive node. A rule similar to PinotSortExchangeCopyRule could push the limit/offset of a Sort above it into the receive.

In practice, for this PR that means removing the mailbox metadata and the sortedOnSender parameter of offer()/offerRaw(), MseBlockWithStats.isSortedOnSender(), isLastBlockSortedOnSender(), MailboxSendOperator.isSortedOnSender(), hasExplicitSortInput() and the full-sort fallback in the merge receiver. A per-row order check is fine in tests, but not as a production path.

Compatibility gets simpler too. On a new server:

Plan Comes from New server
plain receive current broker MailboxReceiveOperator
receive with sort=true broker older than #19412 MailboxReceiveOperator + SortOperator
new merge receive node new broker, option enabled k-way merge

For a new broker with old servers, it's the broker's responsibility not to send the new node and to keep generating a plan that old servers understand, i.e. a plain receive with a Sort downstream. We already have the mechanism for this: SendStatsPredicate in SAFE mode watches the instance configs and detects when any broker or server reports a different Pinot version. Today only servers use it to decide whether to send MSE stats, but the broker can reuse the same logic and only pick PinotKWayMergeSortExchange when the cluster is homogeneous. As a last line of defense, an old server can't deserialize the new node (PlanNodeDeserializer throws Unsupported PlanNode type), so the query fails loudly instead of returning wrong results.

With this, sortedOnSender and sort in plan.proto and the flags in PinotLogicalSortExchange can be deprecated, and SortedMailboxReceiveOperator can go away. Old ORDER BY plans already have a Sort with the fetch above the receive, so they get the bounded implementation with a LIMIT. For old window plans, the server injects a Sort without a fetch. That part can be a follow-up.

@xiangfu0

xiangfu0 commented Oct 4, 2026

Copy link
Copy Markdown
Contributor Author

@gortiz updated at 01af90617f. Ordering is now a logical-plan guarantee: an explicit Sort feeds PinotKWayMergeSortExchange, and fragmentation creates a plain sender plus a distinct merge receive with serialized optional fetch/offset. Mailbox block markers, sender structural checks, production row-order checks, and the new merge receiver's full-sort fallback are removed.

The broker gate requires matching, non-SNAPSHOT broker/server release versions and fails closed on missing/unreadable configs, registration failure or disconnect; multi-cluster queries disable it. Unsupported clusters use plain receive plus an explicit MAX-fetch Sort, preserving complete window input. Legacy sort=true plans still use the deprecated full-sort receiver; its replacement and old-flag cleanup remain follow-up work. The gate is a point-in-time snapshot, so a concurrent version/topology change can still cause a loud unsupported-node query failure.

Recent unchanged planner/window coverage passed 962 cases, including full-input following/RANGE regressions. This latest commit only corrects the existing SNAPSHOT assertion (+7/-3 lines); both broker gate tests, JDK25 warning-enabled compilation, formatting, checkstyle and license checks passed, with no warnings on added lines. All 17 reported checks were green at b047dd4; CI is running on the new head. Please re-review the architecture and retained legacy compatibility path.

@gortiz gortiz left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for reworking this around the plan. The marker is gone, the order is a plan guarantee, and the merge has its own node, which is the model I asked for. I still see some issues before this can land. The first five are the ones I'd consider blocking; the inline comments have the details.

  1. The broker gate takes a global ZooKeeper lock on every MSE query. getAsBoolean() runs for every query, even without windows or with the option off, and isConnected() ends in ZkClient.waitForKeeperState, which acquires the same event lock the ZK event thread holds while it processes events and reconnects.
  2. The listener rescans the whole cluster on every instance config event, on the spectator event thread, even when the feature is off. That is 1 + N ZK reads per event, and every server writes its instance config on start and shutdown, so a rolling restart costs O(N²) reads per broker.
  3. The gate duplicates SendStatsPredicate.Safe and behaves worse than it: it clears the flag before rescanning, stays off after a single failed read with nothing to re-evaluate it, does not log which instances block it, and counts non-live instances, so a stale znode from a decommissioned host disables the feature forever without telling anyone. I'd extract a shared version tracker and use it from both places.
  4. There are now two models for the same concept. KWayMergeExchangeNode and MailboxMergeReceiveNode extend nodes whose sort flags they set to the opposite of their meaning, so every visitor needs an instanceof check, equals() switches to getClass(), and anything reading isSort() gets the wrong answer for the merge node. Meanwhile the old sortOnSender/sortedOnSender flags stay. I'd make the merge receive a sibling PlanNode with its own visitor method, and deprecate the old flags and field 7 of plan.proto in this PR.
  5. The merge path never runs end to end. The gate excludes SNAPSHOT versions, so CI, quickstarts and every local cluster always take the old path. Planner tests force the flag, and operator tests use mocked mailboxes. Nothing goes planner, proto, opchain, gRPC and back, nothing compares against the receiver sort, and hybrid/logical tables (the reason for the ServerPlanRequestVisitor change) are not covered. I'd add a test-only way to open the gate and a CustomDataQueryClusterIntegrationTest that compares windowSortOnSender=true and false with ROWS and RANGE frames, DESC NULLS FIRST, duplicate keys and a hybrid table.

Smaller things, also inline: fetch/offset on the merge receive are never set in production but add permanent proto fields with semantics that differ from SortNode; fetch == Integer.MAX_VALUE is used as an implicit planner-to-server signal; the merge receive uses the active deadline instead of the passive one; a dead isSortOnReceiver() guard; and an unrelated pinot-segment-local/pom.xml change.

A few more that don't need an inline comment:

  • Changing the window input Sort to fetch=MAX_VALUE changes 124 lines of plan JSON and the EXPLAIN of every window query, also with the feature off. In production SortOperator.create already uses Integer.MAX_VALUE when the fetch is missing, so this is hardening, not a fix. I'd move it to its own PR.
  • The feature has five names (windowSortOnSender, KWayMerge*, MailboxMergeReceive, MAIL_MERGE_RECEIVE, SORTED_MAILBOX_MERGE_RECEIVE). windowSortOnSender also clashes with the existing sortOnSender on the wire and suggests it covers all windows, while it only affects windows without PARTITION BY. I'd pick one term, e.g. windowKWayMerge.
  • There are no-op leftovers from earlier iterations: the local variable in ServerPlanRequestVisitor.visitMailboxSend, the one-line extractMseBlock wrapper in BlockingMultiStreamConsumer, and blank lines in SenderCursor.
  • In the merge loop, each row does a heap remove() and add(), and there is no pass-through once a single sender remains. That's not a regression against the full sort, just something to keep in mind.
  • The description doesn't mention the new config, the query option, oneof tag 19, the visitor change or the SNAPSHOT exclusion, and it says "zero offset" while the code uses -1.

Comment thread pinot-common/src/main/proto/plan.proto Outdated
Comment thread pinot-segment-local/pom.xml Outdated
@xiangfu0
xiangfu0 force-pushed the xiangfu0/pinot-issue-19395-ff8b8a branch from 01af906 to 6f63db5 Compare October 8, 2026 01:58
@xiangfu0

xiangfu0 commented Oct 8, 2026 •

Copy link
Copy Markdown
Contributor Author

@gortiz rebased on master f818bae6d7 and pushed 85be3b0e80e4265ceeb6258e7db4582c4415511c with the review fixes. All 14 inline requests have individual evidence-backed replies and are resolved.

The current rebased gate reads only cached state on an enabled query, and shared incremental tracking watches live participant names and config notifications. The planner-internal ExchangeNode uses an explicit k-way mode; ordinary and merge receives are siblings under BaseMailboxReceiveNode with dedicated receive visitors. Legacy flags are deprecated. The single-run requirement is a serialized node hint, unused merge limits are removed, and the merge uses passive receive deadlines.

The current hybrid integration fixture compares receiver-sort and merge results through real cluster planner/proto/server/opchain execution for ROWS/FOLLOWING and RANGE/peer frames with nulls, duplicate keys, DESC NULLS FIRST and all 16 rows (8 offline + 8 realtime). It asserts complete results and a physical merge plan, but does not assert local/gRPC mailbox statistics; its test-only override resets the supplier to false in finally. The earlier integration pass and 968 planner/serde/plan-fixture cases, 177 focused runtime cases, tracker/capability/query-option tests and scoped formatting/license/style/warning checks are historical local validation, not a claim about fresh rebased-head CI.

For the non-inline comments: the earlier Calcite receiver MAX-fetch hardening, 124 JSON plan edits and unrelated annotations dependency are removed. The physical converter still encodes MAX_VALUE for complete-input window fallbacks on older servers that ignore the single-run hint. The query option is windowKWayMerge and the broker key is pinot.broker.multistage.window.k.way.merge. The no-op local variable and extraction wrapper are removed. The compact description now includes the config key, merge oneof tag 19, dedicated receive visitor and explicit SNAPSHOT exclusion. The final formatting cleanup removes the extra SenderCursor blank line (-1 line); the restack has no functional source changes. Heap remove/add and single-remaining-stream improvements are left out as the informational performance follow-up, without adding speculative optimization or benchmark code.

The feature remains opt-in and the memory limitation tracked by #19395 remains open. This update does not claim universal performance superiority or a validated AUTO selector. Focused stack validation passed 603 cases and the separate AUTO suite passed 417 cases on functionally identical trees; 36 scoped hygiene goals and four final runtime checks passed. All current-head CI suites now pass on the five final stack heads, without a failed-job rerun. Maintainer re-review and approval remain required; green CI does not establish acceptance.

@xiangfu0
xiangfu0 requested a review from gortiz October 8, 2026 04:13
xiangfu0 and others added 17 commits October 7, 2026 23:10
Make the optimization opt-in, remove the redundant receiver sort, and add randomized and plan-contract coverage.
Reproduce: run globalPresortedWindow with four senders and windowSortOnSender=true. Use a two-level tournament to reduce per-row comparisons, with a 10k-row boundary test.
Use one priority queue merge path and keep rollout fallback, bounded output and mailbox progress handling. Enable windowSortOnSender=true for global ordered windows; the default remains receiver sorting.
Compile queries before planning and explaining, and document the intentional legacy receiver assertion. This keeps the sender-sorted window tests free of new deprecation warnings.
Expect the explicit MAX_VALUE fetch on receiver sorts below ordered
windows. This preserves complete window input when a response cap is
configured; SQL limits, window frames and ordering remain unchanged.

Update only the 122 affected golden outputs (124 sort nodes). The full
593-case plan suite and existing complete-window regressions pass.
Share incremental live-version tracking without query-path ZooKeeper access. Separate ordered exchange and receive plan nodes, preserve explicit sender ordering, remove unused merge limits, and validate hybrid local/gRPC execution. Drop unrelated window-plan hardening and dependency changes.
Preserve complete sorted window runs and the shared live-version tracker.
The regression exercises three rows with a response limit of one.
@xiangfu0
xiangfu0 force-pushed the xiangfu0/pinot-issue-19395-ff8b8a branch from 7270608 to de1f3c7 Compare October 8, 2026 06:21
Remove the extra empty line noted in review.
Runtime behavior is unchanged.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

multi-stage Related to the multi-stage query engine performance Related to performance optimization query Related to query processing

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants