feat(query): adaptively distribute lazy row fetch#20172
Draft
dantengsky wants to merge 1 commit into
Draft
Conversation
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 2ffdf2e0e9
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
dantengsky
marked this pull request as draft
July 18, 2026 10:51
Contributor
🤖 CI Job Analysis
📊 Summary
❌ NO RETRY NEEDEDAll failures appear to be code/test issues requiring manual fixes. 🔍 Job Details
🤖 AboutAutomated analysis using job annotations to distinguish infrastructure issues (auto-retried) from code/test issues (manual fixes needed). |
dantengsky
force-pushed
the
perf/adaptive-distributed-row-fetch
branch
from
July 18, 2026 11:56
2ffdf2e to
76ca521
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
I hereby agree to the terms of the CLA available at: https://docs.databend.com/dev/policies/cla/
Summary
Problem
Lazy materialization first scans the columns needed by filtering and sorting, keeps
_row_id, and reads the deferred columns only afterORDER BY ... LIMIThas selected the final rows.In a distributed top-N plan, scan workers send their partial results through a Merge exchange. The coordinator produces the final top-N row IDs and currently performs the entire RowFetch itself. This is efficient when the selected rows are concentrated in a few storage blocks, but the coordinator becomes a remote-I/O bottleneck when those rows are spread across many storage blocks. Always distributing RowFetch would remove that bottleneck but add unnecessary shuffle overhead when the result has a low block count.
This PR keeps RowFetch local when the selected rows touch few blocks and distributes it when they touch many blocks.
Scope
Adaptive routing is available only when the physical plan meets all three conditions:
LIMIT > max_threads * 4. This planner heuristic avoids adding an exchange when the result is unlikely to contain enough blocks to benefit; it is not a correctness condition.These are planner-time guards. For eligible plans, the runtime still counts the distinct blocks in the final top-N result and chooses between local and distributed RowFetch. If any guard fails, the physical plan remains unchanged.
For an eligible plan, the planner adds a RowFetch-specific exchange before RowFetch and a second Merge exchange after RowFetch:
Runtime decision
The coordinator first coalesces all top-N input batches and counts distinct block prefixes in
_row_id. Coalescing makes the decision against the complete result; deciding per batch could classify every small batch as local even when the combined result covers many blocks.The routing rule is:
Block count is used instead of row count because remote reads and metadata lookup are organized by block. The threshold allows a bounded number of local I/O waves before a cluster shuffle becomes worthwhile.
Node behavior
Coordinator before RowFetch
RowFetch destination nodes
Coordinator after RowFetch
ORDER BY.Fragment scheduling
The fragment that consumes the first Merge exchange must run only on the coordinator. However, the following node-to-node RowFetch exchange needs the same fragment ID registered on every executor so that all destinations can construct their exchange channels. The scheduler therefore sends the real merge-dependent plan to the coordinator and an empty source stub to the other workers. The downstream fragment containing RowFetch runs on every destination node.
Observability
Profiles, metrics, and logs expose the selected mode, input batches, rows, distinct blocks, destination distribution, and block-affinity statistics.
Performance
Manual benchmark on a three-node object-storage deployment with disk cache disabled used two top-N workloads. Both returned 10K rows:
Full-column scan (no RowFetch)reads all projected columns during TableScan and does not execute the code changed by this PR; it is included as a control.Lazy RowFetchreads deferred columns after the limit and exercises the adaptive path.Beforeis rc7 build4065bd334b;Afteris rc7 buildb5f73c7e51, which contains the same change set applied tomainby this PR. The 99-block full-column scan could not be compared because both builds attempted to spill in a write-restricted test environment. The 99-block local RowFetch path changed by +3.4%, the 792-block full-column control changed by +7.6%, and the target 792-block distributed RowFetch path improved by 51.6%.Tests
Unit tests cover batch coalescing, the local decision, block-affinity routing, and skewed block distributions. Optimizer integration tests cover plan selection and merge-dependent fragment scheduling. Cluster sqllogic coverage checks that row IDs and fetched payloads remain paired after shuffle and merge.
Type of change
This change is