[Sandbox] Narrow scan for sort+limit queries whose output is a subset of sort/filter columns#22294
Conversation
…bset of the sort/filter columns The late-materialization (QTF) rewrite declined whenever the output columns were a subset of the sort/filter columns, on the assumption that the non-QTF plan would read the same columns anyway. That assumption does not hold: when the only output column is (a subset of) the sort key, the projection is not pushed into the scan, so the fallback plan reads every column. Fire the rewrite whenever the query touches fewer columns than the full scan, so the scan is narrowed to just the needed columns. Decline only when every column is needed (narrowing would be a no-op). Existing decline tests for the subset case are converted to assert the narrowed-scan plan. Signed-off-by: Rishabh Kumar Maurya <rishma@amazon.com>
PR Reviewer Guide 🔍Here are some key observations to aid the review process:
|
PR Code Suggestions ✨Explore these optional code suggestions:
|
|
Thanks for taking this up @rishabhmaurya Can you share the Datafusion Physical Plans at the Shard before and after your changes for Q25 and Q27 ? |
|
@expani updated the PR with the plan |
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #22294 +/- ##
============================================
- Coverage 73.37% 73.34% -0.04%
+ Complexity 76067 76016 -51
============================================
Files 6075 6075
Lines 345368 345368
Branches 49718 49718
============================================
- Hits 253424 253316 -108
- Misses 71769 71845 +76
- Partials 20175 20207 +32 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
Description
PPL queries of the shape
... | sort <col> | head <k> | fields <cols>could read every column in the table even when only a few columns are needed, when the projected (output) columns were a subset of the sort/filter columns.The analytics engine has a late-materialization rewrite that narrows the scan to only the columns a query needs (sort keys + filter columns below the sort, plus the display columns above it). It was declining to fire whenever the set of output columns was already contained in the set of sort/filter columns, on the assumption that in that case the ordinary (non-rewritten) plan would read the same narrow set of columns anyway — so the rewrite would save nothing.
That assumption does not hold. When the sole output column is the sort key (or a subset of the sort/filter columns), the column projection is not pushed down into the scan, so the non-rewritten plan reads the full row — all columns — to answer what is effectively a one- or two-column query. Declining the rewrite in this case is a latency cliff.
Approach
This keeps the existing behavior for the common case (output columns are a superset of sort/filter columns) and additionally covers the degenerate case (output ⊆ sort/filter), where the narrowed scan now avoids materializing every column for every row. When there are no extra display columns to defer, the rewrite degenerates to "narrow scan + re-fetch the same few needed columns for the K survivors" — negligible next to the columns it stops reading for all rows.
Shard physical plans (before → after)
Captured on the data node. The key change is the scan's
projection: before, it lists every column; after, it carries only the columns the query needs (the sort-key column indices drop from their position among all columns, e.g.@16, to@0).q25 —
where SearchPhrase != '' | sort EventTime | fields SearchPhrase | head 10Before:
After:
q27 —
where SearchPhrase != '' | sort EventTime, SearchPhrase | fields SearchPhrase | head 10Before:
After:
Benchmark
ClickBench dataset (~100M rows, ~100 columns), single shard per node. Times are warm, two runs each. To isolate the effect of this change, all other query-acceleration paths were disabled for the measurement, so the numbers reflect the projection-narrowing alone.
sort EventTime | head 10 | fields EventTimewhere SearchPhrase != '' | sort EventTime | fields SearchPhrase | head 10where SearchPhrase != '' | sort EventTime, SearchPhrase | fields SearchPhrase | head 10The first row is a synthetic single-column probe (output column == sole sort key) — the clearest case of the degenerate shape this PR fixes. q25 and q27 are standard ClickBench queries that exhibit the same pattern (output column is a subset of the sort/filter columns).
Queries that already projected a superset of the sort/filter columns are unaffected (the rewrite already fired for them).
Testing
LateMaterializationPlanShapeTests— the two cases that previously asserted the rewrite was declined (output = sole sort key; output ⊆ sort+filter) are converted to assert the narrowed-scan plan. Full suite (26 tests) passes.spotlessJavaCheckclean.Check List
--signoff.